Golang Code Examples

Factory Method and Inheritance

02 Sep 2014

Description

This example demonstrates an OOP pattern (Factory Method) and a way to achieve code reuse via composition.

Granted, Go does not support type/class inheritance--There is no Is-A relationship in Go. However, the benefits of code reuse and encapsulation can still be achieved in Go via Has-A relationships.

Composition over inheritance in object-oriented programming is a technique by which classes may achieve polymorphic behavior and code reuse by containing other classes that implement the desired functionality instead of through inheritance.

Composition

Composition is a way to combine simple objects or data types into more complex ones.

In this example:

  • SetInfo and GetInfo that are only implemented once, but reused by all Widgets
  • Each widget has-a WidgetInfo object
  • WidgetInfo is an embedded type for each Widget

Go uses type composition to achieve the benefits of inheritance:

  • Code Reuse
  • Encapsulation

An implementation of composition over inheritance begins with the creation of various interfaces representing the behaviors that the system must exhibit.

The use of interfaces allows this technique to support the polymorphic behavior that is so valuable in object-oriented programming.

Types (WidgetA and WidgetB) implementing the identified interface methods(Add, Remove, IsEqual, Size) are implemented by each business-domain type.

SetInfo and GetInfo are only implemented once, by the embedded WidgetInfo type.

Factory Method

The factory method pattern is a creational pattern which uses factory methods to deal with the problem of creating objects without specifying the exact class of object that will be created.

This is done by creating objects by calling a factory method (newWidgetA or newWidgetB).


Golang Features

This golang code sample demonstrates the following go language features:

  • constants with iota
  • strconv (int to string conversion)
  • structs
  • methods
  • empty struct
  • anonymous field
  • pointers
  • maps
  • interface compliance


3rd Party Libraries

PrettyTest is used to test the go_oops package in this code example.


Code Example

widget.go

package factory_method_with_reuse

import (
    "strconv"
)

// WidgetType indicates which type (WidgetA or WidgetB) is created
type WidgetType int

const (
    Widget_A = iota  // start at 0 and increment by 1
    Widget_B
)

// Widget is the domain type that this example manipulates
type Widget struct {
    WidgetInfo      // Anonymous field
}

// WidgetInfo is an Embedded type that contains widget data
type WidgetInfo struct {
    id   int
    name string
}

// SetInfo is the WidgetInfo method used to set the widget's id and name
func (wi *WidgetInfo) SetInfo(id int, name string) {
    wi.id = id
    wi.name = name
}

// GetInfo is the WidgetInfo method used to return the WidgetInfo object
func (wi WidgetInfo) GetInfo() WidgetInfo {
    return wi
}

// GetInfo is the WidgetInfo method used to return the WidgetInfo object
func (wi WidgetInfo) String() string {
    return strconv.Itoa(wi.id) + "_" + wi.name
}

// Widget_iface is describing a Widget. Widgets are an unordered, unique list of values.
type Widget_iface interface {
    SetInfo(id int, name string)    // reuse
    GetInfo() WidgetInfo            // reuse
    Add(items ...interface{})
    Remove(items ...interface{})
    IsEqual(a Widget_iface) bool
    Size() int
}

// String displays the string name of the specified widget
func (s WidgetType) String() string {
    switch s {
    case Widget_A:
        return "WidgetA"
    case Widget_B:
        return "WidgetB"
    }
    return ""
}

// Create a new Widget interface based on WidgetType and set WidgetInfo
func New(wt WidgetType, wi WidgetInfo) Widget_iface {
    switch wt {
    case Widget_A:
        return newWidgetA(wi)
    case Widget_B:
        return newWidgetB(wi)
    }
    return nil
}

widgetA.go

package factory_method_with_reuse

// WidgetA's internal data map
type dataA struct {
    m map[interface{}]struct{}
}

// WidgetA's anonymous properties
type WidgetA struct {
    WidgetInfo
    dataA
}

// Create WidgetA data map
func newWidgetA(wi WidgetInfo) *WidgetA {
    w := &WidgetA{}
    w.WidgetInfo = wi
    w.m = make(map[interface{}]struct{})
    var _ Widget_iface = w  // Enforce interface compliance
    return w
}

// Add any number of items to the map
func (d *dataA) Add(items ...interface{}) {
    if len(items) == 0 {
        return
    }
    for _, item := range items {
        d.m[item] = struct{}{}
    }
}

// Remove items from the data map
func (d *dataA) Remove(items ...interface{}) {
    if len(items) == 0 {
        return
    }
    for _, item := range items {
        delete(d.m, item)
    }
}

// Size returns the number of items
func (d *dataA) Size() int {
    return len(d.m)
}

// IsEqual tests whether d and a have the same items
func (d *WidgetA) IsEqual(a Widget_iface) bool {
    if sameSize := len(d.m) == a.Size(); !sameSize {
        return false
    }
    if a.GetInfo().String() != d.GetInfo().String() {
        return false
    }
    return true
}

// IsEmpty indicates whether the map is empty
func (d *dataA) IsEmpty() bool {
    return d.Size() == 0
}

widgetB.go

package factory_method_with_reuse

// WidgetB's internal data map
type dataB struct {
    m map[interface{}]struct{}
}

// WidgetB's anonymous properties
type WidgetB struct {
    WidgetInfo
    dataA
}

// Create WidgetB data (deferred creation of a widget)
func newWidgetB(wi WidgetInfo) *WidgetB {
    w := &WidgetB{}
    w.WidgetInfo = wi
    w.m = make(map[interface{}]struct{})
    var _ Widget_iface = w  // Enforce interface compliance
    return w
}

