Мы используем парадигму ООП уже несколько десятилетий, и так называемые три столпа ООП - наследование, полиморфизм и инкапсуляция, кажутся важными и полезными, но у них есть внутренняя проблема. Проблема настолько серьезна, что, если с ней не справиться, она будет прилипать к вашей кодовой базе, как рак. Это сделает ваш код более устойчивым к изменениям, и антишаблоны будут вкрадываться внутрь. В этой статье мы познакомимся с миром языка программирования Go и тем, как он очень элегантно справляется с этими проблемами. Я использую Go уже несколько месяцев и считаю себя новичком, так что не стесняйтесь бить. Без лишних слов, давайте займемся каждым из сломанных столпов ООП.

Наследование

Идея наследования настолько естественна. Мы никогда не отделяем повторное использование кода от наследования, они идут рука об руку, и все же мы думаем, что может быть не так с наследованием.

Проблема хрупкого базового класса

OO's way

Может случиться так, что даже если вы не сделаете никаких изменений в своем классе, но ваш код сломается. Угадай, что? На самом деле изменение было сделано в классе, от которого вы унаследовали !!! Да, такое могло случиться, и это проблема хрупкого базового класса. Очень вероятен сценарий, что после обновления зависимости библиотеки это может вызвать некоторые побочные эффекты, поскольку класс библиотеки, от которого вы унаследовали, был изменен. Давайте посмотрим на это в действии.

Приведенный выше пример прост, inc2()method переопределен, он вызовет inc1()of базовый класс и, таким образом, увеличит i на 1. Давайте сделаем простое изменение в нашем базовом классе.

inc1() метод базового класса теперь фактически вызовет переопределенный метод inc2(), и ваша программа застрянет в бесконечном цикле !! Наследование не является полным доказательством, оно имеет изъян.

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

Путь GO

Прежде всего, мы не можем назвать Go языком ООП, у него есть структуры (например, C), и есть функции, которые вы определяете для этих структур (в отличие от C). Если вы объедините два, то примерно вы можете назвать это классом. Вот шокер - в Go нет наследования, но вы можете очень легко составлять объекты (структуры) с помощью встраивания структур. Возьмем пример.

Благодаря продвижению метода GetName()method типа Human можно вызывать непосредственно для типа Student. В Java нам пришлось бы создать оболочку для вызова методов Human (дополнительная работа).

Когда мы встраиваем тип, методы этого типа становятся методами внешнего типа, но когда они вызываются, получателем метода является внутренний тип, а не внешний.
- Эффективная книга Go

Если бы мы реализовали пример хрупкого базового класса в Go, тогда inc1()method базового класса вызывал бы свой собственный inc2()method, и, таким образом, это вообще не вызвало бы проблемы.

Тестирование

OO способ

Лучшее тестирование может быть выполнено, если классы составлены, а не унаследованы. Если один класс состоит из другого класса, можно легко создать фиктивные объекты для тестирования целевого класса. Наследование не дает такой роскоши. Чтобы протестировать производный класс, вам понадобится его суперкласс. Если ваша кодовая база имеет многоуровневую структуру наследования, вам понадобится чертовски много классов, чтобы провести тестирование простого подкласса.

Проблема объектно-ориентированных языков в том, что у них есть вся эта неявная среда, которую они носят с собой. Вы хотели банан, но у вас была горилла, держащая банан и все джунгли.
- Джо Армстронг

Путь GO

Что ж, Go не поддерживает наследование, поэтому у вас нет другого пути, кроме как составлять (вставлять) объекты.

Алмазная проблема

OO способ

Приведенный выше пример имеет логический смысл и является вариантом использования множественного наследования. Сканер и принтер наследуются от PD (PoweredDevice), а копир наследуются от сканера и принтера. Существует метод start() в PD, который Сканер и Принтер переопределили. Теперь вопрос в том, какой метод унаследует копир? что из сканера или принтера? Большинство языков ООП не поддерживают множественное наследование, но те, которые поддерживают, например C ++, имеют сложный подход и требуют явного указания, какой родительский класс будет использовать метод.

