Home Company Services Experience Process Articles FAQs Contact Us

Thinking in The Way of Design Patterns


 

Design patterns are reuse of designs, just like components are reuse of implementations. They are repeatable solutions to certain design problems. Literally speaking, design patterns can be good or bad, right or wrong. Since most people have no interests in studying bad design patterns, here we only talk about good patterns – those built upon good software practice and proven by real results. Using design patterns is important because most failures come from difficulties and mistakes in applying good practices, not from lack of principles.

 

Realize it or not, pattern recognition is the fundamental and most effective way human beings comprehend the world around them - accumulate knowledge and disseminate knowledge. No scientific principles can be justified until they can be repeated in experiments. Since childhood we have been trained in finding patterns. Imagine you read somebody’s code and try to understand what the code does. You follow the author’s thread of thinking (line after line or back and forth between files) until you say “I think I know what he wants to do but I am not sure I get it right or what he did is right.” or “Ah ha! I know what he is doing and I saw that pattern before” or “Ah ha! I know what he is doing because he is using the Observer pattern”. Which answer would you like to end up with? The different answers suggest that design patterns, after formalized and widely accepted, can help us understand the dynamic structure of code quickly and accurately. Obviously, when you are in the design phase, design patterns can help you reach the right solution more efficiently. In short, design patterns facilitate understanding and communications.

 

Software design is complicated because you have many factors to consider and many tradeoffs to make. The good news is that design patterns have already been there and come up with the “best” solutions. Just use them and you can be lazy and productive at the same time. Design patterns are about design reuse. Back to the old school days, I used to write many small programs to process the data I collected from the experiments, until one day I knew about MatLab. It has all the functions I needed built-in and easy to use so that I could focus on my research. The better news about design patterns is that they are free. They are not locked in by patents. Are we living in great times? May not be that great. Our parents told us that the most expensive thing is the free thing. Same are design patterns – they cannot be used literally; you must learn how to think in the way of design patterns.

 

The key to effectively apply design patterns is to understand the context - the problems to solve and the goals to achieve. Therefore patterns shall be grouped by usages instead of structures, and the relationship among patterns including similarities or differences shall be investigated under the light of usages. The pattern’s way of thinking is to analyze the requirements, conduct the abstraction and map the results to pre-defined pattern context. After a particular context is laid out, the pattern is simply one-to-one mapped. The analog is to think about design patterns as a box of tools for design purposes (verse implementation patterns or even programming tricks). When you use a tool, you do not really care how the tool performs and what it is made of. As long as you know the purpose of the tool, and are able to link that purpose to your goal, you are all set. In the case that multiple patterns can be used to solve one problem, context needs to be more specific and complete so that only one pattern can be determined.

 

Design patterns are ways of thinking. They are about “common senses”. For example, the Mediator Pattern is really about the hub style connection verse point-to-point connection, which we see again and again in our daily life - post office, airport, central office, etc. To think in the ways of design patters, first be aware of the basic design tradeoffs – speed verse space, indirection verse flexibility, and specificity verse uniformity, then understand the basic principles: (1) program to an interface (type) not to an implementation (class), (2) prefer object composition to class inheritance, and finally be familiar with the common design techniques. The best way to learn design patterns is to consciously apply the principles and techniques to your day-by-day design work, get your own solutions, and compare your solutions with the design patterns. In that way, you may come up with your own patterns, or your solutions are deviations of the so-called design patterns. That is fine because design patterns are meant to be alive and extendable. Just be careful when you communicate with other designers.

 

Design techniques are the common themes behind good designs. Design patterns are the reusable solutions – the results of apply certain technique to a particular problem. From the GoF design patterns, we can identify the following design techniques:

-         Promotion. Promote global variable to singleton, call back function to command, pointer to iterator, and hook-up method to template method.

-         Encapsulation. Encapsulate a subsystem with common interfaces (façade, abstract factory), and hide proprietary interfaces behind compatible interfaces (bridge, adapter).

