oo in go
Tue, Nov 18, 2014Subclassing in Go
Quick summary: This article shows some techniques for mimicking behaviour of “oo” languages like java or python in go.
Golang does not offer language features for doing object oriented programming in the style of java or python (or javascript or Common Lisp or whatever). The Go FAQ states:
Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing.
When using go, I rarely miss subclassing. Composition is usually the right design tool. However, every once in a while while porting python code I want to emulate the dispatch used in normal OO code. Here’s a concrete example:
#!/usr/bin/env python
class Parent(object):
def flush(self):
print "parent flush"
def close(self):
self.flush()
print "close"
class Child(Parent):
def flush(self):
Parent.flush(self)
print "child flush"
x = Child()
x.close() # prints "parent flush"\n"child flush"\n"close"
The important thing to notice is that
- The parent object’s close() method is called.
- The parent calls the child’s flush method.
This is actually really interesting, and implies that the definition of flush() must be looked up dynamically.
Let’s examine how to get equivalant functionality out of go. First we’ll try embedding.
package main
import "fmt"
type Parent struct {}
func (p *Parent) flush() {
fmt.Println("parent flush")
}
func (p *Parent) close() {
p.flush()
fmt.Println("close")
}
type Child struct {
Parent
}
func (c *Child) flush() {
c.Parent.flush()
fmt.Println("child flush")
}
func main() {
x := new(Child)
x.close() // "parent flush"\n"close"
}
That doesn’t seem to work. The child’s flush() method is never called. Why? Because embedding isn’t subclassing. When Parent.close() is defined, the call to p.flush() is compiled resolved to a call to Parent.flush(). It just doesn’t know or care about the existence of Child.flush(). What we need is for Parent to resolve the call to flush() at runtime, and to do that we’ll need Parent to have a reference to an interface, like so.
package main
import "fmt"
type Person interface {
close() // define an interface to lookup methods on
flush()
}
type Parent struct {
self Person // retain reference to self for dynamic dispatch
}
func (p *Parent) flush() {
fmt.Println("parent flush")
}
func (p *Parent) close() {
p.self.flush() // call the flush method of whatever the child is.
fmt.Println("close")
}
type Child struct {
Parent
}
func (c *Child) flush() {
c.Parent.flush()
fmt.Println("child flush")
}
func main() {
x := new(Child)
x.self = x
x.close() // "parent flush"\n"child flush"\n"close"
}
So that’s all it takes to imitate subclassing using go’s interfaces. It’s a nice tool to have in your pocket for those times when you can’t figure out how else to model your problem.