In this presentation recorded during QCon London 2008, Udi Dahan, The Software Simplist as he calls himself, explains why sometimes it is not enough to apply good OOP and patterns lessons. He introduces a new principle: make roles explicit.
Watch: Making Roles Explicit (1h)
The entire presentation is about the need to make explicit what one knows about the system. Dahan uses a concrete code example which has some weak points when it comes to flexibility, and demonstrates what it means to make roles explicit and how to do that.
He also responds to questions from the audience during the last third of the session.
Community comments
clarification......
by jack donn,
Re: clarification......
by Udi Dahan,
Relation to DCI
by Sebastian Kübeck,
Expression problem
by Yardena Meymann,
Sounds like Qi4J too me
by Geert Pante,
Re: Sounds like Qi4J too me
by Rickard Öberg,
What is value gained by the coupling...
by Pete Kirkham,
Re: What is value gained by the coupling...
by Udi Dahan,
Re: What is value gained by the coupling...
by Pete Kirkham,
"Making Roles Explicit" style when working with legacy code
by Alexey Sorokin,
Re:
by Udi Dahan,
IWonderAboutInterfaceNames
by Steve Freeman,
Web application
by Tom Revans,
slides
by Martin Menke,
Nothing new is under the Sun
by Vadim Samokhin,
clarification......
by jack donn,
Your message is awaiting moderation. Thank you for participating in the discussion.
in the code fragment "... ORM.Get<IMakeCustomerPreferred> (customerId)"
is the method .Get<>() a form of generic..? I get the idea but was not familiar with the syntax
Thanks for an outstanding presentation!
<></imakecustomerpreferred>
Re: clarification......
by Udi Dahan,
Your message is awaiting moderation. Thank you for participating in the discussion.
Yes, that's a generic method.
Glad you liked it.
Relation to DCI
by Sebastian Kübeck,
Your message is awaiting moderation. Thank you for participating in the discussion.
The approach seems to be similar to DCI...
www.artima.com/articles/dci_vision.html
However, you apply the roles permanently, not context specific as DCI does. As a result, you need workarounds not to loose cohesion. Your naming is different too. You would call the role "TransferMoneySink" "ITransferMoneyFromSource" but the idea is the same. From what I see, DCI could be your next step in enhancing OOP with roles.
Expression problem
by Yardena Meymann,
Your message is awaiting moderation. Thank you for participating in the discussion.
I am only about half-way through the presentation, but it reminds me a lot of the expression problem (revisited by Mads Torgersen) and Odersky/Zenger's Scala solution using traits.
Sounds like Qi4J too me
by Geert Pante,
Your message is awaiting moderation. Thank you for participating in the discussion.
www.qi4j.org
Principles
Composite Oriented Programming builds on some principles that are not addressed by Object Oriented Programming at all.
* Behavior depends on Context
* Decoupling is a virtue
* Business Rules matters more.
* Classes are dead, long live interfaces.
What is value gained by the coupling...
by Pete Kirkham,
Your message is awaiting moderation. Thank you for participating in the discussion.
between the thing which you get from the environment which has the role 'make a customer preferred' and the customer? (the UML diagrams show Customer implements IMakeCustomerPreferred) Why does 'Customer' implement this? Do you ever down cast to 'Customer'? There's an obvious overhead in terms of all the ORM stuff to marshal the orders in and out of the DB to mutate them rather than implementing the role in terms of of the DB, as well as the obvious disadvantages of making the Customer less cohesive. Are there any advantages?
Re: What is value gained by the coupling...
by Udi Dahan,
Your message is awaiting moderation. Thank you for participating in the discussion.
> Why does 'Customer' implement this?
Because the 'Customer' class is responsible for if/when it can be made preferred - for instance, if the customer is already in the 'delinquent' state (doesn't pay their bills), it cannot be made preferred. There are other rules encapsulated in that class.
> Do you ever down cast to 'Customer'?
Haven't had to yet.
> rather than implementing the role in terms of of the DB
It sounds like you find that implementing this kind of logic in the DB is preferable. I've found that keeping the logic in code makes it easier to unit test (which is very valuable when one has many interacting rules).
Hope that's clearer.
-- Udi
Re: What is value gained by the coupling...
by Pete Kirkham,
Your message is awaiting moderation. Thank you for participating in the discussion.
> > Why does 'Customer' implement this?
> Because the 'Customer' class is responsible for if/when it can be made preferred - for instance, if the customer is already in the 'delinquent' state (doesn't pay their bills), it cannot be made preferred. There are other rules encapsulated in that class.
That information should be in the data model. I'd tend to make such rules explicit. It's the customer's handler which decides that, not the customer (ie the customer does not have autonomous responsibility for deciding whether it is delinquent).
> > Do you ever down cast to 'Customer'?
> Haven't had to yet.
That's a big clue that you don't need the coupling. I prefer objects which do one thing and one thing only; if nothing in the logic says it's a customer, and its only role is to apply a rule to a customer's orders, then it can be separate.
> It sounds like you find that implementing this kind of logic in the DB is preferable.
Not particularly, but much of the discussion was how to work around the overheads in terms of (memory) performance which are associated with making the thing which is performing the transformation on the data relating to a customer, how you then had to duplicate the mapping code, then tell the mapping code about what was calling the mapping code and what would be done with the objects it returns. That seems a very large number of hoops to go through for a very simple data update.
In a good OO design, the client code shouldn't care where that functionality is implemented. Since the code doesn't need it to be a Customer, and there is a performance problem, moving the implementation closer to the data would eliminate the problem with minimal work.
I do tend to use loosely-coupled active, autonomous objects for the process, and leave data as data. In the pre-computer world, the customer doesn't have orders - orders are recorded in a file, and if the company makes the customer prefers, some clerk updates the orders to add a discount. The clerk checks the customer's status, and does the work. Nothing in the system cares about the clerk's identity - she could be a temp taken on just for that single task (ie a temporary object). It's not the customer's role to update the company's record of the customer's orders or check the customer's credit record - if a customer could could do that, they all would have perfect credit and 100% discounts.
> I've found that keeping the logic in code makes it easier to unit test (which is very valuable when one has many interacting rules).
Moving code for performance reasons sometimes doesn't always fit with unit tests. On the other hand, I've found the single responsibility rule very valuable in managing complexity - unit testing an object with a single responsibility is usually easier than unit testing several interacting rules.
"Making Roles Explicit" style when working with legacy code
by Alexey Sorokin,
Your message is awaiting moderation. Thank you for participating in the discussion.
What I want to share with you - is that this style of explicitly
describing of what your code is in the code itself has also great
advantages when you're working with legacy code.
Of course, I'm writing all new code using this style, but actually I
realized the great benefits of such approach when I were working with
legacy code. And in the meaning of term "legacy" I rather agree with
Michael Feathers - "people are writing legacy code right now" and "the
main thing that distinguishes legacy code from non-legacy code is
tests, or rather a lack of tests".
My story is: I've joined to existing .NET project. It is a GUI desktop
application that, simply speaking, able to subscribe to some kind of
messages from some server and to show this messages in grid. And my
task was to implement some new kind of subscription.
The example:
Within the project we have the class named Controller. But after I
analyzed this class I realized that it's a much more than a Contoller
(in a bad sense of course). The code in that class also establishes
connections and sends some bytes and manipulates some UI controls
directly. So as a TDDer I never change code without changing first (or
writing from the ground in this case) a unit-test. But as you can see
there is no way that you can write unit-test on such a piece of code.
And also there is no way that you can refactor this code without test
and do not break something. This code is so complex that you cannot
fully understand it and you even shouldn't try - because you easily
miss something and it will be the illusion of understanding. But the
task must be done. And it is exactly where the interfaces come to
help.
With help of interfaces I able to write a unit-test on some piece of
behaviour of that huge complex class with very small changes. And
these changes is rather "just some typing" as you said in your
presentation, not some big efforts (because big efforts can easily
break existing behaviour). And very first step to this - is to admit
all bad things in our code and express it in the code itself. So I did
it (in context of my current task of course):
public class Controller : ISubscriptionService, ISubscriptionView
{
ISubscriptionService subscriptionService;
ISubscriptionView subscriptionView;
...
}
and in the code that creates the Controller class:
Controller controller = new Controller();
controller.RegisterSubscriptionService(controller);
controller.RegisterSubscriptionView(controller);
Some crazy code at first look. Yes it is, but it is only the admitting
of what already exists.
After that I used a few "extract method" refactorings to separate
thing. I think it is quite safe refactoring, and doing it carefully
you don't break anything. But the key point here, is that in method
Subscribe (that I'm going to cover by unit-test and change after that)
I'm using extracted method through these interfaces. For example:
...
try
{
subscriptionService.SendSubscribeCommand();
subscriptionView.ShowSuccessMessage();
}
catch
{
subscriptionView.ShowErrorMessage();
}
...
subscriptionView.AddRowToGrid();
...
and now I'm able to write unit-test to cover behaviour of just
Subscribe method using NUnit and NMock for interfaces for example.
So all of this became possible when I explicitly express the roles in
my code, even if they badly intermixed. Going further I can
step-by-step separate all responsibilities - now it's only the matter
of moving pieces of code from class to class, or maybe re-arranging
method within interfaces.
Re:
by Udi Dahan,
Your message is awaiting moderation. Thank you for participating in the discussion.
Alexey, thanks very much for sharing your experiences.
IWonderAboutInterfaceNames
by Steve Freeman,
Your message is awaiting moderation. Thank you for participating in the discussion.
A nice talk and good to see a focus on Roles presented to a new audience.
I'm wondering about the style of the interface names, in that they bind the functionality to the Customer by name, so they lose context-independence without the benefit of compilation checking. Are you doing that to help the ORM pick up the right object?
Longer version at www.m3p.co.uk/blog/2009/04/27/spreading-the-wor...
S.
Web application
by Tom Revans,
Your message is awaiting moderation. Thank you for participating in the discussion.
Hi,
Great web presentation. Do you have any code examples, even very basic ones you could post? For instance, if I have a home page of a website obviously I want to display lots of different information, do I expose many services and call them from the application or do I call a single service e.g. something like IGetHomePage - What would advise here?
Re: Sounds like Qi4J too me
by Rickard Öberg,
Your message is awaiting moderation. Thank you for participating in the discussion.
Yes, Udi just made a great sales talk for Qi4j. I realized that this is the only way to make sense of a complex domain model back in 2002. Now those ideas are implemented in Qi4j, and it allows you to model your entities using only interfaces, implemented by mixins.
And this is a big difference between Udi's version and Qi4j: it seems like Udi will get multiple physical objects, which will map to the same id, but are not really one object, runtimewise or modelingwise. They are just a bunch of completely separate objects which happen to map to the same id in the database.
In contrast, in Qi4j each Entity Composite that has all of these interfaces (in my current project each Entity has about 10-15 interfaces, easily) is a single object, so you can cast from one interface to another if you have to. Using .equals() works properly. And many other things. What Udi is saying is a good way to wake up from this (as he put it, quite correctly) egocentric way of modeling without explicit roles, and look for other ways to do it. Qi4j I think is a logical next step after you do what Udi talks about, and as others have pointed out, DCI is then the logical next step to get a more formalized way of thinking about it. My next talk at the Oredev conference is "DCI in practice", showing how to use Qi4j to implement DCI principles. Please stop by if you have the chance :-)
slides
by Martin Menke,
Your message is awaiting moderation. Thank you for participating in the discussion.
the slides are linked here: www.udidahan.com/2008/07/23/presentation-intent...
Nothing new is under the Sun
by Vadim Samokhin,
Your message is awaiting moderation. Thank you for participating in the discussion.
I guess that's what Rebecca Wirfs-Brock told about in her book Object Design: Roles, Responsibilities, and Collaborations. Though she hasn't stated that one should define an interface for each role, but it's an inevitable and absolutely reasonable consequence of applying the technique she introduced to find good objects.