-         Delegation. Separate the entity that receives the request from the entity that handles the request (proxy), and use broker to simplify the interdependencies among participants (mediator).

-         Navigation. Treat each node of the hierarchy uniformly so that all the nodes can be traversed in the same way (composite, decorator and interpreter).

-         Specialization. Define specialized and sharable classes so that complexity of the whole system or number of classes can be reduced (memento, fly weight).

-         Trojan horse. Change the behavior of a class or a subsystem by changing the hidden characteristics not the outside interfaces (state, strategy and template method).

-         Assembly line. The integration rules and assembly steps are fixed, but each node or link can be added or replaced so that the final products are different (builder, decorator and chain of responsibility).

 

Design patterns can be applied to different levels of design. One of the most famous or popular system architecture patterns is Model-View-Controller (MVC). Model is the server application that contains, manages and manipulates the data. View is the client application that presents the data to the users. Model focuses on how to store, retrieve and process the data based on business rules, application logics or particular algorithms. View focuses on the various way of presenting the data properly. The data in Model and the perception of data in View must be kept in synch which usually means any changes in Model data must be reflected in View properly and promptly. Model is the only place data are persisted; however, View may choose to cache the data locally, but only Model has the master copy. Any changes in Model data must be notified to the View but how to actually present the data is up to the View. This Model and View communication is commonly done through the publish/subscribe pattern. That is, View subscribes certain data to the Model. When Model detects the changes on that data, it will publish the changes to the subscribed View. This separation of data processing and data presentation greatly improves the flexibility and reuse of the software components. The basic idea is that data processing and data presentation are two different things; when they become complicated enough, they shall be decoupled and treated differently. Controller is the client side component that encapsulates the way the client application reacts to user inputs; that is, the interactive part providing user experience.

 

The “Gang-of-Four” design patterns are the classical and most widely adopted component level design patterns. They are good examples of applying design patterns by understanding the context.

 

Abstract Factory

Don’t instantiate a class directly (such as new). Instead, write a method to create the object so that all the object creation related customizations can be wrapped up nicely in one method. Any changes to the ways of object creation can be hidden in that method. Group all such methods belong to one subsystem in one interface definition class – the AbstractFactory class, and put each set of implementation of such interface class under the corresponding concrete subclass. The user who is responsible for creating the objects of a certain subsystem only needs to define a variable that is of type AbstractFactory and then invoke the proper object creation method. By plugging in different implementation of AbstractFactory at runtime, the user can create a totally different set of objects for the subsystem and totally change the behavior of the subsystem without modifying the use code. This is equivalent to polymorphism at subsystem level. This is a special case of Visitor Pattern.

 

Builder

Sometimes the process of creating a subsystem is fixed, e.g., the component initialization, the sequence, and the steps involved. However, there are certain steps that are highly specialized and complicated or need customization. Another way of thinking is for the Abstract Factory Pattern, the object creation methods of AbstractFactory become very complex and only part of the logics inside the methods can be reused. To separate the reusable parts, promote the object creation method to an object, the Director object, and group different parts of the creation method as different Builder objects. Since nothing will change at the creation method level, the type of the Director object is defined as a concrete class; that is, the combination of both interface (type) and implementation (class). However, each Builder object can be highly specialized; therefore it is defined as an interface, the Builder class, and implemented in the corresponding concreted class. The Director object aggregates a set of Builder objects through references to the Builder interfaces. Different implementation of Builder can be plugged into at runtime to create different objects without changing the code of Director and other Builders. This is a special case of Mediator Pattern, where Director is the Mediator and Builder is the Colleague.

 

Factory Method

