Chapter One: Introduction
Designing object-oriented software is complex, and reusable object-oriented software is even more challenging. You must find pertinent objects, factor them into classes at the right granularity, define class interfaces and inheritance hierarchies, and establish key relationships among them. Your design should be specific to the problem and general enough to address future issues and requirements.
What is a Design Pattern?
A design pattern names, abstracts, and identifies the critical aspects of a standard design structure that make it helpful in creating a reusable object-oriented design. The design pattern identifies the participating classes and instances, their roles and collaborations, and the distribution of responsibilities. Design Patterns describe problems in your environment and explain the gist of the solution to that problem so that you can iteratively use this solution. A design pattern has four critical elements;
- The Pattern name is a handle to describe a design problem, its solutions and consequences in a word or two. Naming a pattern allows you to design at a higher level of abstraction.
- The problem: this explains when to implement the pattern. It explains the situation and its context. Describes specific design problems like how to represent algorithms as objects.
- The solution: this illustrates the elements that make up the design, their relationships, collaborations and responsibilities.
- The Consequences: these are the results and trade-offs of applying the pattern.
Design Patterns in Smalltalk MVC
The Model/View/Controller (MVC) triad of classes [KP88] is used to build user interfaces in Smalltalk-80. The Model View Controller has three kinds of objects. The Model is the application object, the View is its screen presentation, and the Controller defines how the user interface reacts to user input. Before MVC, user interface designs tended to lump these objects together. Then the MVC decouples them to improve flexibility and reuse. MVC decouples views and models by establishing a subscribe/notify protocol. A view must ensure that its appearance reflects the state of the model. Whenever the model’s data changes, it tells views that depend on it. In response, each view gets an opportunity to update itself. This approach lets you attach multiple views to a model to provide different presentations. You can also create new views for a model without rewriting it.
MVC lets you change how a view responds to user input without changing its visual presentation. You might want to change how it responds to the keyboard, for example, or use a pop-up menu instead of command keys. MVC encapsulates the response mechanism in a Controller object. A class hierarchy of controllers makes creating a new controller as a variation on an existing one easy.
Describing Design Patterns
design patterns are defined using a consistent format. Each pattern is divided into sections according to the following template. The template lends a uniform structure to the information, making design patterns easier to learn, compare, and use.
- Pattern Name and Classification: The name succinctly conveys the pattern’s essence. A good reputation is vital because it will become part of your design vocabulary. The pattern’s classification reflects the scheme we introduce in Section 1.5.
- Intent: A short statement that answers the following questions: What does the design pattern do? What are its rationale and objective? What particular design issue or problem does it address?
- Also Known As Other well-known names for the pattern, if any.
- Motivation: A scenario that illustrates a design problem and how the class and object structures in the pattern solve the problem. The system will help you understand the more abstract description of the following pattern.
- Applicability: What are the situations where the design pattern can be applied? What are examples of poor designs that the pattern can address? How can you recognise these situations?
- Structure: A graphical representation of the classes in the pattern using a notation based on the Object Modeling Technique (OMT) [RBP+91]. We also use interaction diagrams [JCJO92, Boo94] to illustrate sequences of requests and collaborations between objects. Appendix B describes these notations in detail.
- Participants: The classes and/or objects participating in the design pattern and their responsibilities.
- Collaborations: How the participants collaborate to carry out their responsibilities.
- Consequences: How does the pattern support its objectives? What are the trade-offs and results of using the pattern? What aspect of system structure does it let you vary independently?
- Implementation: What pitfalls, hints, or techniques should you know when implementing the pattern? Are there language-specific issues?
- Sample Code: Fragments that illustrate how you might implement the pattern in C++ or Smalltalk.
- Known Uses: Examples of the pattern found in real systems. We include at least two examples from different domains.
- Related Patterns: What design patterns are closely associated with this one? What are the essential differences? Which other patterns should this one be used?
How Design Patterns Solve Design Problems
Design patterns solve design problems by finding appropriate objects; design patterns help you identify less-obvious abstractions and the objects that can capture them. Design patterns also determine object granularity; Design patterns describe how to represent complete subsystems as objects and support vast numbers of objects at the finest granularities. Design patterns help you define interfaces by identifying their essential elements and the kinds of data sent across an interface. A design pattern might also tell you what not to put in the interface.
Favourite Quote of the chapter: “Creational class patterns defer some part of object creation to subclasses, while Creational object patterns defer it to another object. The Structural class patterns use inheritance to compose classes, while the Structural object patterns describe ways to assemble objects. The Behavioral class patterns use inheritance to describe algorithms and flow of control, whereas the Behavioral object patterns describe how a group of objects cooperate to perform a task that no single object can carry out
alone.”
Chapter two: A Case Study: Design a Document Editor
Document Structure
A document arranges essential graphical elements such as characters, lines, polygons, and other shapes. These elements capture the total information content of the document. Yet, these elements are not viewed in graphical terms but in terms of the document’s physical structure—lines, columns, figures, tables, and other substructures. In turn, these substructures have their substructures, and so on. Lexi’s user interface should let users manipulate these substructures directly. For example, a user should be able to treat a diagram as a unit rather than as a collection of individual graphical primitives. The user should be able to refer to a table as a whole, not as a shapeless mass of text and graphics. That helps make the interface simple and intuitive.
Formatting
After finding a way to represent the document’s physical structure, you must figure out how to construct a particular physical structure. One that corresponds to a properly formatted document. Representation and formatting are distinct: The ability to capture the document’s physical structure doesn’t tell us how to arrive at a particular system. This responsibility rests primarily on Lexi.
Encapsulating the Formatting Algorithm: With all its constraints and details, the formatting process isn’t easy to automate. There are many approaches to the problem, and people have developed various formatting algorithms with different strengths and weaknesses. Because Lexi is a What You See Is What You Get editor, a necessary trade-off to consider is the balance between formatting quality and formatting speed. Because formatting algorithms tend to be complex, keeping them well-contained or—better yet—completely independent of the document structure is desirable. Ideally, we could add a new kind of Glyph subclass without regard to the formatting algorithm. Conversely, adding a new formatting algorithm shouldn’t require modifying existing glyphs.
Strategy Pattern: Encapsulating an algorithm in an object is the intent of the Strategy pattern. The key participants in the pattern are Strategy objects which encapsulate different algorithms and the context in which they operate. Compositors are strategies; they encapsulate different formatting algorithms. Composition is the context for a compositor strategy. The key to applying the Strategy pattern is designing interfaces for the system and its context that are general enough to support a range of algorithms.
Embellishing the User Interface
Consider two embellishments in Lexi’s user interface. The first adds a border around the text editing area to demarcate the page of text. The second adds scroll bars that let the user view different page parts. To make adding and removing these embellishments easy (especially at run-time), you shouldn’t use inheritance to add them to the user interface. We achieve the most flexibility if other user interface objects don’t even know the embellishments are there. That will let us add and remove the embellishments without changing other classes.
From a programming point of view, embellishing the user interface involves extending existing code. Using inheritance to do such extension precludes rearranging embellishments at run-time, but an equally serious problem is the explosion of classes resulting from an inheritance-based approach.
Decorator Pattern: The Decorator (196) pattern captures class and object relationships that support embellishment by transparent enclosure. The term “embellishment” has a broader meaning than we’ve considered here. In the Decorator pattern, embellishment refers to anything that adds responsibilities to an object.
Favourite Quote: “The choice of internal representation for the document affects nearly every aspect of Lexi’s design.”
Chapter three: Creational Patterns
Creational design patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented. A class creational pattern uses inheritance to vary the instantiated class, whereas a creational object pattern will delegate instantiation to another object.
Creational patterns become essential as systems depend more on object composition than class inheritance. As that happens, the emphasis shifts away from hardcoding a fixed set of behaviours toward defining a smaller group of fundamental behaviours that can be composed of more complex ones. Thus creating objects with specific behaviours requires more than simply instantiating a class.
There are two falling-back themes in these patterns. First, they all encapsulate knowledge about which concrete classes the system uses. Second, they hide how instances of these classes are created and put together. All the system at large knows about the objects is their interfaces defined by abstract classes. Creative patterns can sometimes be competitors. That’s to say, there are instances when either Prototype or Abstract Factory could be used profitably. At other times they are complementary: The builder can use one of the different patterns to implement which components get built. The prototype can use Singleton in its implementation. Creative patterns include;
Abstract Factory: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Builder: Separate the construction of a complex object from its representation so that the same construction process can create different expressions.
Factory Method: Define an interface for creating an object but let subclasses decide which class to instantiate. Factory Method allows a class to defer instantiation to subclasses.
Prototype: Specify the kinds of objects to create using a prototypical instance, and create new things by copying this prototype.
Singleton: Ensure a class only has one instance and provide a global point of access to it.
Favourite Quote of the chapter: “There are two recurring themes in these patterns. First, they all encapsulate knowledge about which concrete classes the system uses. Second, they hide how instances of these classes are created and put together.”
Chapter four: Structural Patterns
Structural patterns deal with how classes and objects are composed to form larger structures. Structural class patterns use inheritance to collect interfaces or implementations. As a simple example, consider how
multiple estates mix two or more classes into one. The result is a class that combines the properties of its parent classes. This pattern is handy for making independently developed class libraries work together. Another example is the class form of the Adapter pattern. Generally, an adapter makes one interface conform to another, providing a uniform abstraction of different interfaces. A class adapter accomplishes this by inheriting privately from an adaptee class. The adapter then expresses its interface in terms of the adaptees. Structural object patterns describe ways to compose objects to realize new functionality. The added flexibility of object composition comes from the ability to change the document at run-time, which is impossible with static class composition. A structural object pattern describes building a class hierarchy of classes for two kinds of objects: primitive and composite. The composite objects let you compose primitive and other composite objects into arbitrarily complex structures. Below are the structural patterns you need to know;
Adapter: Convert the interface of a class into another interface clients expect. The adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.
Bridge: Decouple an abstraction from its implementation so that the two can vary independently.
Composite: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Decorator: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Facade: Provide a unified interface to a set of interfaces in a subsystem. A facade defines a higher-level interface that makes the subsystem easier to use.
Flyweight: Use sharing to support large numbers of fine-grained objects efficiently.
Proxy: Provide a surrogate or placeholder for another object to control access to it.
Favourite Quote: “Rather than composing interfaces or implementations, structural object patterns describe ways to compose objects to realize new functionality.”
Chapter five: Behavioral Patterns
Behavioural patterns are concerned with algorithms and the assignment of responsibilities between objects.
Behavioural patterns describe not just patterns of objects or classes but also the communication patterns between them. These patterns characterize complex control flow that’s difficult to follow at run-time. They shift your focus away from the control flow to let you concentrate just on how objects are interconnected. Behavioural class patterns use inheritance to distribute behaviour between classes. This chapter includes two such patterns. Template Method (360) is the more straightforward and more common of the two. A template method is an abstract definition of an algorithm. It defines the algorithm step by step. Each step invokes either an abstract operation or a primitive operation. A subclass fleshes out the algorithm by defining the abstract operations. The other behavioural class pattern is Interpreter (274), which represents grammar as a class hierarchy and implements an interpreter as an operation on instances of these classes. Behavioural object patterns use object composition rather than inheritance. Some describe how a group of peer objects cooperate to perform a task that no single object can carry out. An important issue here is how peer objects know about each other. Peers could maintain explicit references to each other, increasing their coupling. In the extreme, every object would know about every other. The Mediator (305) pattern avoids this by introducing a mediator object between peers. The mediator provides the indirection needed for loose coupling. These are some of the behavioural patterns;
Interpreter: Given a language, define a representation for its grammar and an interpreter that uses the representation to interpret sentences in the language.
Command: Encapsulate a request as an object, letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
Iterator: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Mediator: Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from explicitly referring to each other and letting you vary their interaction independently.
Memento: Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
Observer: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
State: Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class.
Strategy: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Favourite of the chapter: An important issue here is how peer objects know about each other.”
HOW THIS BOOK CAN HELP SOFTWARE DEVELOPERS
The book “Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides is a seminal work in software engineering that describes 23 design patterns for object-oriented programming. is a book that can help software developers by providing them with reusable solutions to common design problems in object-oriented software development. These patterns solve common design problems and promote flexible, reusable, and maintainable code. By understanding and applying these patterns, software developers can write more efficient, modular, and extensible code that is easier to understand, modify, and maintain over time.