Как мне добавить функциональность к объекту, который уже существует?

25

У меня есть интерфейс, который имеет определенное количество четко определенных функций. Скажем так:

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();

Это кажется хорошим решением проблемы, но мне интересно, пробуждаю ли я старых богов, делая это. Адаптер - это путь? Есть ли лучший образец для подражания? Или я должен просто кусать пулю и просто расширять оригинальный интерфейс?


источник
В Delphi есть вспомогательные классы, это похоже на добавление методов к существующим классам без их реального изменения. Например, Delphi имеет класс TBitmap, определенный в его графическом блоке, вы можете создать вспомогательный класс, который добавляет, скажем, функцию Flip к TBitmap. Пока вспомогательный класс находится в области видимости, вы можете вызывать MyBitmap.Flip;
Билл

Ответы:

14

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

Пуристический подход - это шаблон Decorator , который делает именно то, что вы ищете, динамически добавляя обязанности к объектам. Если вы на самом деле строите пекарни, это определенно излишне, и вам следует просто использовать Adapter.

Яннис
источник
Это именно то, что мне было нужно: я понял, что использование адаптера может привести к внедрению зависимости, но использование декоратора обходит это.
5

Исследуйте концепцию горизонтального повторного использования , где вы можете найти такие вещи, как Черты , все еще экспериментальное, но уже проверенное на практике Аспектно-ориентированное программирование и иногда ненавистные миксины .

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

var MyClass = {
    do : function(){...}
};

var MyNewClass = new MyClass;
MyClass.undo = function(){...};


var my_new_object = new MyNewClass;
my_new_object.do();
my_new_object.undo();

Наконец, вы также можете эмулировать горизонтальное повторное использование или «модификацию» и «добавление» поведения класса / объекта во время выполнения с помощью отражения .

dukeofgaming
источник
4

Если есть требование, что bakeryэкземпляр должен динамически изменять свое поведение (в зависимости от действий пользователя и т. Д.), Тогда вам следует перейти к шаблону Decorator .

Если bakeryего поведение не изменяется динамически, но вы не можете изменить Bakery class(внешний API и т. Д.), Тогда вам следует перейти к шаблону адаптера .

Если bakeryего поведение не изменяется динамически, и вы можете изменить его, Bakery classвам следует расширить существующий интерфейс (как вы изначально предложили) или ввести новый интерфейс BrownieInterface и позволить Bakeryреализовать два интерфейса BakeryInterfaceи BrownieInterface.
В противном случае вы добавите ненужную сложность в свой код (используя шаблон Decorator) без веской причины!

дайм
источник
2

Вы столкнулись с проблемой выражения. В этой статье Стюарта Сьерры обсуждается решение clojures, но он также приводит пример на Java и перечисляет популярные решения в классической ОО.

Также есть презентация Криса Хаузера, в которой обсуждается практически то же самое.

Павел Пенев
источник