Golang Code Examples

Polymorphic Shapes

15 Jul 2014

Description

This example demonstrates how to leverage structs and methods to derive polymorphic benefits when creating rectangle and circle shapes.

We use the variadic idiom to pass any number of shape object parameters to calculate their total area.

We also demonstrate passing by value and how to use pointers to pass by reference.


Golang Features

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

Notes

A language is usually considered object-based if it includes the basic capabilities for an object: identity, properties, and attributes.

A language is considered object-oriented if it is object-based and also has the capability of polymorphism and inheritance. – Wikipedia

This code example demonstrates polymorphism.

The Doubly Linked List article demostrates inheritance via the embedded struct, Value:

type Value struct {
    Name string
    MilesAway int
}
type Node struct {
    Value               // Embedded struct
    next, prev  *Node
}

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

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