Go Programming - OOP

Concepts of Programming Languages

Sebastian Macke

Rosenheim Technical University

Last lecture

2

Last Exercise

3

Error Handling

4

Errors in Go

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

5

Go defer: run code before function exists

The "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
6

unrecoverable error: panic and recover by using defer

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

Exception Pro and Cons

8

Object Oriented Programming

9

Structure of object oriented programming

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

10

Principles of Object Oriented Programming

Go is not a pure object oriented programming language but
allows an object-oriented style of programming

11

Encapsulation I: No classes, but structs

// 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,
    }
}
12

Encapsulation II: Bind together code and data it manipulates

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

13

Orthogonal: Encapsulation can be done with any type

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

Syntax level OOP

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"?
}
15

Syntax level OOP

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.

16

Composition VS. Inheritance

Composition 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 {
}
17

Composition VS. Inheritance?

Composition and inheritance are two ways to achieve code reuse and design modular systems

18

The issue with inheritance

class Runner {
    public void run(Task task) { task.run(); }
    public void runAll(List<Task> tasks) { for (Task task : tasks) { run(task); } }
}

class RunCounter extends Runner {
    private int count = 0;

    @override public void run(Task task) {
        count++;
        super.run(task);
    }

    @override public void runAll(List<Task> tasks) {
        count += tasks.size();
        super.runAll(tasks);
    }
}

When I run 3 tasks with RunCounter.runAll, what value does count have?

19

According to the Go main architects inheritance causes

20

Embedding

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

Polymorphism is not possible with embeddings.

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

var Point p = Point{}
var ColorPoint cp = ColorPoint{}

p = cp // Compile Error
p = cp.Point // Works
22

Delegation of Functions in Go

23

Polymorphism I

An interface is a set of methods

In Java:

interface Switch {
    void open();
    void close();
}

In Go:

type OpenCloser interface {
    Open()
    Close()
}
24

Polymorphism II

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

25

Polymorphism III: Example The stringer interface

The 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
}
26

Polymorphism III: The stringer interface

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

An implementation can support multiple interfaces at the same time.

27

Interfaces and Polymorphism

func main() {
    var p = Point{1, 2}
    var cp = ColorPoint{p, 3} // embeds Point
    fmt.Println(p)
    fmt.Println(cp)
    fmt.Println(cp.x) // access inherited field

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

Recap: Go does support a dynamic type

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{}
29

Summary

30

Exercise 3

31

Exercise

32

Questions

33

Multiple embeddings of same variable or function names

type 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
}
34

Thank you

Sebastian Macke

Rosenheim Technical University

Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)