Ayende Rahien started a nice thread via his blog entry Aspects of Domain Design. He shows how a rule could be implemented as an aspect (in the AOP sense) that modifies a method. That was an invitation to discussions about whether this should really be done via aspects, and the invitation was well received.
In the comments, I remarked that this sounds much more like multi-dimensional separation of concerns to me. After all, AOP as a paradigm focuses on cross-cutting concerns (like "logging, transactions, security, and caching" - Ayende), but not quite as much on individual types and their semantics.
The notion of multi-dimensional SoC seems to be buried quite deep in the common wisdom of our profession, although there are quite nice implementations, and people can often see the advantages. Still, its status is that of alchemy, only less known. Some people use AOP frameworks or languages for multi-dimensional SoC, and technically, dedicated frameworks for multi-dimensional SoC have a lot in common with AOP frameworks. But personally, I would not like to do much of either using a toolkit made for the other (read more here).
Either way, it's quite hard to find guidance about how to use any technique for the goal of multi-dimensional SoC. (Ivar Jacobson's Aspect-Oriented Software Development with Use Cases being a rare exception.) So I thought I'd just start writing about some of our experiences at rubicon with our own mixin implementation.
I always believed that dynamic rule mechanisms should be made explicit where they are to be expected. We created our mixin mechanism to enable 3rd parties to create arbitrary extensions and modifications (notwithstanding the "virtual"-requirement) without modifying our source code or waiting for explicit extension points.
Of course, in a large enough development organization, you have to treat other teams as 3rd parties too, because it becomes just unmanageable to create extension points whenever anybody needs them. For the requesting party that would be pretty tough too, because they would have to live off the trunk and wait for every modification to be designed, implemented, tested and delivered before they can start working.
Using mixins proved to be a panacea for many of those problems. At the beginning, there were several reservations about overusing them, and in fact I'm still not sure how happy I am with the hundreds of mixins that emerged in some projects. But I did eventually give in to the notion that in some cases, explicit extension points would make the system just more fragmented and ad-hoc than using one well-understood paradigm for all of them. It's still something that needs to be decided on a case-by-case basis, and I'm not sure if Ayende's example (Account.Withdraw) is a very good one to make that point, at least not without any context.
There's another example I like for using mixins: parallel class hierarchies.
Here's the problem: I have an OO domain and a generic UI layer that uses data-binding on steroids. This UI can display icons for objects, but of course it cannot go ask the domain objects. We don't want UI code in our domain classes after all.
So we have an icon service that the UI can ask for icons. Pseudo code:
interface IIconService
{
string GetIcon (DomainObject obj);
}
Now most domain objects just return classname.gif, so this service is easy to implement. But wait, here's a special rule:
Male and female persons use different icons.
Now we could have an if-statement in GetIcon, but that's not very OOP. How do we use polymorphism without tainting the domain?
We need another class tree, somehow attached to the original one.
Here's how this is done using mixins:
namespace Domain
{
class DomainObject { } // layer super type
class Person : DomainObject { }
}
namespace UI
{
public interface IDomainObjectMixin
{
string GetIcon ();
}
[Extends(typeof(DomainObject))]
class DomainObjectMixin<TDomainObject> : Mixin<TDomainObject>, IDomainObjectMixin
where TDomainObject : DomainObject
{
virtual string GetIcon ()
{
return This.GetType().Name + "gif";
}
}
[Extends(typeof(Person))]
class PersonMixin<TPerson> : DomainObjectMixin<TPerson>
where TPerson: Person
{
override string GetIcon()
{
return (This.Gender == Gender.Male) ? "male.gif" : "female.gif";
}
}
}
Now you can cast any DomainObject to IDomainObjectMixin and ask for its icon. The PersonMixin class polymorphically provides its own implementation, while all other types derive the implementation of DomainObjectMixin.
class MyIconService : IIconService
{
public string GetIcon (DomainObject obj)
{
return ((IDomainObjectMixin)obj).GetIcon();
}
}
I think this leads to a nice separation of layers. Sure, you have to grasp mixins, but you need to understand this concept once, as opposed to understanding a variety of ad hoc solutions. (The use of generics and where-clauses in parallel class hierarchies is a bit ugly, but it follows a simple rule that you can just learn if you don't want to dig deep and understand why it's required.) Kind of reminds me of Greenspun's Tenth Rule, except that a more obvious conclusion from that rule would be to use another language, I guess, but a well-designed framework is still much better than "an ad hoc, informally-specified, bug-ridden, slow implementation" in each project.
There's another argument I hear a lot that I'd like to address. It goes like this: In OOP, composition is usually preferable to subclassing. That's not quite true. Subclassing is a very convenient way of gaining a certain set of features (like polymorphism and implementation inheritance), and it would be more than a bit tiresome to do OOP without it. But the more complex a problem gets, the less likely it is that we can solve it just by subclassing. For many problems, single inheritance just won't do, and multiple inheritance brings its own set of problems. But let me get this straight: yes, we're trying to make mixins as simple to use as subclassing, so they are easier to learn, and you can apply some of your valuable OOP experience. But the implementation of mixins is basically composition. (There is some sub class generation, but hey, somewhere the composition gluing has to be done.) So the bottom line is, mixins are just classes with a set of composition and delegation rules attached to them. That's a weird way of thinking about mixins, but the most correct one if you're reasoning about them in the context of the inheritance vs. composition debate.
- Stefan
Edit: missing reference for Account.Withdraw
Edit: replaced Type Of (T) notation by Type<T> (Left over from an attempt to post this sample to a blog that thinks <T> is an HTML tag.)