In some cases, part of a method creates an object and then the following behavior and the final result of the method depend on the object being created. To isolate the different ways of creating the object, group the object creation code in its own method – the Factory Method, and provide different implementation of the method in corresponding subclasses. The behavior of the original method in the parent class depends on the object being created, which actually determined by which subclass will be plugged in – each subclass has its own way of implementing the Factory Method. No changes are made to the parent class at all. This is a special case of Template Method Pattern. Very commonly the Factory Method is implemented using Prototype Pattern; that is, let the class instantiate itself through a static method. Factory Method uses the less preferred inherence style of reuse. However, since the interface is well defined and limited (only for object creation), this pattern uses the benefits of class inheritance without exposing to the disadvantages.

 

Prototype

This is a special case of Factory Method Pattern – put all the logics of creating an object into the target class itself so that the creator needs absolutely no knowledge of how to create the object (think about the reuse through library or framework). Since the Prototype is defined as an interface, no matter what the target class is, the creator simply let the class instantiate itself. There is no change on either side. There are two flavors of Prototype Pattern. One is to use a static method to create an instance of the class. The other is to use a non-static method to make a copy of the invoked object.

 

Singleton

To guarantee that there is only one instance of the class within a system, usually an object-oriented style of global variable. This can be extended to a controlled number of instances, such as Smart Pointer.

 

Adapter

Sometimes an object has a fixed interface, such as objects belong to libraries from third-party vendors or components built by other departments. Meanwhile, the caller does not want to change its existing way of invoking objects. To solve this interface mismatch problem, we can define a new interface class Target that has all the interfaces the caller wants, and create a concrete class Adapter that implements the Target interface using the existing class Adaptee. There are two ways of reusing the implementation of Adaptee. One is the static reuse; that is, make Adapter the private subclass of Adaptee. The other is the dynamic binding; that is, define a reference of Adaptee inside Adapter so that Adaptor can easily delegate the functions to Adaptee.

 

Bridge

Decouple the interface definition (type) from implementation (class) so that each of them can change independently. The simplest way of separating interface from implementation is to make the implementation class the subclass of the interface class. However, as the problem with any inheritance, inheritance breaks encapsulation. When a new interface function is added to the interface class, all the implementation classes have to change to accommodate that new interface. On the other hand, when a new function added to the implementation class, the interface class must add that function so that caller can invoke the new function. Plus, when implementation classes evolve into to multiple layers of hierarchy, the interface must contain all the functions defined in all the subclasses, even though not all the functions are proper for certain callers (the popular kitchen sink problem). The fundamental issue is: the type and subtypes shall be designed solely based on the callers’ requirements. They shall be designed freely without worrying about the implementation. Meanwhile, implementation class hierarchy shall be designed based on development considerations, totally hidden from the callers. The Bridge Pattern breaks one class hierarchy into two independent hierarchies - the interface class hierarchy and the implementation hierarchy, and only the root-level interface class contains the reference to the root-level implementation class. This pattern is very commonly seen in system partition, with the variations that the root-level classes can refer to each other (the protocol stack), or classes at the same level can refer to each other (the Model/View).

 

Composite

This pattern gives a unified way of modeling a tree-style class hierarchy for object representation, functional invocation and hierarchical traversing. All the classes in the hierarchy define the same interface Component, which has two types of functions: child manipulating (add, remove, get) and domain specific operations. One of the variations is to add a third type of functions – parent referencing – so that each child can easily find its parent without traversing every time from the root. All the implementation classes belong to two groups: the leaf object (without child) and the non-leaf object (with child). Only the leaf object actually implements the domain specific operations. Non-leaf object simply delegates the domain specific operations to all its child objects. Another variation is that non-leaf object can implement its own domain specific operations first, and then delegate others to all its children.

 

Decorator

