0

Clarification: I'm just learning GO, and came across this problem.

I'm trying to implement a "class" that inherit a method that invokes a "virtual" method that should be implemented by the child class. Here's my code:

https://play.golang.org/p/ysiaPwARkvl

package main

import (
    "fmt"
    "sync"
)

type Parent struct {
  sync.Mutex
  MyInterface
}

func (p *Parent) Foo() {
  p.Lock()
  defer p.Unlock()
  p.Bar()
}

func (p *Parent) B(){
  panic("NOT IMPLEMENTED")
}

func (p *Parent) A() {
  p.Lock()
  defer p.Unlock()
  p.B()
}

type MyInterface interface {
  Foo()
  Bar()
}

type Child struct {
  Parent
  Name string
}

func (c *Child) Bar(){
  fmt.Println(c.Name)
}

func (c *Child) B(){
  fmt.Println(c.Name)
}

func main() {
  c := new(Child)
  c.Name = "Child"
  // c.A() panic
  c.Foo() // pointer error
}

I left out some code regarding the sync.Mutex that does some async update to the values of Child.

So apparently in A() or Foo() the pointer p have type Parent. How should I change my code so that A/Foo refer to B/Bar defined in the Child class?

user1948847
  • 955
  • 1
  • 12
  • 27
  • 2
    Don't do that. This does not work, no matter how hard you try. You'll just hurt yourself. Leave inheritance based OOP behind. – Volker Aug 31 '18 at 03:59
  • 1
    Think of `c.A()` being a short form of `c.Parent.A()`. `c.Parent.A()` only has access to fields and methods in `c.Parent` and does not know about `c` itself. – svsd Aug 31 '18 at 04:48
  • This is an example of the template method pattern which is used in OOP for code reuse. In Go we reuse through composition, not through inheritance. – bereal Aug 31 '18 at 05:03
  • 2
    `I'm trying to implement a "class"` -- this is your first mistake. Don't do that in Go. Go doesn't have classes. Don't fight the language. – Jonathan Hall Aug 31 '18 at 07:28

1 Answers1

5

You're wanting an is-a relationship (inheritance) when Go only provides has-a relationships (composition):

  • Go has no inheritance, thus there is no is-a relationship between two types. Child is not a kind of Parent, so a pointer to a Parent cannot retain a pointer to a Child; Child has-a Parent contained within it instead.

Because there is no is-a relationship between Parent and Child, Parent.Foo cannot receive an object of type Child, nor can it use any of the methods that Child implements. Also, this means that no Parent can access any method defined on a Child such as Bar() or B() directly.

Typically, Parent will not need to call some method in Child. If it does, you'd pass the Parent method an argument, such as an interface that Child satisfies for you to call the method through the interface or a closure that calls the Child method:

// Use of an interface that Child satisfies.
type Beta interface {
    B()
}
func (p *Parent) A(b Beta) {
    p.Lock()
    defer p.Unlock()
    b.B()
}

// Use of a closure.
func (p *Parent) Foo(bar func()) {
    p.Lock()
    defer p.Unlock()
    bar()
}
func callFoo(p *Parent, c *Child) {
    callBar := func() {
        c.Bar()
    }
    p.Foo(callBar)
}

func (c *Child) Bar() {
    // stub
}

func (c *Child) B() {
    // stub
}

You get the Child can call Parent method behavior for free, but it only appears similar to inheritance. child.Foo() actually performs child.Parent.Foo(), which means Parent.Foo still receives a Parent instance (hence the name), not a Child instance.

However, Parent cannot access any information about Child that Child does not explicitly share. Interfaces and closures can act as mechanisms between two classes analogous to the friend keyword in C++, except they're more restrictive than the friend keyword. After all, Child doesn't need to share everything with Parent, just the bits that it wants to share, somewhat similar to this pattern in C++. Personally I would prefer interfaces for this as it allows your Parent "class" to work with multiple types that all satisfy a common interface, making it pretty much the same as calling the method from a normal function or an entirely unrelated type's method.