У меня есть интерфейс, который имеет определенное количество четко определенных функций. Скажем так:
interface BakeryInterface {
public function createCookies();
public function createIceCream();
}
Это хорошо работает для большинства реализаций интерфейса, но в некоторых случаях мне нужно добавить некоторые новые функциональные возможности (например, возможно, в новый метод createBrownies()
). Очевидный / наивный подход к этому состоит в расширении интерфейса:
interface BrownieBakeryInterface extends BakeryInterface {
public function createBrownies();
}
Но у него есть довольно большой недостаток в том, что я не могу добавить новую функциональность без изменения существующего API (например, изменение класса для использования нового интерфейса).
Я думал об использовании адаптера для добавления функциональности после создания экземпляра:
class BrownieAdapter {
private brownieBakery;
public function construct(BakeryInterface bakery) {
this->brownieBakery = bakery;
}
public function createBrownies() {
/* ... */
}
}
Который будет чистым мне что-то вроде:
bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();
Это кажется хорошим решением проблемы, но мне интересно, пробуждаю ли я старых богов, делая это. Адаптер - это путь? Есть ли лучший образец для подражания? Или я должен просто кусать пулю и просто расширять оригинальный интерфейс?
Ответы:
Любой из шаблонов тела дескриптора может соответствовать описанию, в зависимости от ваших точных требований, языка и необходимого уровня абстракции.
Пуристический подход - это шаблон Decorator , который делает именно то, что вы ищете, динамически добавляя обязанности к объектам. Если вы на самом деле строите пекарни, это определенно излишне, и вам следует просто использовать Adapter.
источник
Исследуйте концепцию горизонтального повторного использования , где вы можете найти такие вещи, как Черты , все еще экспериментальное, но уже проверенное на практике Аспектно-ориентированное программирование и иногда ненавистные миксины .
Прямой способ добавления методов в класс также зависит от языка программирования. Ruby допускает возможность внесения исправлений обезьянами, в то время как наследование Javascript на основе прототипов , где классы на самом деле не существуют, вы создаете объект и просто копируете его и продолжаете добавлять к нему, например:
Наконец, вы также можете эмулировать горизонтальное повторное использование или «модификацию» и «добавление» поведения класса / объекта во время выполнения с помощью отражения .
источник
Если есть требование, что
bakery
экземпляр должен динамически изменять свое поведение (в зависимости от действий пользователя и т. Д.), Тогда вам следует перейти к шаблону Decorator .Если
bakery
его поведение не изменяется динамически, но вы не можете изменитьBakery class
(внешний API и т. Д.), Тогда вам следует перейти к шаблону адаптера .Если
bakery
его поведение не изменяется динамически, и вы можете изменить его,Bakery class
вам следует расширить существующий интерфейс (как вы изначально предложили) или ввести новый интерфейсBrownieInterface
и позволитьBakery
реализовать два интерфейсаBakeryInterface
иBrownieInterface
.В противном случае вы добавите ненужную сложность в свой код (используя шаблон Decorator) без веской причины!
источник
Вы столкнулись с проблемой выражения. В этой статье Стюарта Сьерры обсуждается решение clojures, но он также приводит пример на Java и перечисляет популярные решения в классической ОО.
Также есть презентация Криса Хаузера, в которой обсуждается практически то же самое.
источник