We have seen the cases that one object can delegate part of its responsibilities to other objects (dynamic binding). What if we don’t know how many objects we need to delegate to and what those objects are? Or what if we want to plug in different objects at runtime without the tedious referencing to each and every of those objects? The answer is to borrow the Composite Pattern. Make all the classes inherit the same interface Component, and make the one object you absolutely need to call the leaf object, the ConcreteComponent. Then make all the objects to be delegated to the non-leaf objects, the Decorator. Since all objects inherit the same interface, it is very easy to add or remove non-leaf objects at runtime. The end result is a chain of Decorator objects followed by a ConcreteComponent object at the end. Caller invokes the Decorator object at the beginning of the chain, which does some of its own things, then delegates to the next Decorator, which repeats the same pattern of invocation until the last Decorator delegates to the ConcreteComponent, and the chain of invocations stops. Note that each Decorator can add certain operations after its invocation to the next Decorator returns. In that case, all the after-invocation operations will be executed after the ConcreteComponent finishes, in a reverse order of the Decorator invocation, which is typical for system cleanup. Since non-leaf objects define their own unified interface, the Decorator, the dynamic add and remove of Decorator objects are much easier.

 

Façade

Most of the times you don’t want to expose all the objects inside a subsystem to the outside world. It is not necessary, and it is hard to isolate user from internal changes. With the same principle that interface and implementation shall be separated and only interface shall be exposed to outside users, Façade is the interface of a subsystem. It is not simply the aggregation of all the interfaces of all the internal objects. It shall be designed based on user requirements, then somehow mapped to the interfaces of internal objects. For different users and different usages, one subsystem may have more than one Façade, and some Façades may be subclasses of others (kind of Bridge Pattern at the subsystem level). To avoid proliferation of interfaces in the Façade, the subsystem shall not be too large. Subsystem can be further decomposed to nested subsystems, and each of them can have its own Façade.

 

Flyweight

Once in a while we deal with a large (or huge) amount of objects. Note that the number of classes may not be large, but the number of instances of them can be. Quite often we see that those objects are similar to one another: they are the instances of the same class, or limited number of classes, but since the states or the combination of states of those classes can be many, we end up with large number of objects. Obviously the solution of reducing the amount of objects is to reduce the number of states defined in the class. Flyweight Pattern does exactly that. It separates object states into intrinsic and extrinsic states. There is no strict definition of what state is intrinsic and what is extrinsic. Intrinsic state tends to be the primary characteristics and independent of the context. For example, ASCII code of a character does not change if the character appears in different documents, on different pages or at different columns. Extrinsic state tends to be the secondary characteristics and depending on the context. For example, the font and color of a character can be different based on its location in the document. The idea of Flyweight is to separate intrinsic state from the extrinsic state, then let the object keep the intrinsic state and let the context keep the extrinsic state. For any action that works on the object, the extrinsic state is derived or calculated from the context (by caller or by the object itself), and then combined with the intrinsic state. In this way, the number of objects can be greatly reduced.

 

Proxy

This is simply an example of delegation, with the restriction that the object delegating the responsibility and the object being delegated to use the same interface. However, this simple technique is very useful in the situation of distributed computing. The RealSubject object can stay on the server which is remote from the user, and the Proxy object can reside locally with the user. The Proxy object handles and hides all the communications with the RealSubject object, e.g., using CORBA. Another use of the Proxy Pattern is lazy fetch. Proxy object can cache certain data locally and only transfer some heavy data such as image from RealSubject when they are needed.

 

Chain of Responsibility

Similar to the Decorator Pattern, this is another way of decomposing responsibilities and adding new functions dynamically. All the objects that can handle the request implement the Handler interface and connect with one another like a chain. The caller starts with the beginning Handler object of the chain, which may do something, and then pass the request to its successor. This pattern continues until the last object on the chain is invoked. Compared with the Decorator Pattern, there are several differences. First, there is no ConcreteComponent at the end of the chain therefore the request could pass the chain and none of the Handler objects wants to handle it. Second, all the Handler objects define the same interface, while Decorator objects have different interfaces than ConcreteComponent. Third, even though Decorator objects usually set up a chain-style structure, nothing preventing them from grouping in a tree structure. Fourth, Decorator objects are added purposely, which means if you don’t want certain functions, you don’t add the corresponding Decorator object. On the contrary, Handler objects are added blindly. You simply put all the potential Handler objects to the chain and let each object decide by itself whether it wants to handle certain request or not.

 

