Найти в Дзене

#2 ООП в Go

Оглавление

Вопрос на собеседовании обычно звучит так: расскажи про ООП в Go

Рассказываю:

Go реализует парадигмы ООП немного иначе, чем классические ООП языки. Разберём каждый принцип на простых примерах из жизни и на примерах кода на Go.

Инкапсуляция

(мой любимый пример) Представьте наручные часы. У часов есть стрелки, циферблат и, возможно, дата. Мы можем взять их, посмотреть время или установить будильник. Внутри часов скрыто множество сложных механизмов и деталей, которые обеспечивают правильную работу часов.

Эти механизмы и детали инкапсулированы внутри корпуса часов. Мы не знаем, как они работают, и нам это, в общем-то, и не нужно. Главное, что часы показывают правильное время. Все сложности и детали работы часов скрыты от нас, а мы взаимодействуем только с предоставляемым интерфейсом - стрелками, кнопками.

Как инкапсуляция релизуется в Go: Мы создаем структуры, которые скрывают внутренние детали и механизмы, предоставляя пользователю только необходимый и безопасный интерфейс для взаимодействия. Это позволяет изолировать сложную логику и упростить использование объекта.

В этом примере мы не раскрываем, как именно часы отслеживают время, например, как они учитывают переход через полночь и так далее. Мы просто предоставляем методы для получения и установки времени — GetTime и SetTime.

Go обеспечивает инкапсуляцию на уровне пакета. Единственный механизм управления видимостью — использование прописных и строчных букв.

Идентификаторы с прописной (заглавной) буквы экспортируются. В нашем примере структура Watch и ее методы GetTime и SetTime — экспортируется, то есть доступы за пределами пакета.

Идентификаторы со строчной (маленькой) буквы не экспортируются. В нашем примере поля структуры Watch: hours и minutes будут доступны только из того же пакета, за пределами к ним обратиться и вызвать их нельзя.

Наследование Композиция

Тут все просто. На реальном примере можно объяснить так, у нас есть рецепт торта. Мы можем создать новые рецепты на основе этого базового рецепта, добавляя новые ингредиенты или меняя способ приготовления.

Как наследование реализуется в Go: в классическом виде никак, то есть мы не можем наследовать один объект от другого. Вместо наследования используют композицию — разделение крупных структур на более мелкие для их последующего объединения. Базовая структура встраивается в дочернюю, после чего базовые поля и методы напрямую доступы дочерней структуре.

Композиция определяет класс как сумму его частей, в то время как при наследовании мы получаем один класс из другого. Классы и объекты, созданные с использованием наследования, тесно связаны, и изменение родителя может сломать код. Структуры, созданные с помощью композиции, слабо связаны, можно легко изменять составные части, не нарушая код.

Переходим к примеру на Go:

-2

Мы встроили в дочернюю структуру ChocolateCake базовую Cake и теперь у нее есть доступ к ингредиентам для торта от базовой структуру, а также к своим экстра-ингредиентам.

Полиморфизм

В жизни эту парадигму можно представить так, есть несколько разных музыкальных инструментов: гитара, флейта и барабан. Каждый из этих инструментов производит звук, но делает это по-разному. Тем не менее, если позвать группу музыкантов, и попросить их "сыграть" на своем инструменте, каждый бы знал, что делать, несмотря на различия в этих инструментах.

Как полиморфизм реализуется в Go: через интерфейсы. Мы можем определить интерфейс с набором методов и затем реализовать этот интерфейс разными структурами.

Предположим, у нас есть интерфейс Instrument с методом Play():

-3

Создадим несколько структур, каждая из которых реализует этот интерфейс:

-4

Теперь, благодаря полиморфизму, мы можем создать функцию, которая принимает интерфейс Instrument и вызывает метод Play, не заботясь о том, какой конкретный тип инструмента передан. Мы можем передать любой объект, реализующий интерфейс Instrument, в функцию PlayInstrument:

-5

ссылка на Playground

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

Нам этом все. Возможно, слишком подробно, но с реальными примерами все эти концепции прочнее осядут в памяти.