Design, in general, involves complex mental processes, most of them poorly understood. However, it is commonly believed that all design entails using previously learned patterns at some level of detail. One can certainly assume that the designers who are celebrated for their work use patterns at a fairly low level of detail. A musical genius like Mozart probably used any previously learned patterns for only the most basic of the sound effects he wanted to create. On the other hand, it would be safe to say that the lesser composers of his time (or, for that matter, of any time) have probably borrowed significantly from the harmonies and the rhythms created by the geniuses.
Whereas the need for the content to be original in the artistic and the literary domains necessitates that the use of existing patterns be kept to a minimum in a new design, exactly the opposite is true for the case of software. In software design, although the need to be original and creative is important in dealing with hitherto unseen problems, of greater importance are the correctness and the robustness of the software produced.
If a new problem in software design is similar to one seen previously (and for which a correct software solution is already known to exist), the previously developed solution is preferred over what would otherwise be a new and creative way of solving the new problem. Using a previously known trusted solution to the problem can only increase the confidence that others place in your software.
And, should it happen that you are dealing with a large complex problem that does not lend itself to any single previously known solution strategy, youâd be expected to be creative in decomposing the problem into subproblems that can be solved with previously known trusted solutions.
In general, when you decompose a large problem, the subproblems that result are likely to be of varying levels of difficulty and detail. It is even possible that your overall decomposition will be hierarchical in which the smallest of the problems can be solved by using the well-known programming idioms in the language you are using for your software development. And, yes, those programming idioms can also be called patterns. However, we will not concern ourselves with such low-level design issues in this book.
On the other hand, this book is about the trusted solutions for what may loosely be referred to as the mid-level problems you are likely to encounter in creating a software solution for a large problem. In particular, we will focus on the mid-level problems that can be solved by the twenty-three patterns first proposed by four authors who are now affectionately referred to as the Gang of Four (GoF). The book in which these patterns first appeared is now commonly referred to as the âBibleâ of the object-oriented (OO) design patterns. The next section is devoted to this book and its contents.
1.1 THE OO DESIGN PATTERNS âBIBLEâ BY GoF
The patterns movement in the software community was started by the much celebrated book âDesign Patterns â Elements of Reusable Object-Oriented Softwareâ by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides [1]. Drawing from their collective experience with object-oriented programming, the authors succeeded in crystallizing out twenty-three design patterns that have become, as is now universally acknowledged, the building blocks of much modern object-oriented software. As mentioned in the previous section, these authors are known as the Gang of Four (GoF) and the book frequently referred to as the âOO Patterns Book by GoF.â
What is amazing about the GoF book, and also what makes the book timeless, is not only the large variety of programming problems that can be solved by its twenty-three patterns, but also the fact that the authors had the foresight to recognize a host of basic issues in object-oriented design that are likely to endure for all time. To grasp the reality of the moment and to abstract from it new fundamental understandings that can serve us for a long time into the future is no small feat.
Central to most GoF patterns is the interplay between the following four tenets of good object-oriented programming: (1) Programming to the public interfaces declared at the roots of class hierarchies, as opposed to calling directly the methods defined in the implementations of those interfaces. (2) Choosing composition over inheritance if a purely inheritance-based implementation is likely to result in an unmanageable number of classes as you try to figure out the best way to create representations for all the different variants of a generic object. (3) Again choosing composition over inheritance when the flexibility made possible by the former in how the objects relate to one another is more important than the representational efficiency provided by the latter. (4) Exploiting function overriding for runtime adaptation of the behavior of a class to the implementations provided by its subclasses.
Here we will briefly review the intuitive underpinnings of the tenets listed above: When the users of a class hierarchy make sure that their own code calls only the public methods declared in the root interface of the hierarchy, folks whose business it is to provide and maintain the implementation code in the hierarchy acquire the freedom to change that code as long as the interface declarations remain unchanged.
Regarding the second tenet, even though inheritance is enshrined as a cornerstone of object-oriented programming, using it without thought may result in class hierarchies that are much too large. If you try to create a subclass for capturing every small variation from a generic class, you could end up with too many subclasses. Why not take care of the small variations through composition, that is, by endowin...