Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang inheritance using setting protected values

Tags:

inheritance

go

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
        │   └── 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 "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")
}

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 = "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)
}

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("Inside the cheaters bottom")
}

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?

like image 701
rybit Avatar asked Sep 12 '15 00:09

rybit


1 Answers

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.

like image 149
Linear Avatar answered Oct 10 '22 04:10

Linear