我想更好地理解如何在Go中使用受保护的空间.我来自java,这意味着我可以通过受保护的继承访问值,因为这里只有组合我想确保我在正确的路径上.
问题:我想在子实现中设置一个值,但不在通用接口上公开一个setter.
当真正没有层次结构时,为"子类"提供setter的最佳方法是什么?
这意味着我想:
type Bottom interface {
GetYouSome()
// rote things
SetSpeed(int)
DeliveryMechanism() chan string
}
Run Code Online (Sandbox Code Playgroud)
请注意,没有SetDeliveryMechanism(chan string)方法.
我想我会从一个实现死记硬背的基础开始,这意味着实际的实现将提供'GetYouSome'方法.我也希望这些存在于不同的包中.将有几十个实现,我希望将命名空间用沙箱(即它们都可以使用const DefaultPort).
为了说明问题,我做了一个小项目.它的布局如下:
.
??? src
??? main.go
??? parent
??? child
? ??? child.go
??? parent.go
Run Code Online (Sandbox Code Playgroud)
child.go我们在哪里实际创建了几种类型Bottom,但parent.go实际上我们定义了样板代码(setters/getters).问题是我不能在任何地方实例化频道!
理想情况下,实现看起来像这样:
//------------- parent.go -------------
package parent
type Bottom interface {
GetYouSome()
// rote things
SetSpeed(int)
DeliveryMechanism() chan string
}
// Intended to implement the boring things
type GenericBottom struct {
speed int
deliveryChan chan string
}
func (bot *GenericBottom) SetSpeed(speed int) {
bot.speed = speed
}
func (bot GenericBottom) DeliveryMechanism() chan string {
return bot.deliveryChan
}
//------------- child.go -------------
package child
import "parent"
func New(speed int) parent.Bottom {
impl := new(Composite)
impl.name = "simple"
impl.SetSpeed(speed)
// illegal! not exported
// impl.deliveryChannel = make(chan string)
return impl
}
// intended so that we can seamlessly treat the Composite
// as a Bottom
type Composite struct {
parent.GenericBottom
name string
}
func (a Composite) GetYouSome() {
fmt.Println("Inside the actual implementation")
}
Run Code Online (Sandbox Code Playgroud)
我可以通过两种方式来解决这个问题.
(1)创建一个子类,它将包装GenericBottom该类,并传递所有方法.令人伤心的是,它仍然存在我无法deliveryChannel直接访问该类的问题.我必须new在父包中构建一个构造函数,然后在子类中显式设置实例.
//------------- parent.go -------------
func NewGenericBottom() GenericBottom {
return GenericBottom{0, make(chan string)}
}
//------------- child.go -------------
func New(speed int) parent.Bottom {
impl := new(ExplicitComposite)
impl.name = "explicit"
// now I can set the channel? Nope
// impl.gb = parent.GenericBottom{speed, make(chan string)}
impl.gb = parent.NewGenericBottom()
impl.SetSpeed(speed)
return impl
}
// this has to pass through each method
type ExplicitComposite struct {
gb parent.GenericBottom
name string
}
func (e ExplicitComposite) GetYouSome() {
fmt.Println("Inside the explicit implementation")
}
func (e ExplicitComposite) DeliveryMechanism() chan string {
return e.gb.DeliveryMechanism()
}
func (e *ExplicitComposite) SetSpeed(speed int) {
e.gb.SetSpeed(speed)
}
Run Code Online (Sandbox Code Playgroud)
要么!(2)我可以在GenericBottom上添加一个setter方法.但是任何使用GenericBottom的人都可以直接转换为访问权限吗?我不会真的受到"保护".
像这样:
//------------- parent.go -------------
func (bot *GenericBottom) SetChannel(c chan string) {
bot.deliveryChan = c
}
//------------- child.go -------------
type CheatersBottom struct {
parent.GenericBottom
name string
}
func (a CheatersBottom) GetYouSome() {
fmt.Println("Inside the cheaters bottom")
}
func NewCheatersBottom(speed int) parent.Bottom {
impl := new(CheatersBottom)
impl.SetChannel(make(chan string))
impl.SetSpeed(speed)
return impl
}
Run Code Online (Sandbox Code Playgroud)
当真正没有层次结构时,为"子类"提供setter的最佳方法是什么?
Lin*_*ope 14
你的主要问题是你是一名Java程序员.我并不是说那是吝啬,或者是对Java程序员的诽谤; 我的意思是,Java中常见的思维方式与您在Go中的设计思维完全不同.也许没有尝试用C编写Haskell代码那么多,但它们仍然是完全不同的思维模式.
我写了很多试图"修复"你的代码的草稿,但是你设计它的方式基本上与没有真正的概念继承的语言不一致.如果你发现自己在Go中使用了"base"或"parent"这两个词,甚至在某种程度上使用了"generic"这个词,那么你可能会对类型系统进行一场精彩的战斗.我认为这是一个阶段,大多数来自OO语言的人都必须通过这种方式进行斗争.
我建议查看Go标准库并查看它们如何布置它们的包.通常,您将找到以下内容:一个包将定义一个或多个接口,以及在这些接口上运行的功能.但是,接口的实际具体实现的数量要么不存在要么非常小.然后,在另一个包中,它们提供实用的具体实现.最引人注目的是,与您的代码相比,除了实现一个接口自动部分实现另一个接口之外,永远不会有任何部分实现.
没有什么必然与你的"错误" GenericBottom,我当然自己制作了隐藏的伪抽象类,但在Go中你想要的是将它们作为未导出的genericBottom,并且与所有具体实现在同一个包中.那个或在Go 1.4 的内部包中.
您现在可能会说,"但是如果我需要在其他地方定义其他接口的具体实现呢?" 好吧,你将复制代码.这可以被视为(并且可能是)Go类型系统的弱点,但是当在不同的包中实现接口时,重复一些代码肯定是惯用且常见的.这就是为什么事情会internal有所缓解.
另外请注意,不要像处理类一样对待包.这是我一开始遇到的一个问题.对于中大型软件包来说,其中包含10个或更多文件是相对不起眼的.只要它们是一个单一的概念单元,那就是它们的用途.这就是Go中实现"受保护"功能的方式(实际上更像是默认/无修饰符),通过让类型共享包.他们都可以访问彼此未被移植的字段.
最后一条建议是不从NewX方法返回接口.你应该(几乎)总是返回一个指向结构的指针.通常,您希望构造一个类型,并将其作为接口传递给另一个方法,而不是首先接收接口.
我会推荐几乎完全重写.挑战自己; 每当你想到像"儿童班"这样的词语停下来做其他事情.看看你能想出什么.我想你会发现虽然起初有点困难,但你最终会通过与类型系统合作编写代码来完成更多工作,即使它导致某些代码口吃.