« Load-time weaving with AspectJ 1.2 | Main | The Ted Neward Challenge (AOP without the buzzwords) »
May 28, 2004
Person owns Dog...
There's a famous OO problem involving people and dogs that I first learnt about in Meilir Page-Jones' excellent book "Fundamentals of Object-Oriented Design in UML." It involves a class Person, with an attribute numOfDogsOwned and a simple accessor method getNumDogsOwned(). The argument goes that this may not neccessarily be good design, since Person and Dog are distinct concepts, and dog ownership is not an essential property of being a person.
Page-Jones calls this mixed-role cohesion, and he explains the problem better than I can: "What if you wanted to reuse Person in an application that had no dogs? You could do so, but you'd have extra, useless baggage from the class, and you might get some pesky warnings about missing dogs from your compiler (or linker). And where should we stop with this design philosophy? Why not include these attributes in Person: numOfCarsOwned, numOfBoatsOwned, numOfCatsOwned, numOfFrogsOwned,... ?"
It's an interesting problem, because there's no perfect solution in OO - Page-Jones discusses the pros and cons of four possible solutions including just adding the members directly to Person, using a PersonDogOwnership relationship class, using an abstract mix-in class, and using aggregation. Mixed-role cohesion sounds like the kind of problem that inter-type declarations in AspectJ should be able to help us address, so here's the world's first (AFAIK) aspect-oriented solution to the person-owns-dog problem:
/** * not a dog in sight... */ public class Person { private String lastName; private Address address; ... public String getLastName() { return lastName; } ... }
Some of you will have heard me use my adverb/adjective analogy for aspects before, and that's exactly what we've got here. We want to create a dog-owning person, which we could do by creating a DogOwningPerson class (a bit like creating a new noun), but dog-owning isn't limited to people, maybe an Organisation can own dogs too? What we've got is a concept (a compound adjective, dog-owning) that stands in its own right independent of any one class, and that could be applied to many different classes. I'm thinking interface, and I'm thinking aspect...
/** * not a person in sight... */ public aspect DogOwnership { public interface IDogOwner {}; private int IDogOwner.numDogsOwned; public int IDogOwner.getNumDogsOwned() { return numDogsOwned; } }
This aspect represents in a single module the concept of dog ownership. It defines an IDogOwner interface (it doesn't have to be an inner-interface, but making it so helps to keep the whole thing together), and uses inter-type declarations to define a numDogsOwned attribute and a getNumDogsOwned() method on behalf of all dog owners.
We still haven't quite got to person-owns-dog - I wanted to keep the concept of dog ownership independent of any one kind of dog owner. If we have an application where we need person-owns-dog, we can write the following:
public aspect PersonOwnsDog { declare parents : Person implements DogOwnership.IDogOwner; }
I like this solution because Person and DogOwnership are independently reusable, and represent cohesive abstractions in their own right. The PersonOwnsDog aspect that binds the two together is also very clear and simple.
With these aspects in place, you could call the getNumDogsOwned method on a Person as follows:
Person person = new Person(); person.getNumDogsOwned();
this will compile happily and execute without any problems. If ever you build the application without the PersonOwnsDog aspect though, you'll get a compile-time error about missing dogs. If you don't want that to happen, you could code the client this way (and I probably would in this particular circumstance):
... if (person instanceof DogOwnership.IDogOwner) { ((DogOwnership.IDogOwner)person).getNumDogsOwned(); } ...
but it's just a matter of personal taste, the compiler doesn't require it.
Posted by adrian at May 28, 2004 12:21 PM [permalink]
Comments
Nice solution. I may be picking nits, but I'm not sure that the last code snippet should be used... the code following person.getNumDogsOwned() will likely be dependent on that code executing. By using the instanceof check, the code would hide the source of a subtle run-time bug. By not using the check, the problem is detectable immediately with the compiler error. Thanks for the example!
Posted by: Dale Asberry at May 28, 2004 03:06 PM
think you've missed the point here. One, the solution here is to use an interface which you did. OO does in fact provide a solution for this "problem" as you yourself have shown. Two, don't think that AOP is actually part of your design here. AOP is only an implementation detail here. If Java supported C++-style private multiple inheritance that could also have been used. In fact, you could argue that that's all that you've achieved here: private multiple inheritance. Three, it is very unclear what you've gained here. Any code that invokes person.getNumDogsOwned has immediately broken the abstraction. All code that needs to know about dogs should only know about the interface.
Posted by: Bo at May 28, 2004 09:04 PM
Bo, we're actually in agreement on quite a number of points. I agree that an interface is the best way to capture the essence of "dog-ownership," which is why I introduced IDogOwner. But the straightforward "class Person implements IDogOwner" approach doesn't help me since it immediately introduces the mixed-role cohesion back into the class that I was trying to eliminate in the first place (there's a nice thing you can do with inner-aspects in these situations, I'll write about that one day too...).
If we were solving this problem in C++, we could have used a mix-in class that provides an implementation of the interface - and the declaration of the DogOwnership aspect performs exactly that role within the AspectJ language. (But compared to a language like Java that doesn't have such a capability, it's still a good gain). The C++ mix-in solution would still need to statically couple Person to the DogOwnership mix-in though right? The AspectJ solution avoids that.
For the actual usage, I made the (reasonable?) assumption that any code wanting to call getNumDogsOwned() had better be aware of the whole dog ownership concept, and thus it is reasonable to assume that such a client can code to the IDogOwner interface. If person is an IDogOwner, then I don't see that invoking person.getNumDogsOwned() breaks the abstraction at all? If there is ever any chance that person may not be an IDogOwner, then I prefer the second style of usage I showed which very clearly programs to the IDogOwner abstraction.
Posted by: Adrian Colyer at May 29, 2004 08:52 AM
Now I'm going to comment on my own comment (is that a sign of madness? a bit like talking to yourself??). I was literally out walking the dog (this person does own dog) and thinking about the mix-in solution. I said in my previous comment that the mix-in solution would statically couple Person to DogOwnership, but of course that need not be true if you create a new subclass, say DogOwningPerson that inherits from both Person and DogOwnership.
The issue then becomes one of creation - everywhere in your application that you were previously constructing a Person you now need to construct a DogOwningPerson instead. So maybe we could introduce a factory method..., or maybe that's ok in this particular application - who knows?
That's the beauty of this particular problem, it's very simply stated, and it works because there is no single right answer, just many possible solutions with different strengths and weaknesses. Which one you choose will depend on the subtleties of the particular application in hand, and we have to use our skill, judgement, and experience to make the call. The most I can hope to do here is offer a new potential solution into the mix, that shows how you might consider solving the problem using AspectJ.
Posted by: Adrian Colyer at May 29, 2004 10:25 AM
it is this last example of mixins that excites me, it allows me to build a class from reusable base implementations that are focused purely on their particular interface, and the subclass can contain any inter-type logic that may be required. [BTW Adrian, as far as precedence goes, isn't this similar to the Abstract Schema ideas of Rickard Oberg
Posted by: Jed Wesley-Smith at May 31, 2004 09:10 AM
Post a comment
Thanks for signing in, . Now you can comment. (sign out)
(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)