TITLE: discussion of encapsulation heuristics (Group: patterns-discussion@cs.uiuc.edu) [ This is part of an interesting discussion about partitioning and encapsulation. The discussion is over a design like the following, where Editor and Player use instances of the class Composition. +--------+ | Editor |<>---------+ +--------+ | | +-------------+ +---+ Composition | | +-------------+ +--------+ | | Player |<>---------+ +--------+ In this discussion, it is assumed that the Composition class has six methods, some only used by Editor and some only used by Player. -adc ] ARTHUR: arthurr9@mail.idt.net > The Composition class does have all six methods. The fact > that one application doesn't want to see them is > irrelevant. ROBERT: Robert Martin's comments: > No, its not! And this is a very important issue. The fact that > all six methods are present means than when the Player application > needs a new method in the Composition class, the "Editor" application > must be recompiled. This is not insignificant even when there are > just two applications. But when there are many applications, this > becomes a problem that cannot be endured. > > Interface pollution is a serious problem and should not be ignored, or > whitewashed. There are times when preventing it is at odds with > encapsulation. However, that's just a fact of life. Neither rule is > paramount. Encapsulation must sometimes be sacrificed so that the > applications are buildable. These are *engineering* decisions. ARTHUR: But it is not interface pollution. If the editor and player were simply two modules of one application we would put all six methods on composition without hesitation. These are methods which use all of the data of the abstraction all of the time. They clearly belong to the composition abstraction. If you're telling me that due to accidental complexity imposed by C++ (or compiled languages in general), that I must throw encapsulation out the window then I'd have to say you've just given me the best argument possible to learn Java and abandon C++. You're corrupting every reusable framework implemented with a compiled language due a physical design issue (the need for recompilation). The trade-off here just doesn't make sense to me. ROBERT: "Robert C. Martin" , 8 Jul 96 Java has the same problem. So does smalltalk. It's not a problem of language, it is a problem inherent to encapsulation. If you encapsulate two or more separate service interfaces together in the same class, then the class becomes a focus for maintenance problems. Whenever one application forces you to change one of the service interfaces, all the other applications that use that class will have to be recompiled and retested. *Work* will need to be done in every client application, even those client applications that don't use the particular service interface that changed. In many development environments, this is not an acceptable alternative. But there is a solution. You have described two applications; I assume you mean two seperately linked executables. These two applications use a framework which includes an abstraction that you called 'Composition'. One of the applications, the Editor, needs to call methods a, b, and c of the Composition class. The other application, the Player, needs to call methods x, y, and Z of the Composition class. My assertion is that a, b, and c form a single abstraction, call it the CompositionEditAdapter, and that x, y, and z form another completely separate abstraction, call it the CompositionPlayerAdapter. With the following relationships: (using ASCII UML) +--------+ +-------------------+ | Editor |<>-------+ CompositionEditor | +--------+ | Adapter |<>--+ +-------------------+ | +-------------+ +---+ Composition | | +-------------+ +--------+ +-------------------+ | | Player |<>-------+ CompositionPlayer |<>--+ +--------+ | Adapter | +-------------------+ Now, the service interface used by the Editor has been separated from the service interface used by the Player. When Editor needs changes made to the CompositionEditorAdapter, it will not affect the Player. The downside to this pattern is that the 'Composition' abstraction has been weakened. Some of its methods have been exported and placed into the Adapters. Composition begins to look more like a data structure with no methods than a real class. And that's where the reasoning stops. OO is not a perfect discipline, it does not solve all problems. There is an engineering trade off to be made at this point: 1. Do we weaken the 'Composition' abstraction in order to protect the Editor and Player applications from each other. 2. Or, do we merge the two service interfaces together into a single Composition class, and live with the fact that when one application forces changes to its service interface, the other application will need to be recompiled and retested. There is no simple answer. I prefer choice 1 because I think that there will eventually be more than just two applications. Perhaps the client will need an indefinite number of applications that must manipulate composition. With each new application of this sort, the problems inherent to option 2 above worsen. Every change to a service interface causes all of the other applications to be recompiled and retested. This can quickly get out of hand. On the other hand, if we are absolutely sure that there will never be more than two applications; and if we are absolutely sure that those applications are not going to change very much, then we might risk option 2 in order to keep the composition abstraction together. However, consider this. The network of Composition, and its adpaters above, *are* the Composition abstraction. There is no reason why a single abstraction cannot be represented by a multitude of classes. Indeed, we do this all the time when we bind iterators and containers together with friendship relationships into a single abstraction. I have not thrown encapsulation out the window, I simply have not adhered to the oversimplification that a class is the only form of encapsulation boundary. IMHO, it is just as valid to make a network of classes act as an encapsulation boundary.