Uwe Zdun uwe.zdun@wu-wien.ac.at wrote on 08/29/2006 12:20:16 PM:
well, why do you think the behavior breaks encapsulation?
When I extend Base with BaseMixin, I'm altering the interface to Base. I can't safely alter the interface to Derived, because Base and BaseMixin don't necessarily know about Derived. In fact, since Derived is completely independent, it might not even exist when BaseMixin is defined. In gerneral BaseMixin can't be defined to work correctly with all possible derived classes, that set is open.
I believe when I add a mixin to Base I'm intercepting Base method invocations. When Derived invokes "next", it's logically chaining to Base. The Base may or may not have BaseMixin, but Derived shouldn't need to know. With BaseMixin intecepting Derived methods, BaseMixin needs to be made to work with Base & Derived. In practice, this seems like you can't safely use a mixin on any class thats also used as a superclass since the mixin will intercept any derived class's interface.
In practice, it's not possible to work with both. Assume BaseMixin is intercepting a leaf method with a leaf method: if BaseMixin invokes 'next' it will break Base and if it doesn't invoke 'next' it will break Derived.
The current behavior means I can't intercept a subset of the hierarchy, I have to intercept the whole thing. That means I can't encapsulate the use of a mixin; the mixin gets applied globally.
Rather a behavior where mixin are not added in precedence before the class hierarchy, requires you to know about the interceptor and might break encapsulation. Consider you are omitting to use "next" in a method of Derived, for instance simply because this method exists nowhere else in the class hierarchy. If the mixin would be introduced after Derived, you
would need to modify Derived to introduce a method of the same name on the mixin. If the mixin is always before the class hierarchy, as a base hierarchy developer you don't need to care for how a potential mixin is designed, because you can be sure once a call has reached your class, you can be sure, you cannot accidently introduce side-effects on mixins (maybe designed later by other developers).
To be clear, I agree that a mixin should take precedence over the class it's mixed into. I don't think it should take precedence over classes derived from the class it's mixed into. If I wanted to intercept method invocations on Derived, I would add a mixin to Derived but my explicit semantic is that I want to intercept methods on Base.
To ground this discussion a bit more in reality, my base class accesses hardware (Device). I then have a AppDevice class derived from the Device class that I use in my application. AppDevice will get a method, perform some processing and then invoke Device (via 'next').
So from the app point of view I have: AppDevice --> Device
For testing purposes I want to use a simulator. I created a DeviceSim class that reads and writes a buffer instead of the HW. I don't want my derived classes to know or care if it's using the simulator or the actual device. In either case the classes define leaf methods; the methods don't (and can't) chain.
In the test environment I want DeviceSim to intercept Device methods ("Device mixin add DeviceSim"): AppDevice --> DeviceSim --> Device
But what I get is: DeviceSim --> AppDevice --> Device
Since both Device and DeviceSim have leaf methods, adding DeviceSim as mixin to Device breaks AppDevice. It's the documented behavior, but it doesn't feel like the right behavior. The behavior I wanted (and what my code expressed) was to intercept Device methods not AppDevice methods. I don't see any way to limit interception to the subset of the hierarchy that I want.
Is what I'm trying to do unreasonable? It feels like I'm using the language features as intended, I just don't get the desired result.