Re: Decorator with public methods

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Dec 26, 2008 at 6:53 PM, Larry Garfield <larry@xxxxxxxxxxxxxxxx>wrote:

> Excuse me a moment while I delve into complex OO. :-)
>
> I have an object to which I want to add behavior (methods).  I cannot use
> inheritance here because the object is already of a type or subtype (vis, I
> am
> already using inheritance for something else), and because I want to be
> able
> to add multiple types of behavior at runtime.  The normal OO response to
> this
> situation is the Decorator pattern.
>
> --------------------
>
> interface F {
>  function doStuff();
> }
>
> class Foo {
>  function doStuff() { ... }
> }
>
> class Decorator implements F {
>  protected $foo;
>
>  function __construct(Foo $foo) {
>    $this->foo = $foo;
>  }
>
>  function doStuff() {
>    $this->foo->doStuff();
>  }
> }
>
> class Bar extends Decorator {
>  function doThings() { ... }
> }
>
> $f = new Foo();
>
> $b = new Bar($f);
> $b->doStuff();
> $b->doThings();
>
> --------------------
>
> OK, great, that's wonderful.  You can also nest such decorators
> indefinitely,
> provided that they only override methods from Foo and change its behavior,
> then pass on up the chain.  Neat.  What you cannot do, however, is nest
> decorators that have public methods.  That is:
>
> --------------------
>
> class Baz extends Decorator {
>  function doOtherThings();
> }
>
> $f = new Baz(new Bar(new Foo));
> $f->doOtherThings(); // Works.
> $f->doStuff(); // Works.
> $f->doThings(); // Fail.
>
> --------------------
>
> Now, PHP does have a loophole around this problem in the form of __call().
> Specifically, instead of Decorator wrapping each method of F/Foo directly
> it
> implements __call():
>
> --------------------
> class Decorator {
>  protected $foo;
>
>  function __construct(Foo $foo) {
>    $this->foo = $foo;
>  }
>
>  function __call($method, $args) {
>    return call_user_func_array(array($this->foo, $method), $args);
>  }
> }
> --------------------
>
> That should work and allow the code snippet above to run, but it has two
> significant problems:
>
> 1) Because Decorator does not directly implement F, you cannot use type
> hinting.
>
> 2) __call() and call_user_func_array() are both fairly slow operations, and
> stacking them then becomes a nightmare for performance.
>
> #1 can largely be solved by both directly implementing F *and* implementing
> __call(), but we're still left with the performance problems of #2.  While
> for
> some uses cases that is OK, it can add up to unpleasant microseconds lost.
>
> Can anyone suggest an alternate solution that has less of a performance
> hit?


the main drawback to decoration vs inheritance is the fact that code is not
essentially 'written for you', which results in lots of boilerplate code in
decorators, in my experience.  what that amounts to, is if you have 10
methods, whereby you only want to alter one of them in a decorator, then w/
decoration youve got 9 additional methods of crappy boilerplate code to
write, that you wouldnt have to when extending instead.

what is really irritating about the php __call() implementation is that
interfaces are not implemented by it.  i think this is pretty silly (there
may be a solid reason the internals people could mention).  what i mean is,
intuitively, i would expect

class A implements B {
  public function __call() {}
}

to work, no matter what B declares.  but it doesnt, WEAK, *cough* *cough*..

*sigh* sadly, i think the best solution is just to add bolierplate code in
your decorators, such that deocration methods explicitly implement the
interrface w/ each successive layer, and not incur the performance overhead
of __call().  essentially this is what the language would do for you when
extending something.  i rarely put methods all on one line elsewhere, but
when decorating ill just end up doing something like this

/* these are here just to implement interface F */
function doStuff() { return $this->doStuff(); }

another alternative is to really buy into the whole 'dynamic' thing, and
forget about the interface, roll w/ __call() and take the performance hit,
which is more akin to something like youd see in javascript.  personally i
like to use a medly of the two.

i never did much with it, but i suspect C++ templates make decoration nearly
as simple as inheritence, and avoid performace overhead of something like
__call().  which brings me to the third option, only really for the truely
brave php programmer.  templating php w/ php; its often done in code
generation systems, like propel and other orm's, but basically, you'd have a
template w/ skeletons for all the methods and then youd have to run some
sort of generation script in order to create concreate classes for actual
use.  esoteric, and perhaps a little ugly (editors dont really work 'right'
when templating php w/ php =/), but the technique is very powerful.

to summarize, using your example above, i would most liely add doThings() to
Baz, or create another decoration interface for doThings() if you plan on
using the Bar implementation of doThings() in many places,

interface G {
  function doThings();
}

class Bar extends Decorator implements G {
  function doThings() {
    // concreate implementation
  }
}

class Baz implements F, G {
  // recycle Bar::doThings()
  public function doThings() {
    return $this->foo->doThings();
  }
  public function doOtherThings() {}
}

i appologize if this response is long winded, but im a big fan of the
decorator, and ive actually got some pretty slick code in production in the
photobucket code that uses the decorator pattern :D  it took about 2 months
to code, and i leraned a lot about some of the practical aspects of
decroration, specifically within the realm of php.  i know i repeated a few
things there, but i felt it neccessary to better explain myself.

-nathan

[Index of Archives]     [PHP Home]     [Apache Users]     [PHP on Windows]     [Kernel Newbies]     [PHP Install]     [PHP Classes]     [Pear]     [Postgresql]     [Postgresql PHP]     [PHP on Windows]     [PHP Database Programming]     [PHP SOAP]

  Powered by Linux