diff --git a/ru/02.5.md b/ru/02.5.md deleted file mode 100644 index 2471fd53..00000000 --- a/ru/02.5.md +++ /dev/null @@ -1,307 +0,0 @@ -# Объектно-ориентированное программирование - -В предыдущих двух разделах мы говорили о функциях и структурах, но рассматривали ли Вы когда-нибудь функции как поля структуры? В этом разделе я познакомлю Вас с еще одним видом функций, который называется "метод". - -## Метод - -Предположим, Вы определили структуру "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 Связь между функцией и структурой - -Очевидно, что это не очень хорошо. Площадь должна быть свойством круга или прямоугольника. - -По этой причине в Go есть концепция `метода`. `Метод` привязывается к типу данных. У него такой же синтаксис, как и у функции, за исключением дополнительного параметра, идущего после ключевого слова `func` и называемого `ресивер`, который является основным телом метода. - -В этом же примере `Rectangle.area()` мог бы принадлежать непосредственно `rectangle`, а не являться внешней функцией. Еще точнее, `length`, `width` и `area()` все принадлежат `rectangle`. - -Как сказал Rob Pike: - - "Метод - это функция, где первым указанным является аргумент, называемый ресивером." - -Синтаксис метода: - - func (r ТипРесивера) funcName(параметры) (результаты) - -Давайте изменим наш пример, используя `методы`: - - 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()) - } - -Примечания относительно использования методов: - -- Если у методов одинаковые имена, но они относятся к разным ресиверам - это разные методы. -- Методы имеют доступ к полям внутри ресивера. -- Для того, чтобы вызвать метод структуры, используйте `.`, аналогично тому, как Вы работаете с полями. - -![](images/2.5.shapes_func_with_receiver_cp.png?raw=true) - -Рисунок 2.9 Методы отличаются друг от друга, если принадлежат разным структурам - -В указанном выше примере методы area() есть у структуры Rectangle и у Circle соответственно, поэтому ресиверами этих методов являются Rectangle и Circle. - -Стоит отметить, что метод с многоточием означает, что ресивер передается по значению, а не по ссылке. Различие в том, что когда ресивер передается по ссылке, метод может менять его значение, а когда ресивер передается по значению, метод работает с его копией. - -Можем ли ресивер быть только лишь структурой? Конечно, нет. Ресивером может быть любой тип данных. Если у Вас возникла неясность в связи с типами, создаваемыми пользователями - структура является одним из них, но их может быть и больше. - -Чтобы создать свой тип, используйте следующий формат: - - type typeName typeLiteral - -Примеры типов, созданных пользователем: - - type ages int - - type money float32 - - type months map[string]int - - m := months { - "Январь":31, - "Февраль":28, - ... - "Декабрь":31, - } - -Я надеюсь, теперь Вы поняли, как использовать такие типы. Так же, как `typedef` используется в C, в вышеприведенном примере можно использовать `ages` для замены `int`. - -Но давайте вернемся к `методам`. - -В созданных пользователем типах можно использовать столько методов, сколько захотите. - - 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 //срез, состоящий из элементов типа Box - - 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 {"белый", "черный", "синий", "красный", "желтый"} - 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("В наборе имеются %d коробок\n", len(boxes)) - fmt.Println("Объем первой из них равен ", boxes[0].Volume(), "cm³") - fmt.Println("Цвет последней - ",boxes[len(boxes)-1].color.String()) - fmt.Println("Самая большая из них имеет цвет: ", boxes.BiggestsColor().String()) - - fmt.Println("Давайте покрасим их все в черный цвет") - boxes.PaintItBlack() - fmt.Println("Цвет второй коробки - ", boxes[1].color.String()) - - fmt.Println("Очевидно, что цвет самой большой коробки теперь ", boxes.BiggestsColor().String()) - } - -Мы определили несколько констант и своих типов: - -- Мы использовали `Color` как синоним `byte`. -- Определили структуру `Box`, у которой есть поля height, width, length и color (высота, ширина, длина и цвет соответственно - прим.пер.). -- Определили тип `BoxList`, содержащий элементы типа `Box`. - -Затем мы определили методы для наших созданных типов: - -- Volume() использует Box как ресивер и возвращает объем Box. -- SetColor(c Color) изменяет цвет Box. -- BiggestsColor() возвращает цвет самой большой коробки. -- PaintItBlack() устанавливает цвет всех коробок (Box) в BoxList как черный. -- String() использует Color как ресивер и возвращает название цвета как строку. - -Когда мы используем слова для того, чтобы описать свои потребности, все становится яснее. Мы часто записываем свои потребности перед тем, как начать писать код. - -### Использование указателя в качестве ресивера - -Давайте посмотрим на метод `SetColor`. Его ресивером является указатель на Box. Да, можно использовать `*Box` в качесве ресивера. Почему мы использовали здесь указатель? Потому что в этом методе мы хотим изменить цвет коробки (Box). Если бы мы не использовали указатель, метод бы изменил цвет лишь у копии Box. - -Если мы видим, что ресивер - первый аргумент метода, несложно понять, как это работает. - -Вы можете спросить, почему мы не написали `(*b).Color=c` вместо `b.Color=c` в методе SetColor(). Но все в порядке, поскольку Go знает, как интерпретировать это выражение. Не правда ли, Go восхитителен? - -Вы также можете спросить, не должны ли мы использовать `(&bl[i]).SetColor(BLACK)` в `PaintItBlack`, ведь мы передаем в `SetColor` указатель. Опять же, и здесь все в порядке, поскольку Go знает, как интерпретировать и это! - -### Наследование методов - -В предыдущем разделе мы изучили наследование полей. Аналогично этому в Go мы можем наследовать методы. Если анонимное поле содержит методы, то структура, которая содержит это поле, также располагает всеми методами этого поля: - - 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 - } - - // определяем метод в Human - func (h *Human) SayHi() { - fmt.Printf("Привет, меня зовут %s, моете позвонить мне по телефону %s\n", h.name, h.phone) - } - - func main() { - mark := Student{Human{"Марк", 25, "222-222-YYYY"}, "MIT"} - sam := Employee{Human{"Сэм", 45, "111-888-XXXX"}, "Golang Inc"} - - mark.SayHi() - sam.SayHi() - } - -### Перегрузка методов - -Если мы хотим, чтобы у Employee был свой метод `SayHi`, мы можем определить метод с таким именем в Employee, и когда мы будем его вызывать, он скроет метод с тем же именем в Human. - - 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("Привет, меня зовут %s, можете позвонить мне по телефону %s\n", h.name, h.phone) - } - - func (e *Employee) SayHi() { - fmt.Printf("Привет, меня зовут %s, я работаю в %s. Звоните мне по телефону %s\n", e.name, - e.company, e.phone) //Да, здесь можно разбить строку на две. - } - - func main() { - mark := Student{Human{"Марк", 25, "222-222-YYYY"}, "MIT"} - sam := Employee{Human{"Сэм", 45, "111-888-XXXX"}, "Golang Inc"} - - mark.SayHi() - sam.SayHi() - } - -Сейчас Вы уже можете написать объектно-ориентированную программу. Методы, начинающиеся с заглавной буквы, являются публичными, со строчной - приватными. - -## Ссылки - -- [Содержание](preface.md) -- Предыдущий раздел: [Структуры](02.4.md) -- Следующий раздел: [Интерфейсы](02.6.md)