TITLE: What makes OOD good? RESPONSE: rmartin@rcmcon.com (Robert Martin), 30 Nov 94 From time to time I post articles which make reference to "good" practices in OOD. So I thought it was about time for me to supply the missing context and define what I think a good OOD is. What makes an object-oriented design "good"? Robert Martin 30 Nov 1994 Much has been promised about the benefits of using object-oriented design (OOD). Programs that are designed using OO methodologies are supposed to be more robust, more maintainable, and more reusable. Why? To answer this, we need to understand what the forces that make a program less reusable, less robust and less maintainable are. Consider the typical scenario: A project starts out fine. Even its first few releases survive the jitter of specifications and the demands of the users. But as time progresses, the program becomes harder and harder to maintain. With each new feature that is added, the risk of making a change to the software increases. Eventually the software reaches a state where making any change, regardles of how trivial, is almost certain to introduce one or more new bugs somewhere else in the program. At this point the program has become unmaintainable, and brittle. As for reuse, again, early in the lifecycle of a product, portions of that product can be extracted and reused in different contexts. Typically this involves copying and restructuring the code. However, as the product moves through its lifecycle, the chances that some portion of it can be successfully extracted and reused in a different environment decrease rapidly. Eventually, the quality such an extraction is so low, and the effort to support it is so high, that it becomes less costly to simply redesign and rewrite the necessary functionality instead of extracting it from the existing product. What is it that forces software to degrade? Why do reusability, maintainability and robustness decrease over time? It is because, as the product is maintained, the number of interdependencies between the modules increases. With each new dependency comes a risk that making a change in one module will create a bug in another. Also, with each new dependency, extracting a module for reuse becomes more difficult since that module depends upon other modules. If OO is going to help us create more resuable, reobust and maintainable software, then it must address the issue of dependencies. Inter module dependencies must be kept to a minimum, and must not increase (or must increase at a much slower pace) during the product's life cycle. If the modules in a software product are all independent of each other, then there is no way that changing one module can produce bugs in another. Moreover, each module can be reused without worrying about dragging the other modules along. How can we achieve this level of inter module isolation? The first principle of OO gives us a clue. This principle: "The open-closed" principle states that a module should be open for extension, yet closed for modification. This is an incredible statement. How can you change what a module does if you don't change the code in the module? By using the tools of OOD, abstract interfaces and polymorphism. class DisplayItem { public: virtual void Draw() const = 0; virtual void Move(double dx, double dy) = 0; }; void DrawTriple(DisplayItem& d) { d.Draw(); d.Move(1,1); d.Draw(); d.Move(1,1); d.Draw(); d.Move(-2,-2); // put it back. } Notice that the 'module' DrawTriple depends only upon the abstract class DisplayItem. We can create as many derivatives (subclasses) of DisplayItem as we please. For example, we could create Square, Circle, Triangle, SmileyFace, etc. Each of these new derivatives can be manipulated by the DrawTriple function, yet DrawTriple *does not depend* upon these derivatives. In fact, DrawTriple can be compiled and put away in a library long before any of the derivatives of DisplayItem are written. Thus, DrawTriple can be extended to draw any new kind of shape we wish, without modifying it at all. This is a perfect example of a "reusable client". DrawTriple is a client of DisplayItem. It is also a client of any derivative of DisplayItem. And it is reusable. I can reuse DrawTriple with any new derivative of DisplayItem without creating a dependency. Consider this from a different point of view. DrawTriple makes a statement of high level policy. "Draw three copies of some item offset from each other by one unit in X and Y". This policy can be implemented on any derivative of DisplayItem, whether that derivative is written yet or not. The essence of the open closed principle is that it is possible to put a great deal of the high level policy of an application into modules that are closed. This is possible *if* we write those policies such that they manipulate abstract interfaces rather than concrete objects. Once modules are closed in this fashion, they become clients that are independent of their servers. Thus, if changes are made to their servers, the clients are unaffected. The more modules that are closed, the less interdependent the design is. And the fewer opportunities there are for introducing new dependencies during the product's lifecycle. Thus, the robustness, reusability and maintainability of the design is greatly enhanced. However, creating designs which minimize interdependence is non-trivial. OO provides the tools to achieve this, but does not provide any guarantees. It is completely possible to use OO tools to create software that is just as interdependent as procedural software is. If the designer is not focused on the elimination and management of dependencies, then those dependencies will not be managed. If the designer is not familiar with the design patterns that reduce interdependency, then he may miss opportunities to break dependencies. If the designer is not familiar with the principles of creating substitutable derivatives, he may wind up *creating* the very dependencies that he is trying to eliminate. So. What is a good OO design? If one had to choose a single attribute to measure the quality of a design, I would choose to measure interdependence. A "good" OOD is one which has made use of abstract interfaces and polymorphism to minimize the interdependence between its modules. I have recently written a paper discussing design metrics which measure interdependence between modules. I posted this paper to this group a few months back, so I will not repost it now. However if anybody would like a copy, send me some email and I will forward the paper.