26

I have an interface that has a certain amount of well-defined functionality. Let's say:

interface BakeryInterface {
  public function createCookies();
  public function createIceCream();
}

This works well for most implementations of the interface, but in a few instances, I need to add some new functionality (like perhaps rolled into a new method, createBrownies()). The obvious/naive approach to doing this would be to extend the interface:

interface BrownieBakeryInterface extends BakeryInterface {
  public function createBrownies();
}

But has a pretty big downside in that I can't add the new functionality without modifying the existing API (like changing the class to use the new interface).

I was thinking about using an adapter to add the functionality after instantiation:

class BrownieAdapter {
  private brownieBakery;

  public function construct(BakeryInterface bakery) {
    this->brownieBakery = bakery;
  }

  public function createBrownies() {
    /* ... */
  }
}

Which would net me something like:

bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();

This seems like a good solution to the problem, but I'm wondering if I'm awakening the old gods by doing it. Is the adapter the way to go? Is there a better pattern to follow? Or should I really just be biting the bullet and just extending the original interface?

  • Delphi has helper classes, it is like adding methods to existing classes without really modifying them. For example Delphi has a TBitmap class defined in its graphics unit, you can create a helper class that adds, say, a Flip function to TBitmap. As long as the helper class is in scope you can call MyBitmap.Flip; – Bill Jun 07 '13 at 13:43

4 Answers4

14

Any of the Handle Body patterns could fit the description, depending on your exact requirements, language, and needed level of abstraction.

The purist approach would be the Decorator pattern, which does exactly what you are looking for, dynamically add responsibilities to objects. If you are actually building bakeries, it's definitely overkill and you should just go with Adapter.

yannis
  • 39,547
  • 40
  • 183
  • 216
  • This is exactly what I needed: I realized using an adapter would screw with dependency injection, but using a decorator gets around that. –  Feb 16 '12 at 11:37
5

Investigate the concept of horizontal reuse, where you can find stuff such as Traits, the still experimental but already production-proof Aspect Oriented Programming and the sometimes hated Mixins.

A direct way of adding methods to a class also depends on the programming language. Ruby allows for monkey-patching while Javascript's prototype-based inheritance, where classes don't really exist, you create an object and just copy it and keep adding to it, e.g.:

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

Finally, you can also emulate horizontal reuse, or runtime "modification" and "addition" of class/object behavior with reflection.

dukeofgaming
  • 13,943
  • 6
  • 50
  • 77
4

If there is a requirement that bakery instance must change its behaviour dynamically (depending on the user actions, etc.) then you should go for the Decorator pattern.

If bakery does not change its behaviour dynamically but you cannot modify the Bakery class (external API, etc.) then you should go for the Adapter pattern.

If bakery does not change its behaviour dynamically and you can modify Bakery class then you should extend existing interface (as you initially proposed) or introduce a new interface BrownieInterface and let Bakery implement two interfaces BakeryInterface and BrownieInterface.
Otherwise you gonna add unnecessary complexity to your code (using the Decorator pattern) for no good reason!

Dime
  • 295
  • 1
  • 6
2

You have encountered the expression problem. This article by Stuart Sierra discusses clojures solution, but he also gives an example in Java and lists the popular solutions in classic OO.

There's also this presentation by Chris Houser that discusses pretty much the same ground.

Pavel Penev
  • 582
  • 4
  • 4