Go Programming - OOP
Concepts of Programming Languages
Sebastian Macke
Rosenheim Technical University
Sebastian Macke
Rosenheim Technical University
Go doesn't have try-catch exception based error handling.
Go distinguishes between recoverable and unrecoverable errors.
Recoverable: e. g. file not found
err := ioutil.WriteFile(src.Name(), []byte("hello"), 0644) if err != nil { log.Error(err) }
Unrecoverable: array access outside its boundaries, out of memory
5The "defer" statement lets us ensure that code runs before a function exits
f, err := os.Open("myfile.txt") if err != nil { return err } defer f.Close() // Will be executed on function exit, even in case of an unrecoverable error. .... // read, write
func getElement(array []int, index int) int { if index >= len(array) { panic("Out of bounds") } return array[index] } func getElementWithRecover(array []int, index int) (value int) { defer func() { r := recover() if r != nil { fmt.Println("Recovered with message '", r, "'") } value = -1 }() return getElement(array, index) } func main() { array := []int{3, 4, 5} ret := getElementWithRecover(array, 3) fmt.Println("return value: ", ret) }
Wikipedia: Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).
Go is not a pure object oriented programming language but
allows an object-oriented style of programming
// Rational represents a rational number numerator/denominator. type Rational struct { numerator int denominator int } // Constructor func NewRational(numerator int, denominator int) Rational { if denominator == 0 { panic("division by zero") } return Rational{ numerator: numerator, denominator: denominator, } }
// Multiply method for rational numbers func Multiply(r Rational, y Rational) Rational { return NewRational(r.numerator*y.numerator, r.denominator*y.denominator) }
// Multiply method for rational numbers func (r *Rational) Multiply(y Rational) Rational { return NewRational(r.numerator*y.numerator, r.denominator*y.denominator) } r1 := NewRational(1, 2) r2 := NewRational(2, 4) r3 := r1.Multiply(r2)
The variable r is in both cases similar to the Java this, which reference to the class instance
13package main import "fmt" type MyInt int func (b *MyInt) Inc() { *b = *b + 1 } func main() { var b MyInt = 10 fmt.Println(b) b.Inc() fmt.Println(b) }
package main import "fmt" type Bar struct{} func (b *Bar) GetHello() string { fmt.Println(b) return "Hello" } type Foo struct { B *Bar } func main() { var f Foo fmt.Println(f.B) // Output "nil" fmt.Println(f.B.GetHello()) // Null Pointer error or "Hello"? }
Encapsulation of methods is basically is just a syntax element.
func (r *Rational) Multiply(y Rational) Rational { .... }
is internally transformed into
func Multiply(r *Rational, y Rational) Rational { .... }
A lot of languages do it this way.
16Composition and inheritance are two ways to achieve code reuse and design modular systems
class Engine { .... } class Car { private Engine engine }
class Animal { .... } class Dog extends Animal { }
Composition and inheritance are two ways to achieve code reuse and design modular systems
// Point is a two dimensional point in a cartesian coordinate system. type Point struct{ x, y int }
// ColorPoint extends Point by adding a color field. type ColorPoint struct { Point // Embedding simulates inheritance but it is (sort-of) delegation! c int }
fmt.Println(cp.x) // access inherited field
comparison of behavior in Java and Go at
- ../src/oop/delegation/delegation.go
- ../src/oop/delegation/delegation.java
- ../src/oop/delegation2/delegation.go
- ../src/oop/delegation2/delegation.java
An interface is a set of methods
In Java:
interface Switch { void open(); void close(); }
In Go:
type OpenCloser interface { Open() Close() }
class Door implements Switch { public void Open() { ... } public void Close() { ... } }
type Door struct {} func (d *Door) Open() { ... } func (d *Door) Close() { ... }
Door implicitly satisfies the interface OpenCloser
Go supports polymorphism only via interfaces, not through classes
24The print functions in the fmt package support the following interface
type Stringer interface { String() string }
if tmp, ok := object.(Stringer); ok { // The object implements stringer }
package main import "fmt" type DoorOpen bool func (d DoorOpen) String() string { if d == true { return "Door is open" } else { return "Door is closed" } } func main() { var d DoorOpen = false fmt.Println(d) }
func main() { var p = Point{1, 2} var cp = ColorPoint{Point{1, 2}, 3} // embeds Point fmt.Println(p) fmt.Println(cp) fmt.Println(cp.x) // access inherited field // p = cp // does not work: No hierarchy, no polymorphism // p = cp.Point // works // s is an interface and supports Polymorphism var s fmt.Stringer s = p // check at compile time fmt.Println(s) s = cp fmt.Println(s) }
func PrintVariableDetails(v any) { typeof := reflect.TypeOf(v) fmt.Printf("The variable with type '%s' has the value '%v'\n", typeof.Name(), v) } func main() { var someValue any someValue = 2 PrintVariableDetails(someValue) someValue = "abcd" PrintVariableDetails(someValue) if tmp, ok := someValue.(string); ok { fmt.Println("someValue is a string and has the value", tmp) } }
type any = interface{}
github.com/s-macke/concepts-of-programming-languages/blob/master/docs/exercises/Exercise3.md
31type Foo struct { Name string } type Bar struct { Name string } type X struct { Foo Bar } func main() { y := X{ Foo: Foo{Name: "Foo!"}, Bar: Bar{Name: "Bar!"}, } fmt.Print(y.Foo.Name) fmt.Print(y.Bar.Name) //fmt.Print(y.Name) // compile error, Ambiguous Reference }