Golang继承使用设置受保护的值

huangapple go评论76阅读模式
英文:

Golang inheritance using setting protected values

问题

我正在尝试更好地理解如何在Go语言中使用受保护空间。我来自Java,可以通过受保护继承来访问值,但由于Go语言只有组合,我想确保我走在正确的道路上。

问题是:我想在子实现中设置一个值,但不在通用接口上公开一个setter方法。

在没有继承关系的情况下,提供给“子类”一个setter方法的最佳方式是什么?

这意味着我想要:

type Bottom interface {
    GetYouSome()

    // 一些常规方法
    SetSpeed(int)
    DeliveryMechanism() chan string
}

请注意,没有SetDeliveryMechanism(chan string)方法。

我想从实现一些常规方法的基础开始,这意味着实际的实现将提供GetYouSome方法。我还希望这些实现存在于不同的包中。将会有数十个实现,我希望命名空间被隔离(例如,它们都可以使用常量DefaultPort)。

为了说明问题,我创建了一个小项目,结构如下:

.
└── src
    ├── main.go
    └── parent
        ├── child
        │     └── child.go
        └── parent.go

child.go中,我们创建了几种Bottom类型,但在parent.go中,我们定义了样板代码(setter/getter)。问题是我无法在任何地方实例化通道!

理想情况下,实现应该像这样:

//------------- parent.go -------------
package parent

type Bottom interface {
    GetYouSome()

    // 一些常规方法
    SetSpeed(int)
    DeliveryMechanism() chan string
}

// 实现一些常规方法
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)

    // 非法!未导出
    // impl.deliveryChannel = make(chan string)
    return impl
}

// 为了无缝地将Composite视为Bottom
type Composite struct {
    parent.GenericBottom
    name string
}

func (a Composite) GetYouSome() {
    fmt.Println("Inside the actual implementation")
}

我可以想到两种解决方法。

(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"

    // 现在我可以设置通道了吗?不行
    // impl.gb = parent.GenericBottom{speed, make(chan string)}

    impl.gb = parent.NewGenericBottom()
    impl.SetSpeed(speed)
    return impl
}

// 必须通过每个方法进行传递
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)
}

或者(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
}

在没有真正的继承关系的情况下,提供给“子类”一个setter方法的最佳方式是什么?

英文:

<!-- language-all: golang -->
I am trying to understand better how one can use the protected space in Go. I am coming form java which means that I can have values accessible via protected inheritance, as there is only composition here I wanted to make sure I was on the right path.

The problem: I want to set a value in a child implementation, but not expose a setter on the generic interface.

What is the best way to provide a setter to a 'sub-class' when there really isn't a hierarchy?

This means I want:

type Bottom interface {
	GetYouSome()

	// rote things
	SetSpeed(int)
	DeliveryMechanism() chan string
}

Note that there is no SetDeliveryMechanism(chan string) method.

I thought I would start with a base that implements the rote things, meaning that actual implementations will provide the 'GetYouSome' method. I'd also like these to exist inside different packages. There will be dozens of implementations and I'd like to have the namespaces be sandboxed (i.e. they can both use the const DefaultPort).

To illustrate the problem I have made a little project. It is laid out like this:

.
└── src
    ├── main.go
    └── parent
        ├── child
        │&#160;&#160; └── child.go
        └── parent.go

Where in child.go we actually create a few types of Bottom, but in parent.go we actually define the boilerplate code (setters/getters). The problem is that I can't instantiate the channel anywhere!

Ideally an implementation would look like this:

//------------- 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 &quot;parent&quot;

func New(speed int) parent.Bottom {
	impl := new(Composite)
	impl.name = &quot;simple&quot;
	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(&quot;Inside the actual implementation&quot;)
}

There are two ways I can think to get around this.

(1) create a child class that would wrap the GenericBottom class, passing through all the methods. That is le sad, and it also still has the problem that I can't access the deliveryChannel class directly. I would have to build a new constructor into the parent package, then explicitly set the instance in the child class.

//------------- parent.go -------------
func NewGenericBottom() GenericBottom {
    return GenericBottom{0, make(chan string)}
}

//------------- child.go -------------
func New(speed int) parent.Bottom {
	impl := new(ExplicitComposite)
	impl.name = &quot;explicit&quot;

	// 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(&quot;Inside the explicit implementation&quot;)
}

func (e ExplicitComposite) DeliveryMechanism() chan string {
	return e.gb.DeliveryMechanism()
}

func (e *ExplicitComposite) SetSpeed(speed int) {
	e.gb.SetSpeed(speed)
}

OR! (2) I can add a setter method on the GenericBottom. But anyone using the GenericBottom can just cast to that an access it right? I wouldn't really be 'protected'.

Like this:

//------------- 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(&quot;Inside the cheaters bottom&quot;)
}

func NewCheatersBottom(speed int) parent.Bottom {
	impl := new(CheatersBottom)
	impl.SetChannel(make(chan string))
	impl.SetSpeed(speed)
	return impl
}

