Ralph, On Tue, Aug 14, 2012 at 3:30 PM, Ralph Schindler <ra...@ralphschindler.com>wrote:
> > Well, I like it at first glance. There are two main problems that I see >> with it: >> >> 1. It still requires a separate decorator class for every combination of >> interfaces you want to decorate. So, for example if you wanted to >> decorate a Foo interface, but sometimes it's used with Iterator as well. >> Then you'd need two classes. If there was a third interface, you'd need >> four separate classes. Same problem with class bloat minus a lot of the >> boilerplate. >> > > You'd need a class anyway for your custom logic to live, that custom logic > is the reason you want a Proxy in the first place. In your example, you > want to proxy and object, but for one of the method, presumable an > expensive method, you want to be able to cache the output before needing to > go to the actual object. > > If Iterator was required for the Foo contract/interface, that would have > bee in the Foo interface definition as well. > No, it would be required by the class itself. That's the point. I don't want to decorate an interface, but an instance. Otherwise it makes decorating real world classes much more difficult (which was the pain I'm looking to clean up). > Even in your example, you still have this: > > class Bar extends SplDecorator {} > > But what you are really intending is: > > class Bar extends MagicToMakeThisActLikeAnyType {} > > Ultimately, the instance you are decorating and decorator itself need to > be "type equivalent". Meaning the generally accepted understanding of > is_a() and instanceof constructs need to also apply to the decorator object > just as it would any other object. It sounds to me like you haven't tried to use decorators for any complex logic. Making it type equivalent leads to very vebose code. And a PITA to write. This is one of the reasons that traits are so heralded. Because problems that are easy to solve with decorators (in general) are difficult to solve with PHP, so people wind up writing copy/paste systems (which is what traits are) to solve them in a more brittle and less flexible manner. I'm after flexibility in addition to enforcement. They are not opposites. The whole point of interfaces is not typing, but a contract. There's nothing stopping you from making an Iterator which uses the valid() class to do logic. The accepted contract though says what it should do. But interfaces are only half of that contract. We assume that if you implement an interface that you're going to abide by the whole contract. But without interface level preconditions, postconditions and invarients, it's not an "All or nothing" approach. By using interfaces I can gain some compile-time assurance that the contract is met, but only manual review will confirm this. What I'm proposing here can be abused. Sure, it can be used to do all sorts of nasty things. But so can traits. So can eval. So can inheritance. So can regular expressions. That doesn't mean that they aren't incredibly useful... > 2. It's still not possible to decorate a typed class. So I can't >> decorate PDO without first extending PDO with a user-supplied interface. >> > > I don't follow. What do you mean by a "typed class"? A class which is checked for directly. There is plenty of user-land code that uses PDO as a type hint. That means I'd need to extend PDO to add functionality, thereby rendering decorators useless (and instead causing all sorts of issue). And even if I wanted to make a new class for my using to decorate, I'd need to create an interface and class: interface PDOLike { } class MyPDO extends PDO implements PDOLike { } That winds up creating really weird coupling and hard to reuse code. So that means that I either need to depend on my PDO instance, or not be able to use decorators at all. Not a good tradeoff if you ask me. > And I don't understand how this breaks LSP... We're not extending the >> decorated class. In fact, we can add construct time checking to ensure >> the original interfaces are all enforced (and even go a step further and >> > > That is where we are going to differ. > > Simply because your object responds to all the same methods of, for > example, the FooInterface, does not make it a FooInterface subtype. It > just means that in the "duck typing" sense of the phrase, it can act like a > FooInterface for people that are not necessarily concerned that it's > actually not is_a() FooInterface. > But the flip side is true as well. Just because a class implements a FooInterface doesn't mean it can act like a FooInterface is supposed to. That's one reason that relying on interfaces alone for that much is a mistake. It takes review. Or (in other languages) formal contracts to ensure that the contract is enforced. And since interfaces in PHP are weak signature based contracts, we can implement them dynamically without absolutely any break in the contract. That's why this style system (letting a decorator assume the interfaces of the parents, as long as the decorator doesn't violate the interface) can work in PHP. If we had actual contracts, it wouldn't work. In other words, yes, it will pass all sorts of method_exists() checks, but > it'd never be able to pass an instanceof, is_a, or is_subclass_of() check > on FooInterface. Which is what's possible today. I'm talking about changing that to let it pass those checks, because it's decorating, not becoming a type of itself. > ensure that all method signatures are the same that are overridden). LSP >> is not a compile time concern, it's a purely runtime concern (in PHP at >> > > When I say "compile time" I mean PHP's compile time, in so far as PHP is a > compile and execute style interpreted language. > But the contract is not enforced at compile time. Only half of it is (the issuer, or the fact that the class says it signed the contract). The other half is done at runtime when a method asks for a specific contract (the signer). > The following script demonstrates the enforcement of subtype expectations: > > <?php > interface Foo { public function bar(); } > class Bar implements Foo { public function bar(array $a) {} } > ?> > > That will produce: PHP Fatal error: Declaration of Bar::bar() must be > compatible with that of Foo::bar() in ... > > That is an LSP check, and it is done at PHP's compile time. The rule > "Preconditions cannot be strengthened in a subtype" is violated since you > attempted to add an argument to the bar() method of the Foo contract. Let's get one thing clear here. It's not a LSP check. In that exact example it is. But in general, it also prevents the LSP valid: interface foo { public function bar(array $a); } class Bar implements Foo { public function bar($a); } That's 100% in compliance with LSP. Yet it's disallowed. The reason is that interfaces are not primarily a LSP enforcer. They are primarily a contract enforcer. LSP is a usual side-effect, but it's not a guarantee. > least). The fact that interface contracts are enforced at compile time >> is nice, but it's not a strict necessity to enforcing LSP. >> > > Why not do it at compile time? When php is building up its class entry > table, it should be in its best interest to not have incomplete or invalid > types in there, right? Because you can't know at compile time without doing a bunch of weird and wonky things. Which completely blow away the benefits of decorating. For example, let's say you want to decorate an iterator to also expose the ArrayAccess interface. That way you can access an iterators elements by key directly. That's fine and doable today: class ArrayAccessDecorator implements Iterator, ArrayAccess { /* Proxy Methods Here */ public function offsetGet($key) { if (!isset($this->data[$key]) { while ($this->valid()) { if ($key == $this->key()) break; $this->next(); } if (!isset($this->data[$key])) return false; } return $this->data[$key]; } } Where it internally caches the iterated items. It doesn't work anywhere, but it decorates real world functionality onto an iterator. Now, let's say that we also have a reason to implement an observer decorator, to let other classes observe the iteration as it happens. class ObservableDecorator implements Iterator, SplSubject { /* SPL Methods Here*/ /* Proxy Methods Here */ public function next() { $this->parent->next(); $this->notify(); } } Now, today, we can't decorate an iterator with both. We'd have to choose one or the other, because the second decorator would nuke the type information of the first. So we're left with a dirty ass hack: class ArrayAccessObservableDecorator extends ObservableDecorator implements ArrayAcces { public function __construct(ArrayAccess $parent) {} } Which is exactly the type of thing this proposal is targeted to prevent. > But, I do support getting more duck typing facilities in PHP, and reusing > traits in places where we need more compiler assisted copy/paste when > boilerplate is cumbersome. > But that's exactly the point. Traits are not (and never were) designed to solve this problem. I'm talking about runtime composability of functionality. Not compile time. I want to be able to construct my objects from other interactions (like database settings), not just be limited to having to copy/paste for everything. And I don't need to resort to duck typing for it to work. All I need is the ability to decorate at will. I'm not talking about nuking the meaning of the interfaces (as they'd be proxied to). But more just gaining the flexibility to decorate without having to muck up everything a dozen times for every possible interface combination of my decorated class. Anthony