Skip to content

Latest commit

 

History

History
290 lines (259 loc) · 7.21 KB

Readme.md

File metadata and controls

290 lines (259 loc) · 7.21 KB

Go

Is Go Object-Oriented?

Go does not have class implemenations. But class == struct types + methods in Go

  • struct only holds the state, not the behavior
  • method changes the state of struct types

Access level in a package

  • capitalized fields, methods and functions are public
  • lower case fields, methods and functions are package private

"Constructors" in Go

  • declare lower case struct type, thus making it private
  • and use a capitalized function New which return an object of this struct type
  • This is a factory pattern by default

Composition over Inheritence

  • Go prefers embedding a struct inside the other
  • Ask yourself what is inheritence?
    • base class's data members are in the derived class ==> Go uses composition
    • base class's non-private interfaces can be invoked inside derived class
      • ===> Go uses interface and package-level access level control
    • polymorphism ===> Go uses interface values such as var x <Interface> = <Type>('hello world')
      • so any interface can be associated with any type

Type

  • golang is very serious about variable types
    • var x int = 1
    • y := x the compiler infers new variable y's type
      • which is the same as x's
    • all cast must be explicit (unlike C++)
  • constant const x = 1.1
  • define your own struct type
type MyFloat float64
type Person struct {
	name string
	age int
}
  • struct embedding
    • Composition over inheritance
// copied from https://flaviocopes.com/golang-is-go-object-oriented/
type Dog struct {
	Animal      // Composition by struct embedding 
}
type Animal struct {
	Age int
}
func (a *Animal) Move() {
	fmt.Println("Animal moved")
}
func main() {
	d := Dog{}
	d.Age = 3 // Age automatically becomes part of Dog
	d.Move()  // call Animal's method directly
}

Function

  • func needInt(x int) int { return x*10 + 1 }
  • func needInt(x int) (int, int) { return 1, 2 }
  • bind the return variable
  func needInt(x int) (x, y int) {
    x++
    y += x
    return
  }
  • closure adder is bound to its own variable sum
func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}
x = adder() // sum == 0 now
x(1) // sum == 1 now
x(2) // sum == 3 now 
  • functions can be
    • stored as struct fields
    • passed as arguments to other functions
    • returned from a function or methods

Method

  • there is no class in Go
  • a method is a function with a receiver
// Abs() has v as its receiver
func (v Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
} 
  • methods can only be attached to a type in the same package where this type is defined
  • Use pointer receiver to allow modification
func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

Interface

See this Post

  • interface is a set of methods

  • interface type (I interface{}) is a type

    • var i I interface{} = "hello"
  • all types implement at least zero methods

    • thus they are all of the empty interface type, i.e. interface{}
func DoSomething(v interface{}) {
   // ...
}

will accept any parameter v whatsoever. But they will convert any type to the interface type.

  • Example of a type string implementing an interface I
type I interface {
	M()
}

// we can say: interface `I` holds the concrete type string 
// or: M() has a receiver of type string 
func (s string) M() { /* whatever */ }

// create an interface value
var i I = string("hello") 
// we will be able to call the method
i.M()
  • a type can implement different interfaces

  • an interface can hold many different types

  • t, ok := i.(T) checks if interface value i holds a type T

    • ok == true if yes; otherwise, ok == false
    • t will be the underlying value if yes
  • an interface value i provides Polymorphism

    • for example, every type can have its own way to fmt.Printf() if
      • interface Stringer's method String() string holds this type
      • func (t SomeType) String() string { return <string of t to display> }
  • Polymorphism

func do(i interface{}) {
  switch v := i.(type) {
    case T: ...
    case S: ... 
    default: ...
  }
}

Error

  • built-in interface
type error interface {
  Error() string
}
  • the customized Error type implements an error interface
    • func (e MyError) Error() string { /* handle error */ }

Logic

  • one extra statement before the if condition
if v := math.Pow(x, n); v < lim {
  return v
}
// v is only in the scope of the if condition
  • for loop can ignore end condition for { /* forever */ }
  • switch automatically add break for every case (unlike C++)

defer

  • function executes after the current function returns
  • defer functions are pushed to a stack
    • their executions order is the reversed push order

Pointer

  • No pointer operations!! ==> We do not need the -> like in C++, always use . to access fields.
  • No pointer operation does not imply no pointer
    • there is no pass-by-reference in Go; one always pass by value (this can avoid many bugs)
    • pointer saves the cost of copy in pass-by-value
type Vertex struct {
  X, Y int
}
p := &Vertex{1, 2} // p is a pointer which allows modification of the struct 
p.X = 100 

Array

  • var a [2]string
  • primes := [6]int{2, 3, 5, 7, 11, 13}
  • var s []int = primes[1:4]
  • slices are like Python, a := names[0:2]
    • a is a reference of the array
    • len(a) is length of slice
    • cap(a) is the allocated memory starting from a[0]
  • slice x == nil if len(x) == 0
  • create slice a := make([]int, 5)
  • slice of slices
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}
  • range iterates through (index, value) pairs, like Python's enumerate()
    • for i := range pow { /* index i*/ }
    • for i, val := range pow { /* index i*/ }

Map

  • var m map[<key type>]<value type>
  • Insert by m[key] = value
  • Obtain value by value = m[key]
    • Check exist by val, exit = m[key]
    • exit == false if key is not in m
  • Remove by delete(m, key)

Closure

How to implement a local static variable inside a function? Use Closure

func main() {
  counter := newCounter()
  counter()  // return 1
  counter()  // return 2
}

// Here newCounter() returns an anonymous function
// which has access to n even after it exists
func newCounter() func() int {
  n := 0
  return func() int {
    n += 1
    return n
  }
}

Common ways to use closure

Test

Run go test -v at the package where the test file has a filename ending with _test.go.

func TestSum(t *testing.T) {
  t.Run("[1,2,3,4,5]", testSumFunc([]int{1, 2, 3, 4, 5}, 15))
  t.Run("[1,2,3,4,-5]", testSumFunc([]int{1, 2, 3, 4, -5}, 5))
}

func testSumFunc(numbers []int, expected int) func(*testing.T) {
  return func(t *testing.T) {
    actual := Sum(numbers)
    if actual != expected {
      t.Error(fmt.Sprintf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, actual))
    }
  }
}

More