What is the best way to provide a setter to a 'sub-class' when there really isn't a hierarchy?

答案1

得分: 21

你的主要问题是你是一个Java程序员。我并不是说这是恶意的,或者对Java程序员进行贬低;我的意思是,Java中常见的思维方式与Go中的设计思维完全不同。也许不像在C中编写Haskell代码那样不同,但它们仍然是完全不同的思维方式。

我写了很多草稿,试图“修复”你的代码,但你设计的方式与没有真正继承概念的语言完全不一致。如果你发现自己在Go中使用“base”或“parent”这样的词,甚至是“generic”这个词,你可能即将与类型系统进行一场激烈的斗争。我认为这是大多数从面向对象语言转到Go的人必须经历的阶段。

我建议浏览Go标准库,看看它们如何布置其包。通常,你会发现以下内容:一个包将定义一个或多个接口,以及操作这些接口的函数。然而,实际的、具体的接口实现的数量要么不存在,要么非常少。然后,在另一个包中,它们提供实用的具体实现。与你的代码相比,最引人注目的是,除非实现一个接口自动部分实现另一个接口,否则永远不会有任何部分实现。

你的GenericBottom并没有什么明显的“问题”,我自己当然也写过隐藏的伪抽象类,但在Go中,你希望它们作为genericBottom未导出,并与所有具体实现位于同一个包中。或者在Go 1.4中作为internal package

你可能会说,“但是如果我需要在其他地方定义接口的其他具体实现怎么办?”嗯,你将会重复代码。这可以被视为(并且可能是)Go类型系统的一个弱点,但重复一些代码来实现不同包中的接口是符合惯例和常见的。这就是internal之类的东西的目的,它们在一定程度上缓解了这个问题。

另外,不要把包当作类来对待。这是我刚开始时遇到的问题。中等到大型包通常有10个或更多的文件是相对普遍的。只要它们是一个概念上的单元,那就是它们的用途。这就是Go中实现“protected”功能的方式(更像是默认/无修饰符),通过让类型共享一个包来实现。它们可以访问彼此的未导出字段。

最后一个建议是不要从NewX方法返回接口。你应该(几乎)总是返回指向结构体的指针。通常情况下,你希望构造一个类型,并将其作为接口传递给另一个方法,而不是首先接收一个接口。

我建议进行一次完全的重写。挑战自己;每当你想到“子类”这样的词时,停下来做其他事情。看看你能想出什么。我认为你会发现,虽然一开始可能有点困难,但通过以与类型系统合作的方式编写代码,你最终能够更高效地完成更多的工作,即使这可能导致一些代码重复。

英文:

Your main problem is that you're a Java programmer. I don't mean that to be mean, or cast aspersions on Java programmers; I mean that the common style of thinking in Java is just completely disparate from the kind of design thinking you do in Go. Maybe not as much as trying to write Haskell code in C, but they're still entirely different mindsets.

I've written quite a few drafts trying to "fix" your code, but the way you designed it is just fundamentally at odds with a language with no real notion inheritance. If you find yourself ever using the words "base" or "parent" in Go, and to some degree even the word "generic", you're probably about to have a nice fight with the type system. I think this is a phase most people who come to Go from OO languages have to fight through.

I advise looking through the Go standard library and looking at how they lay out their packages. Generally, you'll find the following: One package will define one or several interfaces, and functions that operate on those interfaces. However, the number of actual, concrete implementations of the interface are either non-existent or very small. Then, in another package, they provide utility concrete implementations. Most strikingly, compared to your code, is that there are never, ever any partial implementations, except in the case that implementing one interface automatically partially implements another.

There's nothing necessarily "wrong" with your GenericBottom, I've certainly made hidden pseudo-abstract classes myself, but in Go what you want is for them to be unexported as genericBottom, and in the same package as all concrete implementations. That or in an internal package as of Go 1.4.

You may now be saying, "but what if I need to define other concrete implementations of my interfaces elsewhere?" Well, you'll be duplicating code. This can be viewed as (and probably is) a weakness of Go's type system, but it's definitely idiomatic and common to repeat some code when implementing an interface in a different package. That's what things like internal were meant to alleviate somewhat.

As another note, do not treat packages like classes. It's a problem I had too at first. It's relatively unremarkable for medium-large packages to have 10 or more files in them. As long as they're a single conceptual unit, that's what they're for. That's how "protected" functionality is achieved in Go (more like default/no modifier actually), by having types share a package. They can all access each others' unexported fields.

The final piece of advice would be to not return interfaces from NewX methods. You should (almost) always return a pointer to the struct. Generally you want to construct a type, and pass it into another method as an interface, not receive an interface in the first place.

I would recommend pretty much a complete rewrite. Challenge yourself; every time you think of words like "child class" stop and do something else. See what you can come up with. I think you'll find that while it's a bit hard at first, you'll ultimately get a lot more done by writing code in a way that's cooperative with the type system, even if it leads to some code stutter.

huangapple
  • 本文由 发表于 2015年9月12日 08:38:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/32533992.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定