02.5 is added

This commit is contained in:
Slava Zgordan
2015-08-25 11:15:36 +02:00
parent 4bdcad6388
commit 1aba241321

310
ru/02.5.md Normal file
View File

@@ -0,0 +1,310 @@
# Объектно-ориентированное программирование
We talked about functions and structs in the last two sections, but did you ever consider using functions as fields of a struct? In this section, I will introduce you to another form of function that has a receiver, which is called `method`.
В предыдущих двух разделах мы говорили о функциях и структурах, но рассматривали ли Вы когда-нибудь функции как поля структуры? В этом разделе я познакомлю Вас с еще одним видом функций, который называется "метод".
## Метод
Предположим, Вы определили структуру "rectangle"(прямоугольник), и Вам нужно вычислить его площадь. Обычно для этого мы используем следующий код:
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("Площадь r1: ", area(r1))
fmt.Println("Площадь r2: ", area(r2))
}
Этот код может вычислить площадь прямоугольника. Мы используем для этого функцию `area`, но это не метод структуры "rectangle" (как методы классов в классических объектно-ориентированных языках). Как Вы можете заметить, функция и структура здесь - две независимые друг от друга сущности.
Пока что это не является проблемой. однако, если Вам нужно будет посчитать также площади круга, квадрата, пятиугольника или другой геометрической фигуры, Вам придется добавлять новые фукнции с похожими именами.
![](images/2.5.rect_func_without_receiver.png?raw=true)
Рисунок 2.8 Связь между функцией и структурой
Очевидно, что это не очень хорошо. Площадь должна быть свойством круга или прямоугольника.
For those reasons, we have the `method` concept. `method` is affiliated with type. It has the same syntax as functions do except for an additional parameter after the `func` keyword called the `receiver`, which is the main body of that method.
По этой причине в Go есть концепция `метода`. `Метод` привязывается к типу данных. У него такой же синтаксис, как и у функции, за исключением дополнительного параметра, идущего после ключевого слова `func` и называемого `ресивер`, который является основным телом метода.
В этом же примере `Rectangle.area()` мог бы принадлежать непосредственно `rectangle`, а не являться внешней функцией. Еще точнее, `length`, `width` и `area()` все принадлежат `rectangle`.
Как сказал Rob Pike:
"Метод - это функция, где первым указанным является аргумент, называемый ресивером."
Синтаксис метода.
func (r ReceiverType) funcName(parameters) (results)
Давайте изменим наш пример, используя `методы`:
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("Площадь r1: ", r1.area())
fmt.Println("Площадь r2: ", r2.area())
fmt.Println("Площадь c1: ", c1.area())
fmt.Println("Площадь c2: ", c2.area())
}
Примечания относительно испоьлзования методов:
- Если у методов одинаковые имена, но они относятся к разным ресиверам - это разные методы.
- Методы имеют доступ к полям внутри ресивера.
- Use `.` to call a method in the struct, the same way fields are called.
- Для того, чтобы вызвать метод структуры, используйте `.`, аналогично тому, как Вы работаете с полями.
![](images/2.5.shapes_func_with_receiver_cp.png?raw=true)
Рисунок 2.9 Методы отличаются друг от друга, если принадлежат разным структурам
В указанном выше примере методы area() есть у структуры Rectangle и у Circle соответственно, поэтому ресиверами являются Rectangle и Circle.
One thing that's worth noting is that the method with a dotted line means the receiver is passed by value, not by reference. The difference between them is that a method can change its receiver's values when the receiver is passed by reference, and it gets a copy of the receiver when the receiver is passed by value.
Can the receiver only be a struct? Of course not. Any type can be the receiver of a method. You may be confused about customized types. Struct is a special kind of customized type -there are more customized types.
Use the following format to define a customized type.
type typeName typeLiteral
Examples of customized types:
type ages int
type money float32
type months map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}
I hope that you know how to use customized types now. Similar to `typedef` in C, we use `ages` to substitute `int` in the above example.
Let's get back to talking about `method`.
You can use as many methods in custom 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() uses Box as its receiver and 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 clearer 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 `SetColor` method. Its receiver is a pointer of Box. Yes, you can use `*Box` as a receiver. Why do we use a pointer here? Because we want to change Box's color in this method. Thus, if we don't use a pointer, it will only change the value inside a copy of Box.
If we see that a receiver is the first argument of a method, it's not hard to understand how it works.
You might be asking why we aren't using `(*b).Color=c` instead of `b.Color=c` in the SetColor() method. Either one is OK here because Go knows how to interpret the assignment. Do you think Go is more fascinating now?
You may also be asking whether we should use `(&bl[i]).SetColor(BLACK)` in `PaintItBlack` because we pass a pointer to `SetColor`. Again, either one is OK because Go knows how to interpret it!
### Inheritance of method
We learned about inheritance of fields in the last section. Similarly, we also have method inheritance in Go. If an anonymous field has methods, then the struct that contains the field will have all the 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 a method that has the 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
- [Directory](preface.md)
- Previous section: [struct](02.4.md)
- Next section: [interface](02.6.md)