Tuesday, July 15, 2014

OO Example in Golang

Find this article and more at Golang Code Examples

This golang code sample demonstrates the following go language features:
  • string and float64 data types
  • constants
  • variables with initializers
  • iterating ranges
  • slices
  • encapsulation / visibility
  • variadic functions
  • new function
  • pointers
  • structs / struct literals
  • methods
  • multiple implicit interfaces
  • signature based polymorphism

Code Example


package main    // Executable commands must always use package main.

import (
    "fmt"       // fmt.Println formats output to console
    "math"      // provides math.Sqrt function
)

// ----------------------
//    Shape interface
// ----------------------
// Shape interface defines a method set (consisting of the area method)
type Shape interface {
    area() float64          // any type that implements an area method is considered a Shape
}
// Calculate total area of all shapes via polymorphism (all shapes implement the area method)
func totalArea(shapes ...Shape) float64 {   // Use interface type as as function argument
    var area float64                        // "..." makes shapes "variadic" (can send one or more)
    for _, s := range shapes {
        area += s.area()    // the current Shape implements/receives the area method
    }                       // go passes the pointer to the shape to the area method
    return area
}

// ----------------------
//    Drawer interface
// ----------------------
type Drawer interface {
    draw()                  // does not return a type
}
func drawShape(d Drawer) {  // associate this method with the Drawer interface
    d.draw()
}

// ----------------------
//      Circle Type
// ----------------------
type Circle struct {        // Since "Circle" is capitalized, it is visible outside this package
    x, y, r float64         // a Circle struct is a collection of fields: x, y, r
}
// Circle implements Shape interface b/c it has an area method
// area is a method, which is special type of function that is associated with the Circle struct
// The Circle struct becomes the "receiver" of this method, so we can use the "." operator
func (c *Circle) area() float64 {   // dereference Circle type (data pointed to by c)
    return math.Pi * c.r * c.r      // Pi is a constant in the math package
}
func (c Circle) draw() {                                
    fmt.Println("Circle drawing with radius: ", c.r)    // encapsulated draw implementation for Circle type
}
// ----------------------
//     Rectangle Type
// ----------------------
type Rectangle struct {     // a struct contains named fields of data
    x1, y1, x2, y2 float64  // define multiple fields with same data type on one line
}
func distance(x1, y1, x2, y2 float64) float64 {         // lowercase functin name visible only in this package
    a := x2 - x1
    b := y2 - y1
    return math.Sqrt(a * a + b * b)
}
// Rectangle implements Shape interface b/c it has an area method
func (r *Rectangle) area() float64 {       // "r" is passed by reference
    l := distance(r.x1, r.y1, r.x1, r.y2)  // define and assign local variable "l"
    w := distance(r.x1, r.y1, r.x2, r.y1)  // l and w only available within scope of area function
    return l * w
}
func (r Rectangle) draw() {                // "r" is passed by value
    fmt.Printf("Rectangle drawing with point1: (%f, %f) and point2: (%f, %f)\n", r.x1, r.y1, r.x2, r.y2)
}
// ----------------------
//    MultiShape Type
// ----------------------
type MultiShape struct {
    shapes []Shape  // shapes field is a slice of interfaces
}
//
func (m *MultiShape) area() float64 {
    var area float64
    for _, shape := range m.shapes {    // iterate through shapes ("_" indicates that index is not used)
        area += shape.area()            // execute polymorphic area method for this shape
    }
    return area
}

