Overcoming the one weakness of OOP


Abstract:
OOP does not provide a built-in support, comparable to encapsulation for flexibility, in object interrelationship such that relationships between objects or structure of objects in a relationship can change, without affecting the rest of the program. 

The work of Professor Karl Lieberherr on Adaptive Object-Oriented software programming, based on the Law of Demeter, is the first to highlight this weakness of OOP, to explain its relevance and to introduce a solution based on a specific design, high-level specifications and automatic code-generation tools.


This post describes the work and the solution introduced by 
Karl Lieberherr and other well-known authors such as Steve Freeman, Nat Pryce, Tim Mackinnon and Sandi Metz. The presented solutions are based on designs that use composition, dependency injection pattern, visitor-like pattern, composite pattern and duck typing.




Glossary of ambiguous terms, as used here
Object graph: the web of relationships among a group of interrelated objects.
Object relationship
: association such as composition and aggregation, and dependency.
Object structure
: the definition of the publicly accessible object’s state and behaviour, its data and functions, its fields and methods.



Which is the one weakness of OOP?

A key advantage of object-oriented programming is certainly the kind of flexibility that object encapsulation provides.
Thanks to that flexibility, the representation of an object (the inside of the object, the internal implementation details, the internals) can be changed without affecting the rest of the program.
As a consequence, it is easy to locally understand an object in isolation, change it, extend it, evolve it, and reuse it.

When talking about encapsulation, the focus often goes to encapsulating the object’s state, the information, the data, the internal object structure.
What about relationships among objects?
OOP does not provide a built-in support, comparable to encapsulation for flexibility, in object interrelationship such that relationships can change without affecting the rest of the program.






The one weakness of OO code is that it is brittle in the face of changes to the relationship between objects and to the structure of objects that are part of an object graph.
Two common examples follow:
  1. When an object’s unstable dependency changes, it can trigger changes into the object itself and this can start a chain reaction of changes in dependent objects.
  2. When an object graph changes or some responsibilities or state is moved from one object to another, code traversing the objects and making computations on the traversed objects needs to be changed too, to adapt to the new object graph and the new structure of the objects.

Examples with source code are also available here [1] and here [2].

Overcoming the one weakness of OOP is a common design challenge for many software developers building systems with OO languages.
What solutions have been found so far? How do they relate to encapsulation?



How to overcome the one weakness of OOP?

How to make OO code less brittle in the face of changes to the relationship between objects and to the structure of objects that are part of an object graph?


This post introduces solutions from three well-known sources.
The three solutions follow below.


1. Professor Karl Lieberherr work on Adaptive programming and the Law of Demeter



In 1987 Ian Holland at Northeastern University formulated a style rule for designing object-oriented systems called The Law of Demeter [3].


The Law of Demeter is best known in its formulation at the method level that pertains to how methods are written for a set of class definitions. Typical examples of violation for this formulation include method call chains such as  dog.getBody().getTail().wag() that is colloquially known as a train wreck [4]. Conformance to this formulation of The Law of Demeter supports object encapsulation.


The formulation of the The Law of Demeter that applies to the structure of the classes, is less known. This formulation makes the notion of unnecessary coupling very explicit. Conformance to this formulation supports modularity and low coupling in object relationships, and it makes code less brittle in the face of changes in the relationships and in the related objects [4]. In other words, conformance to this formulation helps to overcome the one weakness of OO code. But how to achieve this conformance?


Between 1991 and 1996 Professor Karl Lieberherr [5] developed Adaptive Object-Oriented software programming and the Demeter Method [0], a concept that takes encapsulation to a new level. This work clearly identifies and describes the one weakness of OOP and provides a working solution.


The solution as shown here [1] conforms to the Law of Demeter and uses programming by composition so that each composite object shields other objects from changes in the composed objects and in their relationships. The solution also replaces hard-coded navigation paths used to traverse the object graph with higher-level navigation specifications. Automatic tools called Demeter Tools use the navigation specifications to regenerate and adapt the code that traverse the object graph and invoke the functions, whenever the object graph or an object structure changes.


Here I name this solution as: “Programming by Composition + Demeter Tools”.


2. Mock Objects and Growing object-oriented software, guided by tests

Thanks to Steve Freeman, Nat Pryce and Tim Mackinnon for reviewing the draft of this post!


Between 1999 and 2010 a group of people from Connextra team first and then from Extreme Tuesday Club (Connextra team members: Tim Mackinnon, Tung Mac, Matthew Cooke, Iva More, Peter Marks, John Nolan; Extreme Tuesday Club members: Steve Freeman, Philip Craig, Oli Bye, Paul Simmons; Joe Walnes from ThoughtWorks and Nat Pryce) explored, experimented and developed a new way of writing object-oriented software that revolves around the practice of test-driven development (TDD), tests automation and a new technique called Mock Objects.

The full story is documented here too [10]. The initial work between 1999 and 2004 has been documented into two papers [6][7], and in 2010 in a new book [8] that summarised the whole experience produced by all the people involved.



The trigger for the discovery of the technique was when John Nolan set the challenge of writing code without getters and then favouring void methods over non-void ones. Later Peter Marks helped coin the name ‘Mock'.

The driver for the technique was literally a pragmatic way to practice TDD and write good tests without going against what was felt were good design principles, for example without exposing object internals for the sake of testing, or shying away from composition. The work was then inspired and influenced also by the Law of Demeter and Lieberherr’s work and also by [12][13].