Путь GO

Тип сканера и принтера может быть встроен в копир, а start()method может быть добавлен в копир, чтобы решить, какой метод будет вызван, так что снова композиция - это решение, и это не что-то уникальное для Go, просто оно упрощает композицию.

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

Концепция инкапсуляции также вдохновлена ​​реальным миром: вся внутренняя работа окружающих вас вещей, таких как телевизор, ноутбук или телефон, скрыта от внешнего мира. Вы взаимодействуете с ними только через интерфейс. Способность класса скрывать свои данные и методы - это инкапсуляция. Так в чем проблема? Что ж, инкапсуляция - это хорошо, но способ ее реализации на языках ООП - проблема.

OO способ

У нас есть два класса Human и Student, содержащие Human. В приведенном выше коде мы передаем объект Human по ссылке конструктору Student, а затем устанавливаем его в частную переменную. Мы видим, что объект на самом деле небезопасен. Его можно настроить снаружи. Хранить ссылку на этот объект вне класса опасно. Поскольку Java и многие другие языки не различают ссылку и фактические данные, класс никогда не узнает, можно ли изменить его частные члены извне. Грязная работа может заключаться в том, чтобы всегда копировать переданный объект в конструктор.

Есть еще одна проблема, которую мы уже видели в проблеме хрупкого базового класса и которая очень хорошо резюмирована в библии шаблонов проектирования:

Поскольку наследование предоставляет подклассу сведения о реализации его родительского элемента, часто говорят, что «наследование нарушает инкапсуляцию»
- Группа четырех, шаблоны проектирования

Путь GO

Указатели на помощь. В Go есть указатели (например, C), но нет арифметики с указателями, на случай, если вы испугаетесь. Объекты могут передаваться по указателю или по значению, поскольку оба относятся к разным типам, класс (структура) может определять себя для ожидания значения или указателя. Пройденный объект можно сделать безопасным путем передачи по значению. Кроме этого, в Go нет модификаторов доступа, таких как private или public, первая буква переменной или структуры определяет доступность. Используйте заглавную букву, и она будет общедоступной, используйте строчную букву, и она будет частной (красиво, не правда ли?). Это не является серьезным улучшением как таковое, и инкапсуляция имеет другое значение в Go, поскольку степень детализации конфиденциальности - это пакет, а не структура. Тем не менее, это улучшение.

Полиморфизм

OO способ

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

Путь GO

Похоже, настоящая причина в наследовании. Интерфейсы настолько хорошо определены в Go, что для достижения полиморфизма вам вообще не нужно наследование. Go не требует от нас явно указывать, что наши типы реализуют интерфейс. На самом деле лучше поработать над требованиями структуры (класса), а затем пометить ее. Таким образом, если все необходимые методы интерфейса определены в структуре, она неявно реализует этот интерфейс.

Это некоторые из многих преимуществ Go. Я едва поцарапал поверхность. Go на самом деле известен своей интуитивно понятной моделью параллелизма, стандартной библиотекой и системой пакетов. Некоторые из лучших последних проектов написаны на Go - Docker, Kubernetes, InfluxDb и Hugo (убийца Джекила!)

Go не занимается ни объектно-ориентированным программированием, ни функциональным программированием. Честно говоря, ему нечего рекомендовать, так как к нему нет никаких модных словечек. Это просто, по существу и ортогонально.

Целью Go является не исследование дизайна языков программирования; он призван улучшить рабочую среду для дизайнеров и их сотрудников. Go - это больше о программной инженерии, чем об исследованиях языков программирования. Или, перефразируя, речь идет о языковом дизайне на службе разработки программного обеспечения.
- Роб Пайк, создатель Go.

Надеюсь, вы хорошо прочитали. Если у вас есть вопросы, пишите в комментариях и не забывайте ❤️.

Использованная литература: