Pull to refresh

Объектно-дезориентированный язык

Reading time4 min
Views44K
Original author: Krzysiek

Каждый раз когда речь заходит о Go приходится слышать один и тот же вопрос:
Является ли Go объектно-ориентированным языком?

Честно говоря, меня это окончательно достало. Моя задача — расписать сию тему в данной статье, напечатать ссылку на визитках и раздавать их каждый раз когда фанаты ООП будут спрашивать у меня этот вопрос.

Структуры на первый взгляд


авторская картинка

Если кратко, то на этот священный вопрос не существует строгого ответа. Структуры Go на первый взгляд работают как объекты и вроде даже предоставляют модель наследования, например:

type Person struct {
        Name string
}

func (p *Person) Intro() string {
        return p.Name
}

type Woman struct {
        Person
}

func (w *Woman) Intro() string {
        return "Mrs. " + w.Person.Intro()
}

В примере выше показан простейший пример того, что большинство из вас назовут наследованием. Скажем пока что, что Woman наследуется от Person. Тип Woman содержит все поля типа Person, может переназначать его функции, может вызывать методы родительского типа. Как и было сказано выше, на первый взгляд это выглядит как наследование. Ага… но это всего лишь трюк!

Трюк?


Давайте посмотрим повнимательнее, и позвольте мне объяснить как это работает внутри. Перво-наперво здесь нет никакого настоящего наследования. Было бы замечательно, если бы вы забыли всё что вы знаете о наследовании во время чтения всего этого… Чик!

авторская картинка

Теперь представьте структуру в виде коробки. Обычная серая картонная коробка… И представьте поля в качестве вещей, каких-то магических предметов которые вы можете положить внутрь коробки. Вот пример:

type SmallBox struct {
        BaseballCards   []string
        BubbleGumsCount int
        AnyMagicItem    bool
}

Ага, именно так наша маленькая коробка может выглядеть. Но однажды её может стать недостаточно, так? Или возможно вам захочется разделить ваши вещи. В этом случае вы можете взять большую коробку и положить внутрь неё маленькую вместе с другими предметами. Например:

type BigBox struct {
        SmallBox
        Books []string
        Toys  []string
}

Великолепно, у нас есть большая коробка содержащая все предметы имеющиеся в маленькой плюс немного книг и игрушек. И вот тут происходит магия… Мы можем спросить:
Что находится в большой коробке?

Мы можем ответить по разному. С одной стороны мы можем коротко сказать что в ней книги, игрушки и какая-то маленькая коробка, но с другой мы можем быть более подробны сказав что в коробке находятся игрушки, книги, бейсбольные карточки и какие-то магические предметы. Оба ответа верны, но различны в своей подробности. Go кроме всего прочего позволяет определять уровень этой подробности, например:

bigBox := &BigBox{}
bigBox.BubbleGumsCount = 4          // correct...
bigBox.SmallBox.AnyMagicItem = true // also correct

Если вкратце, то BigBox не родня SmallBox, он лишь содержит внутри один экземпляр SmallBox и предоставляет быстрый доступ к его полям.

Переопределение?


В самом первом примере вы увидели нечто вроде переопределения метода. Повторю снова, вам необходимо забыть этот термин. Этот магический приём не более чем вызов функции принадлежащей коробке находящейся внутри происходящий снаружи большой коробки, как например тут:

func (sb *SmallBox) Capacity() int {
        return 20
}

func (bb *BigBox) Capacity() int {
        return bb.SmallBox.Capacity() * 3
}

Мы задаём что BigBox может содержать в три раза больше предметов по сравнению с маленькой коробкой, но мы не переопределяем функцию принадлежащую SmallBox. Мы всё ещё можем получить доступ к ним обоим, т.к. они принадлежат разным коробкам.

fmt.Println(bigBox.Capacity())          // => 60
fmt.Println(bigBox.SmallBox.Capacity()) // => 20

Однако, функции могут недвусмысленно вызываться из внешней коробки с использованием сокращений:

func (sb *SmallBox) Color() string {
        return "gray"
}

// *snip*

bigBox.SmallBox.Color() // => "gray"
bigBox.Color()          // => "gray"

Это киллер-фича которая привносит в Go глоток свежего воздуха в вопросе наследования. Функция Color в обоих вызовах относится к одной и той же функции связанной с SmallBox.

Жадничаем память!


Go в общем является языком системного программирования и позволяет нам до некоторых пределов управлять памятью используя указатели. Мы можем использовать их для экономии памяти при работе со структурами. Можно предположить что BigBox может или не может содержать внутри себя SmallBox. До сих пор мы постоянно выделяли память под маленькую коробку, хотя она и не использовалась. Мы можем проделать то же самое чуть более эффективно посредством включения указателя в нашу структуру:

type SkinflintBigBox struct {
        *SmallBox
        Books []string
        Toys  []string
}

Но тут имеется один трюк, данная включённая структура работает таким же образом как и любой другой указатель, так что нам необходимо помнить что её необходимо инициализировать, иначе может случиться много плохого:

bigBox := &SkinflintBigBox{}
bigBox.SmallBox     // => nil
bigBox.AnyMagicItem // ...

авторская картинка

Нам необходимо инициализировать нашу маленькую коробку абсолютно так же как и любой другой указатель:

bigBox := &SkinflintBigBox{SmallBox: &SmallBox{AnyMagicItem: true}}
bigBox.AnyMagicItem // => true

Ура! Теперь всё отлично работает! Кроме того, вам возможно будет интересно знать, что включённый указатель может быть инициализирован в любое время, совершенно необязательно делать это при инициализации внешней структуры.

Это не магия, это трюк...


Суммируя, здесь нет магии. Так называемое наследование это не более чем особый тип поля которое предоставляет сокращения к собственным функциям. Просто, умно и достаточно что бы ответить фанатикам ООП:
Конечно, это ООП… Вперёд!

авторская картинка

На этом всё, надеюсь вам понравилось!

(прим.: согласно требованиям публики авторские картинки спрятаны за ссылками)
Tags:
Hubs:
+35
Comments43

Articles

Change theme settings