Probably the most important effects on coding style were the development of the Mock Objects, and favouring composition over inheritance that led toward programming by composition. Programming by composition led to decouple object behaviour from the structure of the object graph. In addition to that, each composite object shields, to some degree, other objects from changes in the composed objects.

Programming by composition also led toward a design pattern that nowadays is called dependency injection [9] not to be confused with the dependency injection frameworks (a.k.a. IoC frameworks or IoC containers) that were never needed in the large Connextra code base. In turn dependency injection led to minimising objects’ dependencies and to decoupling dependencies.

Unlike the solution of Lieberherr, this solution does not use automatic tools for code generation.

Here I name this solution as: “Programming by Composition + Dependency Injection”.


A secondary effect on coding style was the tendency to push behaviour towards Visitor-like objects, objects resembling the Internal Iterator pattern [11] that have similarities with the Visitor pattern. A team member from Connextra remember using the Visitor-like pattern together with the Composite pattern used among other things to abstract away differences between the traversed objects.

The abstraction introduced with the Composite protects the code, to some degree, from changes in the object graph and in the structure of the object.
The Visitor-like design reverses the direction of an unstable dependency relationship (from a stable object to an unstable one), turning it into a stable relationship (from the unstable object acting as the "visitor" to the unstable one acting as the"element").




While the focus on this solution diminishes after the first paper, it is documented here because of a similarity with the solution presented by Sandi Metz.

Here I name this solution as: “Visitor-like + Composites”.



An interesting after thought about the technique of TDD with mocks from Tim Mackinnon: I can’t stress enough how most people have missed, and still do, that connection with CRC cards, mocks and role play. Mocks and the technique really came from the idea of working with a partner to act out what you expected the design/objects to do - and then “asserting” those interactions. That was the number one design point. For us, this was the aha moment.

On the same line Nat Pryce comments: There was quite a change in emphasis between the Endo-Testing paper and the Mock Roles, Not Objects paper and GOOS book. For example, the former recommended using mock objects to fake third-party APIs that are difficult or slow to use for real in tests, such as JDBC. The latter recommended *not* mocking third-party APIs, but rather discovering the appropriate interfaces for mocking from the need of client objects. Also the Mock Roles, Not Objects and GOOS style focused more on messages and protocols between objects.


3. Less, The path to better design



Sandi Metz in her speech 'Less, The path to better design' [2] takes on the challenge of the one weakness of OOP and presents design solutions that deal with changes in unstable object’s dependencies, both in the object structure and in the object graph.


The approach she presents is based on the idea that designers cannot predict the future but they can guard against it choosing carefully object dependencies, identifying those that are less stable and surrounded by more uncertainty and then aggressively decoupling them.

This approach is in tune with the second formulation of the The Law of Demeter that applies to the structure of the classes.


The solution that Sandi Metz presents, employs a design similar to the visitor pattern to decouple from unstable dependencies.

In the problem presented there are two objects that needs to interact to execute a task. The first object is known, under control and more stable, the second object is less stable because surrounded by more uncertainty. In order to reduce the coupling of the first one to the second, the second object acts like a Visitor in the Visitor pattern while the first object plays the role of the visited element. This design reverses the direction of the dependency, doing so it turns the unstable dependency relationship into a stable one.

Unlike the previous solution, this one does not make use of the Composite pattern to guard the code against changes in the object graph, because the language she uses is Ruby that supports duck typing, which serves the same purpose.


Here I name this solution as: “Visitor-like + Duck Typing”.



Comparing solutions

These are some similarities about the solutions suggested by the authors:

  • One solution uses tools and higher-level navigation specifications to regenerate code whenever there are changes to the relationship between objects and to the structure of objects.

  • Two solutions include the use of Programming by Composition: to decouple object behaviour from the structure of the object graph, and to shield, to some degree, other objects from changes in the composed objects.

  • One solution include the use of the dependency injection pattern: to minimize objects’ dependencies and to decouple dependencies.

  • Two solutions include the use of a Visitor-like pattern, used essentially to reverse the direction of an unstable dependency and so turning the dependency relationship into stable.

  • Two solutions abstract away differences between objects traversed in an object graph to protects the code, to some degree, from changes in the object graph and in the structure of objects:
    • One solution for statically typed languages does this with the Composite pattern,
    • The other solution for dynamically typed languages does this with Duck Typing.

  • All the solutions explicitly tell how to carefully choose and limit dependencies, with different but substantially equivalent means.

 

Print | posted @ martedì 20 gennaio 2015 02:14

Comments on this entry:

Gravatar # re: Overcoming the one weakness of OOP
by Indrit at 28/05/2015 18:01

Hi Luca,

maybe, as usual, the weak points could be also the strength ones and viceversa.
Therefore yes, on one hand, technically, it is true that the way we model/design our objects relationship could determine "axis" of code malleability but to the other hand on change/new requirements (and on increasing knowledge) it "forces" us to continuously learn more about the specific problem ({something} Driven D and etc) and adapt therefore implementing SOFTware.

That said, I must admit that I have yet to understand the first method, i.e. "Programming by Composition + Demeter Tools" but the 'automatic' tools sounds currently to me like an alarming bell...
Comments have been closed on this topic.