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