// Add any number of items to data map
func (d *dataB) Add(items ...interface{}) {
    if len(items) == 0 {
        return
    }
    for _, item := range items {
        d.m[item] = struct{}{}
    }
}

// Remove items from the map
func (d *dataB) Remove(items ...interface{}) {
    if len(items) == 0 {
        return
    }
    for _, item := range items {
        delete(d.m, item)
    }
}

// Size returns the number of items
func (d *dataB) Size() int {
    return len(d.m)
}

// IsEqual tests whether d and a have the same items
func (d *WidgetB) IsEqual(a Widget_iface) bool {
    if sameSize := len(d.m) == a.Size(); !sameSize {
        return false
    }
    if a.GetInfo().String() != d.GetInfo().String() {
        return false
    }
    return true
}

// IsEmpty indicates whether the map is empty
func (d *dataB) IsEmpty() bool {
    return d.Size() == 0
}

factorymethodtest.go

package factory_method_with_reuse

import (
    "testing"
    "github.com/remogatto/prettytest"
)

type mySuite struct {
    prettytest.Suite
}

func TestRunner(t *testing.T) {
    prettytest.Run(t, new(mySuite))
}

func (s *mySuite) TestNew() {
    wi := WidgetInfo{1001, "A"}
    s.Equal(wi.name, "A")

    w := Widget{WidgetInfo{1001, "A"}}
    s.Equal(w.name, "A")

    wa := New(Widget_A, WidgetInfo{1001, "A"})
    s.Equal(wa.GetInfo().id, 1001)
    s.Equal(wa.GetInfo().name, "A")
}

func (s *mySuite) TestAdd() {
    wa := New(Widget_A, WidgetInfo{1001, "A"})
    wa.Add("thinga", "ma", "bop", 1, 2)
    s.Equal(wa.Size(), 5)
}

func (s *mySuite) TestRemove() {
    wa := New(Widget_A, WidgetInfo{1001, "A"})
    wa.Add("thinga", "ma", "bop")
    wa.Remove("bop")
    s.Equal(wa.Size(), 2)

    wa.Remove("ma")
    s.Equal(wa.Size(), 1)

    wa.Remove("xxx")
    s.Equal(wa.Size(), 1)
}

func (s *mySuite) TestIsEqual() {
    wa := New(Widget_A, WidgetInfo{1001, "A"})
    wa.Add("thinga", "ma", "bop")
    wb := New(Widget_B, WidgetInfo{1001, "B"})
    wb.Add("thinga", "ma", "bop")
    s.Equal(wa.IsEqual(wb), false)

    wc := New(Widget_A, WidgetInfo{1001, "A"})
    wc.Add("thinga", "ma", "bop")
    s.Equal(wa.IsEqual(wc), true)

    wc.SetInfo(1001, "C")
    s.Equal(wa.IsEqual(wc), false)

    wc.SetInfo(1001, "A")
    s.Equal(wa.IsEqual(wc), true)

    wc.Remove("bop")
    s.Equal(wa.IsEqual(wc), false)
}

Notes

Interfaces

Java requires you to explicitly declare that a class "implements" an interface, but in Go you simply implement the methods defined by the interface.

So, in Go inheritance. is achieved by way of duck typing:

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Given the following interface definition:

type Widget_iface interface {
    SetInfo(id int, name string)
    GetInfo() WidgetInfo
    Add(items ...interface{})
    Remove(items ...interface{})
    IsEqual(a Widget_iface) bool
    Size() int
}

The Widget_iface interface is composed of methods that each Widget implements.

Each widget has an anonymous field, WidgetInfo, that implements SetInfo and GetInfo and those methods can be reused by each widget.

Each widget (WidgetA and WidgetB) must implement Add, Remove, IsEqual and Size.

Each widget's implementation of Add, Remove, IsEqual and Size is completely hidden from the caller.

Go does not have inheritance baked in, but you can achieve inheritance effects using interfaces and composition.

Note that ...interface{} parameter for Add, Remove, IsEqual and Size is what enables those methods to accept any widget type (WidgetA or WidgetB).

The interface parameter is what enables polymorphism in Go: The implementation of Add, Remove, IsEqual and Size can be written without any mention of any specific widget type and thus can be used transparently with each type.

Enforce interface compliance

var _ Widget_iface = w 

Note that there no type assertions/type-casting required in our example yet we still have the benefit of compile-time type-checking with the line above.

Polymorphism

To see an example of polymorphism in Go, check out the Polymorphic Shapes article.

Classes

When I say, "Classes", I'm be referring to Go's type/structs and their associated methods.

Thread Safety

The underlying data structure in this example is a map.

Maps in Go are not thread safe.

If you need thread safety, use either channels or a mutex.

PrettyTest

We use prettytest to test our code.

For a description of prettytest, see Invoicing USD and PrettyTest


Output

Run $ go get github.com/go-goodies/go_oops to get the gooops package and run the pta command in your <GOSRCDIRECTORY>/github.com/go-goodies/gooops/factorymethodwith_reuse directory to see the test results.

pta

For details on pta, see PrettyAutoTest


Conclusion

Go is expressive, concise, clean, and efficient. Its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.

So, every time that I think, "There are no 'objects' in Golang. Where are the objects?" ... or "Where are the 'generics'?" ... or "Where are the built-in functional programming idioms?", Go provides a better way of doing what I want to do.


References

comments powered by Disqus
The content of this site is licensed under the Creative Commons 3.0License and code is licensed under a BSD license