Command

This is the object-oriented version of call back function. The Command object encapsulates the request and the reference to the call back object, the Receiver. The client sends the Invoker the request by registering the Command object to the Invoker. Invoker stores the Command object, and later on executes it. The Command object has all the information the Invoker needs to process the request and notify the Receiver.

 

Interpreter

This is a special case of the Composite Pattern. Parse an expression into a syntax tree and use the Composite Pattern to represent it.

 

Iterator

This is the object-oriented style of index and pointer and has been commonly used in container style libraries.

 

Mediator

When the number of objects increases, interactions among each pair of objects increase exponentially. The increasing associations among objects also violate the “decoupling” design principle. The introducing of Mediator changes the fully meshed object topology to a star-type structure and greatly decreases the complexity of object association. One object won’t associate and interact with every other object directly. Instead, it sends the request to the Mediator, which dispatches the request to the designated object. The response is also sent to the Mediator, which relays the response to the receiver object.

 

Memento

This is the object-oriented version of object internal state. Only the Originator sees the whole contents saved by Memento. Others only pass, store and handle the Memento as an envelope without accessing its contents.

 

Observer

Multiple objects can depend on one object, and when that object (the Observable) changes state, all the dependant objects want to know and change accordingly. In such case, make all the dependent objects the Observers, and register them with the Observable object. When the Observable changes state, it will notify all the Observer objects registered with it. Then it is up to the Observer objects to decide how to react.

 

State

Object has internal states and it is common that the object can behave differently based on the particular state it has. Create an object for each state, and define all the operations associated with that state. When the object receives a request, it delegates the request to the current state object, which responds in the state specific way. At the end of the operation, if a state change is necessary, the object of the new state will replace the existing one. A unified interface class is usually defined for all the state objects, in which case, the interface class contains all the operations of all the state objects. The better way is to define a state object hierarchy and define a set of interface classes accordingly. Each state object is usually implemented as a Flyweight or a Singleton object.

 

Strategy

This is similar to the State Pattern. The behavior of the object is customized by the Strategy object attached to the object rather than the internal state of the object.

 

Template Method

This is the object-oriented style of hook method. To leave part of the method modifiable to the subclass, make it contain a second method, which is a virtual function (maybe protected) of the base class and can be overridden by the subclass. So the interface and implementation of the main method remains unchanged in the base class, but its behavior changes only by overriding the embedded method in the subclass.

 

Visitor

Another way of modifying the behavior of an object (the Host) is to plug in another object, the Visitor. The contract between the Host and the Visitor is that the Host will define a method such as Accept (Visitor) and the Visitor will define a method such as DoThingsForHost ( ). Within Accept (Visitor), the Host simply invokes the Visitor’s DoThingsForHost ( ) method. So by plugging in different Visitor objects which implement the DoThingsForHost ( ) differently, the behavior of Accept (Visitor) of the Host changes without changing the code of Accept (Visitor) itself. We see real benefits of the Visitor Pattern when it is used for a group of objects, or for all the objects belong to a subsystem. Add the Accept (Visitor) method to the interface class of a group of Host objects, and write DoThingsForHost ( ) as the default implementation of Accept (Visitor). However, each Host object can provide its own version of the implementation, for example, HostA may write DoThingsForHostA ( ) in its Accept (Visitor). In this way, we can modify the behavior of the whole subsystem by simply plugging in different Visitor objects, without any changes to the objects in the subsystem. The drawback is that since there must be different implementation of Visitor objects, the interface class of Visitor objects must contain all the functions implemented by all the Visitors.

 


Jerry Zhong, March 2001.