James O. Coplien and Trygve Reenskaug have recently published the first article of a series that will introduce the new architectural approach to object oriented programming based on Data, Context and Interaction (DCI) pattern.
In this first article, the authors argue that, though object oriented programming is instrumental for capturing structure, it doesn’t allow fully expressing user mental models because it fails to represent “end user behavioral requirements”. To illustrate what they actually mean by “behavior”, they take an example of a Savings Account object that can, for instance, decrease its balance and do a withdrawal. According to Coplien and Reenskaug, “these two behaviors are radically different”: “decreasing the balance is merely a characteristic of the data: what it is. To do a withdrawal reflects the purpose of the data: what it does”. The fact of being able to reduce balance characterizes data in any situation – it is stable. Withdrawal, on the contrary, involves “interactions with an ATM screen or an audit trail” – it is dynamic, it is no longer about “being” but rather about “doing”.
While the user model naturally combines the being and the doing parts, “there is little in object orientation, and really nothing in MVC, that helps the developer capture doing in the code.” “Object-orientation lumped [these two actions] into the same bucket” making it difficult to separate “simple, stable data models from dynamic behavioral models”, which is though essential from architecture and maintenance perspective. Moreover, pure object orientation requires splitting up large algorithms and distributing their parts – methods - to objects that are most tightly linked with a given method. However, while some algorithm can live within a single object, “interesting business functionality often cuts across objects.”
To represent these dynamic behavioral models, James and Trygve advocate for using the DCI model that is based on three concepts:
- The data, that is expressed with domain objects representing the stable parts;
- The interactions, expressed in terms of roles that are “collections of behaviors that are about what objects do”;
- The context, that can be viewed as “a table that maps a role member function (a row of the table) onto an object method (the table columns are objects). The table is filled in based on programmer-supplied business intelligence in the Context object that knows, for a given Use Case, what objects should play what roles.”
To provide readers with a concrete illustration, the authors use an example of Money transfer Use Case. Even though the transfer would involve the savings account and the investment account, within this precise Use Case, the user will rather reason in terms of “source account” and “destination account”. These are roles and the interactions of Money transfer can be described through their algorithms. These roles can then be played by different objects depending on context: in this precise example a source account role will link to the savings account object.
A general design concept that would allow representing roles in code would be a trait but its implementation would depend on constructs that exist in a given programming language: traits in Scala, Squeak Traits in Squeak Smalltalk, templates in C++, etc… The greatest advantage of this approach is that the example of code provided by the authors is “almost a literal expansion from the Use Case”:
That makes it more understandable than if the logic is spread over many class boundaries that are arbitrary with respect to the natural organization of the logic—as found in the end user mental model.
This article triggered a great number of reactions and critics that allowed James and Trygve to provide some precisions about DCI concept.
Michael Feather and many other commentators argue that assigning the responsibility for transfer to the source account is arbitrary and doesn’t really fit users’ mental model where transfer is not done by either account but rather a bank or “transaction objects which map to the user's conception of an interaction”. John Zabroski, for instance, suggests using the analysis class TransferSlip. Some other argue that DCI relates to things that people already know : “traits” in some language, “the general idea [of functional programming] that algorithms matter and should be able to be clearly expressed”, etc…
James O. Coplien responds that DCI “tries to reproduce the convenience of algorithmic expression that procedural languages [e.g. Fortran] used to give us combined with many of the good domain modeling notions from 1980s object orientation.” Traits in languages like Scala are a “way of rendering the solution” but different constructs can be used in other languages in order to yield DCI architecture. What counts indeed is not the tool suggested or the example used but the architectural approach of separation between: 1) behavior that is specific to the domain object whatever the situation is, and 2) behavior that is context-specific, that belongs to business logic and often cuts across objects. As Bill Venners puts it, “if the account concept is involved in 10 use cases of your application, you may end up placing some behavior for each of those use cases into class Account” and this is a big challenge for the designer. So letting “an object have a different class in each context” by applying DCI is “an attempt to improve the understandability of OO programs”:
[…] this article points out that sometimes you can end up wanting to put too much [behavior on] objects, and that different subsets of all that behavior may be needed in different contexts. [The authors suggest that] you model that extra stuff in traits, and that the traits would map to roles in the user's mental model. And then in a particular context, or use case, you add on the traits that you need for that context to the dumb domain objects.
To insist on readability that is yielded by DCI, Coplien points out four reasons why it renders code easier to read and to debug:
1.The context switches across business functions are fewer and more closely follow the mental model (role-based) than the programmer model (domain-based);
2. Inclusion polymorphism is almost completely gone. When I call foo I get foo: not one of many foos up and down the subtyping hierarchy.
3. I can find a test point for something of business value: that is, I can really do BDD. That makes it easier to develop test cases to support debugging.
4. I do less run-time debugging because the code is more readable at compile time.
Trygve Reenskaug stresses that to understand DCI, one needs to “lift one's eyes from the class abstraction and open one's mind to an additional abstraction that applies to such object structures” and “to add an object abstraction that augments the class and that retains object identity”: a role.