Давайте немного узнаем об объектно-ориентированном программировании! Внедрение зависимостей, полиморфизм и переадресация.

Я использую GitHub gists для этих примеров, чтобы попробовать.

Допустим, у нас есть некоторый код Ruby для класса Journey, который позволяет нам передавать пункт назначения, начинать и заканчивать путешествие, возвращая соответствующую строку с интерполированным пунктом назначения:

Это все очень хорошо, но на данный момент жестко запрограммировано, что мы пройдем все пути. Что, если мы захотим поехать на другом виде транспорта?

Это можно сделать, передав режим транспорта в инициализаторе, сохранив его как переменную экземпляра и используя логику if-else для возврата различных строк в начале и конце:

Я думаю, вы можете видеть, как это выйдет из-под контроля, когда будет добавлено больше видов транспорта. Вместо этого мы можем использовать внедрение зависимостей и полиморфные классы:

Здесь вместо громоздкого блока if-else различные варианты поведения содержатся в классах Walk и Car.

Класс, используемый в качестве вида транспорта, внедряется в экземпляр Journey при его инициализации. Это называется внедрением зависимостей! (по умолчанию используется класс Walk, если не указан другой метод).

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

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

Еще немного о внедрении зависимостей: это позволяет очень легко тестировать классы изолированно. При тестировании Journey мы могли внедрить дубликат метода транспорта (фальшивый метод транспорта, созданный для тестов), поэтому, даже если классы Walk и Car сломаны или недоступны, тесты Journey все равно будут работать. Вот как это будет выглядеть в RSpec:

Сначала подготавливаются несколько дублей, класс double, которому разрешено реагировать на new и возвращать экземпляр double. Затем подготавливается изолированный класс Journey, в который передается дубликат класса.

Первый тест ожидает, что метод start в изолированном путешествии приведет к вызову метода start для экземпляра double, что он и делает, а второй тест делает то же самое для Finish.

Эти тесты останутся верными, даже если функциональные возможности классов Walk и Car будут полностью удалены, поскольку они тестируются с двойником вместо реального класса. Аккуратно да?

Взгляните на код в этом репозитории GitHub, повеселитесь и поиграйте с ним, если хотите!

Спасибо за чтение!