Skip to content

Latest commit

 

History

History
301 lines (218 loc) · 8.77 KB

File metadata and controls

301 lines (218 loc) · 8.77 KB

#Object-oriented We talked about functions and structs in the last two sections, did you ever think about using functions as fields of a struct? In this section, I will introduce you another form of method that has receiver, which is called method.

##method Suppose you define a struct of rectangle, and you want to calculate its area, we usually use following code to achieve this goal.

package main
import "fmt"

type Rectangle struct {
	width, height float64
}

func area(r Rectangle) float64 {
	return r.width*r.height
}

func main() {
	r1 := Rectangle{12, 2}
	r2 := Rectangle{9, 4}
	fmt.Println("Area of r1 is: ", area(r1))
	fmt.Println("Area of r2 is: ", area(r2))
}

Above example can calculate rectangle's area, we use the function called area, but it's not a method of a rectangle struct (like methods in class in classic Object-oriented language). The function and struct are two independent things as you may notice.

It's not a problem so far. What if you also have to calculate area of circle, square, pentagon, even more, you are going to add more functions with very similar name.

Figure 2.8 Relationship between function and struct

Obviously, it's not cool. Also the area should be the property of circle or rectangle.

For those reasons, we have concepts about method. method is affiliated of type, it has same syntax as function except one more thing after the keyword func that is called receiver which is the main body of that method.

Use the same example, Rectangle.area() belongs to rectangle, not as a peripheral function. More specifically, length, width and area() all belong to rectangle.

As Rob Pike said.

"A method is a function with an implicit first argument, called a receiver."

Syntax of method.

func (r ReceiverType) funcName(parameters) (results)

Let's change out example by using method.

package main
import (
	"fmt"
	"math"
)

type Rectangle struct {
	width, height float64
}

type Circle struct {
	radius float64
}

func (r Rectangle) area() float64 {
	return r.width*r.height
}

func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}

func main() {
	r1 := Rectangle{12, 2}
	r2 := Rectangle{9, 4}
	c1 := Circle{10}
	c2 := Circle{25}

	fmt.Println("Area of r1 is: ", r1.area())
	fmt.Println("Area of r2 is: ", r2.area())
	fmt.Println("Area of c1 is: ", c1.area())
	fmt.Println("Area of c2 is: ", c2.area())
}

Notes for using methods.

  • If the name of methods is same, but they don't have same receivers, they are not same.
  • methods are able to access fields in receivers.
  • Use . to call a method in the struct, just like to call fields.

Figure 2.9 Methods are difference in difference struct

In above example, method area() is respectively belonging to Rectangle and Circle, so the receivers are Rectangle and Circle.

One thing is worthy of note that the method with a dotted line means the receiver is passed by value, not by reference. The different between them is that the method could change receiver's value when the receiver is passed by reference, and it gets the copy of receiver when the receiver is passed by value.

Does the receiver can only be struct? Of course not, any type could be the receiver of a method. You may be confused about customized type, struct is a special type of customized type, there are more customized types.

Use following format to define a customized type.

type typeName typeLiteral

Examples about customized type.

type ages int

type money float32

type months map[string]int

m := months {
	"January":31,
	"February":28,
	...
	"December":31,
}

I hope you know how to use customized type now. it's like typedef in C, we use ages to substitute int in above example.

Let's get back to method.

You can use as many methods in customized types as you want.

package main
import "fmt"

const(
	WHITE = iota
	BLACK
	BLUE
	RED
	YELLOW
)

type Color byte

type Box struct {
	width, height, depth float64
	color Color
}

type BoxList []Box //a slice of boxes

func (b Box) Volume() float64 {
	return b.width * b.height * b.depth
}

func (b *Box) SetColor(c Color) {
	b.color = c
}

func (bl BoxList) BiggestsColor() Color {
	v := 0.00
	k := Color(WHITE)
	for _, b := range bl {
    	if b.Volume() > v {
        	v = b.Volume()
        	k = b.color
    	}
	}
	return k
}

func (bl BoxList) PaintItBlack() {
	for i, _ := range bl {
    	bl[i].SetColor(BLACK)
	}
}

func (c Color) String() string {
	strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
	return strings[c]
}

func main() {
	boxes := BoxList {
    	Box{4, 4, 4, RED},
    	Box{10, 10, 1, YELLOW},
    	Box{1, 1, 20, BLACK},
    	Box{10, 10, 1, BLUE},
    	Box{10, 30, 1, WHITE},
    	Box{20, 20, 20, YELLOW},
	}

	fmt.Printf("We have %d boxes in our set\n", len(boxes))
	fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
	fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
	fmt.Println("The biggest one is", boxes.BiggestsColor().String())

	fmt.Println("Let's paint them all black")
	boxes.PaintItBlack()
	fmt.Println("The color of the second one is", boxes[1].color.String())

	fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
}

We define some constants and customized types.

  • Use Color as alias of byte.
  • Define a struct Box which has fields height, width, length and color.
  • Define a struct BoxList which has Box as its field.

Then we defined some methods for our customized types.

  • Volume() use Box as its receiver, returns volume of Box.
  • SetColor(c Color) changes Box's color.
  • BiggestsColor() returns the color which has the biggest volume.
  • PaintItBlack() sets color for all Box in BoxList to black.
  • String() use Color as its receiver, returns the string format of color name.

Is it much clear when we use words to describe our requirements? We often write our requirements before we start coding.

###Use pointer as receiver Let's take a look at method SetColor, its receiver is a pointer of Box. Yes, you can use *Box as receiver. Why we use pointer here? Because we want to change Box's color in this method, if we don't use pointer, it only changes value of copy of Box.

If we see receiver as the first argument of the method, it's not hard to understand how it works.

You may ask that we should use *b.Color=c instead of b.Color=c in method SetColor(). Either one is OK here because Go knows it. Do you think Go is more fascinating now?

You may also ask we should use (&bl[i]).SetColor(BLACK) in PaintItBlack because we pass a pointer to SetColor. One more time, either one is OK because Go knows it!

###Inheritance of method We learned inheritance of field in last section, and we also have inheritance of method in Go. So that if a anonymous field has methods, then the struct that contains the field have all methods from it as well.

package main
import "fmt"

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
	Human // anonymous field
	school string
}

type Employee struct {
	Human 
	company string
}

// define a method in Human
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

	mark.SayHi()
	sam.SayHi()
}

###Method overload If we want Employee to have its own method SayHi, we can define the method that has same name in Employee, and it will hide SayHi in Human when we call it.

package main
import "fmt"

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
	Human 
	school string
}

type Employee struct {
	Human 
	company string
}

func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func (e *Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
    	e.company, e.phone) //Yes you can split into 2 lines here.
}

func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

	mark.SayHi()
	sam.SayHi()
}

You are able to write an Object-oriented program now, and methods use rule of capital letter to decide whether public or private as well.

##Links