func main() {
    c := Circle{0, 0, 5}                                            // initialize new instance of Circle type by field order "struct literal"
                                                                    // The new function allocates memory for all  fields, sets each to their zero value and returns a pointer
    c2 := new(Circle)                                               // c2 is a pointer to the instantiated Circle type
    c2.x = 0; c2.y = 0; c2.r = 10                                   // initialize data with multiple statements on one line
    fmt.Println("Circle Area:", totalArea(&c))                      // pass address of circle (c)
    fmt.Println("Circle2 Area:", totalArea(c2))                     // c2 was defined using built-in new function
    r := Rectangle{x1: 0, y1: 0, x2: 5, y2: 5}                      // "struct literal" rectangle (r) initialized by field name
    fmt.Println("Rectangle Area:", totalArea(&r))                   // pass address of rectangle (r)
    fmt.Println("Rectangle + Circle Area:", totalArea(&c, c2, &r))  // can pass multiple shapes
    m := MultiShape{[]Shape{&r, &c, c2}}                            // pass slice of shapes
    fmt.Println("Multishape Area:", totalArea(&m))                  // calculate total area of all shapes
    fmt.Println("Area Totals:", totalArea(&c, c2, &r))              // c2 is a pointer to a circle, &c and &r are addresses of shapes
    fmt.Println("2 X Area Totals:", totalArea(&c, c2, &r, &m))      // twice the size of all areas
    drawShape(c)                                                    // execute polymorphic method call
    drawShape(c2)
    drawShape(r)
}



Output


Circle Area: 78.53981633974483
Circle2 Area: 314.1592653589793
Rectangle Area: 25
Rectangle + Circle Area: 417.69908169872417
Multishape Area: 417.69908169872417
Area Totals: 417.69908169872417
2 X Area Totals: 835.3981633974483
Circle drawing with radius:  5
Circle drawing with radius:  10
Rectangle drawing with point1: (0.000000, 0.000000) and point2: (5.000000, 5.000000)

Process finished with exit code 0




Notes

Nearly every line of this code example is documented; Scroll to the right to see all of the comments. This code example shows how to implement a solution using object oriented techniques in Golang.

Golang does not use a class keyword but is none-the-less object oriented.

Methods and interfaces are the language constructs that define objects and their behavior.

There is no explicit implementation inheritance; However, type embedding can be used for the same purpose.

Golang includes very little ceremony in order to provide language features.
  • no semi-colons at end of each statement
  • little declaration required - golang performs implicit type conversion and eliminates need for var keyword with ":=" operator
  • visibility and encapsulation - Some languages require you to use public, private, friend, etc. keywords. Golang simply looks for capitalization of function/struct names.
  • A type implements an interface by implementing the methods - Some languages require explicit declaration of intent; You don't have to find every interface implementation and label it with the new interface name.

By adding a receiver to a function definition, Golang allows you to associate that method with a structure, whereby you can use the "." operator to call the associated method.

Golang does not have a self or this keyword to reference to the current instance. In the method example of func (c *Circle) area() float64 the receiver struct is named "c". Use that variable name, rather than this to refer to the current instance.

Golang uses signature based polymorphism; If a struct, in the example above, implements an area method that returns a float64, then it is a Shape.

This is what some call "Duck Typing" meaning, "If it walks like a duck and sounds like a duck then it must be a duck."

In our case, if the struct implements the area method then it is a Shape type struct.

You can define a method on any type you define in your package, not just a struct.

You cannot define a method on a type from another package, or on a basic type.

Methods can be associated with a named type func drawShape(d Drawer) or a pointer to a named type func (c *Circle) area() float64.

Benefits of using a pointer receiver:
  • Avoid copying value of parameter on each method call
  • Allow method to modify the value that its receiver points to

Golang is a statically typed language, which has some significant benefits over dynamic languages like:
  • Compiler catches errors that could be otherwise hard to find runtime errors
  • Better documentation via type signatures that include type of arguments
  • Better error reporting, sooner. Compiler will report line number and indicate exactly what caused the bug.
  • Code runs faster
The first one is especially important when your code base grows.

A few important Golang topics not discussed in this post include closures, concurrency, error handling and testing.

A more notable exclusion is lack of mention of Embedding. which is an OO technique used by Go to inherit/embed the implementation from another class.

Go does not use the class keyword, but I refer to a Go class as a struct and it's associated methods, which combined encapsulates the properties and behavior of a "class" of objects.


References

http://www.golang-book.com/
http://golang.org/doc/code.html
http://tour.golang.org/

This work is licensed under the Creative Commons Attribution 3.0 Unported License.

No comments:

Post a Comment