10. Design Patterns

One of the interesting things about software development is that when you create a software system, you are actually modeling a real-world system. For example, in the Information Technology industry, it is safe to say that IT is the business—or at least IT implements the business. To write the business software systems, the developers must thoroughly understand the business models. As a result, the developers often have the most intimate knowledge of a company’s business processes.

We have seen this concept throughout this book as it relates to our educational discussions. For example, when we discussed using inheritance to abstract out the behaviors and attributes of mammals, the model was based on the true real-life model, not a contrived model that we created for our own purposes.

Thus, when we create a mammal class, we can use it to build countless other classes, such as dogs and cats and so on, because all mammals share certain behaviors and attributes. This works when we study dogs, cats, squirrels, and other mammals because we can see patterns. These patterns allow us to inspect an animal and make the determination that it is indeed a mammal, or perhaps a reptile, which would have other patterns of behaviors and attributes.

Throughout history, humans have used patterns in many aspects of life, including engineering. These patterns go hand-in-hand with the holy grail of software development: software reuse. In this chapter, we consider design patterns, a relatively new area of software development (the seminal book on design patterns was published in 1995).

Design patterns are perhaps one of the most influential developments that have come out of the object-oriented movement in the past several years. Patterns lend themselves perfectly to the concept of reusable software development. Because object-oriented development is all about reuse, patterns and object-oriented development go hand-in-hand.

The basic concept of design patterns revolves around the principle of best practices. By best practices, we mean that when good and efficient solutions are created, these solutions are documented in a way that others can benefit from previous successes—as well as learn from the failures.

One of the most important books on object-oriented software development is Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. This book was an important milestone for the software industry and has become so entrenched in the computer science lexicon that the book’s authors have become known as the Gang of Four. In writings on object-oriented topics, you will often see the Gang of Four referred to as the GoF.

The intent of this chapter is to explain what design patterns are. (Explaining each design pattern is far beyond the scope of this book and would take more than one volume.) To accomplish this, we explore each of the three categories of design patterns (creational, structural, and behavioral) as defined by the Gang of Four and provide a concrete example of one pattern in each category.

Why Design Patterns?

The concept of design patterns did not necessarily start with the need for reusable software. In fact, the seminal work on design patterns is about constructing buildings and cities. As Christopher Alexander noted in A Pattern Language: Towns, Buildings, Construction, “Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use the solution a million times over, without ever doing it the same way twice.”

The Four Elements of a Pattern

The GoF describe a pattern as having four essential elements:

  • The pattern name is a handle we can use to describe a design problem, its solutions, and consequences in a word or two. Naming a pattern immediately increases our design vocabulary. It lets us design at a higher level of abstraction. Having a vocabulary for patterns lets us talk about them with our colleagues, in our documentation, and even to ourselves. It makes it easier to think about designs and to communicate them and their trade-off to others. Finding good names has been one of the hardest parts of developing our catalog.?

  • The problem describes when to apply the pattern. It explains the problem and its content. It might describe specific design problems, such as how to represent algorithms as objects. It might describe class or object structures that are symptomatic of an inflexible design. Sometimes the problem will include a list of conditions that must be met before it makes sense to apply the pattern.

  • The solution describes the elements that make up the design, their relationships, responsibilities, and collaborations. The solution doesn't describe a particular concrete design or implementation, because a pattern is like a template that can be applied in many situations. Instead, the pattern provides an abstract description of a design problem, and how a general arrangement of elements (classes and objects in our case) solves it.

  • The consequences are the results and trade-offs of applying the pattern. Although con-sequences are often unvoiced, when we describe design decisions, they are critical for evaluating design alternatives and for understanding the costs and benefits of the applying pattern. The consequences for software often concern space and time trade-offs. They might address language and implementation issues as well. Because reuse is often a factor in object-oriented design, the consequences of a pattern include its impact on a system's flexibility, extensibility, or portability. Listing the consequences explicitly helps you understand and evaluate them.

Smalltalk’s Model/View/Controller

For historical perspective, we need to consider the Model/View/Controller (MVC) introduced in Smalltalk (and used in other object-oriented languages). MVC is often used to illustrate the origins of design patterns. The Model/View/Controller paradigm was used to create user interfaces in Smalltalk. Smalltalk was perhaps the first popular object-oriented language.

Smalltalk

Smalltalk is the result of several great ideas that emerged from Xerox PARC. These ideas included the mouse and using a windowing environment, among others. Smalltalk is a wonderful language that provided the foundation for all the object-oriented languages that followed. One of the complaints about C++ is that it's not really object-oriented, whereas Smalltalk is. Although C++ had a larger following in the early days of OO, Smalltalk has always had a very dedicated core group of supporters. Java is a mostly OO language that embraced the C++ developer base.

Design Patterns defines the MVC components in the following manner:

The Model is the application object, the View is the screen presentation, and the Controller defines the way the user interface reacts to user input.

The problem with previous paradigms is that the Model, View, and Controller used to be lumped together in a single entity. For example, a single object would have included all three of the components. With the MVC paradigm, these three components have separate and distinct interfaces. So if you want to change the user interface of an application, you only have to change the View. Figure 10.1 illustrates what the MVC design looks like.

An illustration of MVC design.
Figure 10.1 Model/View/Controller paradigm.

Remember that much of what we have been learning about object-oriented development has to do with interfaces versus implementation. As much as possible, we want to separate the interface from the implementation. We also want to separate interface from interface as much as possible. For example, we do not want to combine multiple interfaces that do not have anything to do with one another (or the solution to the problem at hand). The MVC was one of the early pioneers in this separation of interfaces. The MVC explicitly defines the interfaces between specific components pertaining to a very common and basic programming problem—the creation of user interfaces and their connection to the business logic and data behind them.

If you follow the MVC concept and separate the user interface, business logic, and data, your system will be much more flexible and robust. For example, assume that the user interface is on a client machine, the business logic is on an application server, and the data is located on a data server. Developing your application in this way would allow you to change the way the GUI looks without having an impact on the business logic or the data. Likewise, if your business logic changes and you calculate a specific field differently, you can change the business logic without having to change the GUI. And finally, if you want to swap databases and store your data differently, you can change the way the data is stored on the data server without affecting either the GUI or the business logic. This assumes, of course, that the interfaces between the three do not change.

MVC Example

One example is that of a listbox used in a user interface. Consider a GUI that includes a list of phone numbers. The listbox is the view, the phone list is the model, and the controller is the logic that binds the listbox to the phone list.

MVC Drawbacks

Although the MVC is a great design, it can be somewhat complex in that a lot of attention must be paid to the upfront design. This is a problem with object-oriented design in general—there is a fine line between a good design and a cumbersome design. The question remains: How much complexity should you build into the system with regard to a complete design?

Types of Design Patterns

Design Patterns features 23 patterns grouped into the three categories that follow. Most of the examples are written in C++, with some written in Smalltalk. The time of the book’s publication is indicative of the use of C++ and Smalltalk. The publication date of 1995 was right at the cusp of the Internet revolution and the corresponding popularity of the Java programming language. After the benefit of design patterns became apparent, many other books rushed in to fill the newly created market.

In any event, the actual language used is irrelevant. Design Patterns is inherently a design book, and the patterns can be implemented in any number of languages. The authors of the book divided the patterns into three categories:

  • Creational patterns create objects for you, rather than having you instantiate objects directly. This gives your program more flexibility in deciding which objects need to be created for a given case.

  • Structural patterns help you compose groups of objects into larger structures, such as complex user interfaces or accounting data.

  • Behavioral patterns help you define the communication between objects in your system and how the flow is controlled in a complex program.

The following sections discuss one example from each of these categories to provide a flavor of design patterns. For a comprehensive list and description of individual design patterns, refer to the books listed at the end of this chapter.

Creational Patterns

The creational patterns consist of the following categories:

  • Abstract factory

  • Builder

  • Factory method

  • Prototype

  • Singleton

As stated earlier, the scope of this chapter is to describe what a design pattern is—not to describe each and every pattern in the GoF book. Thus, we will cover a single pattern in each category. With this in mind, let’s consider an example of a creational pattern and look at the factory pattern.

The Factory Method Design Pattern

Creating, or instantiating, objects may well be one of the most fundamental concepts in object oriented programming. It goes without saying that you can’t use an object unless that object exists. When writing code, the most obvious way to instantiate an object is to use the new keyword.

To illustrate, let’s revisit the Shape example used throughout this book. Here we have the familiar parent class Shape, which is abstract, and the child class Circle, which is the concrete implementation. We instantiate a Circle class in the usual way by employing the new keyword:

abstract class Shape {

}

class Circle extends Shape {

}

Circle circle = new Circle();

Although this code certainly works, there may be many other places in your code where you need to instantiate a Circle, or any other Shape for that matter. In many cases, you will have specific object creation parameters that need to be handled each time you create a Shape.

As a result, any time you change the way objects are created, the code must be changed in every location where a Shape object is instantiated. The code is highly coupled because a change in one location necessitates code changes in potentially many other locations. Another problem with this approach is that it exposes the object creation logic to the programmers using the classes.

To remedy these situations, we can implement a factory method. In short, the factory method is responsible for encapsulating all instantiation so that it is uniform across the implementation. You use the factory to instantiate, and the factory is responsible for instantiating properly.

Factory Method Pattern

The fundamental intent of the factory method pattern is to create objects without having to specify the exact class—in effect, using interfaces to create new types of objects.

To illustrate how to implement a factory pattern, let’s create a factory for the Shape class example. The class diagram in Figure 10.2 helps visualize how the various classes in the example interact.

The shape factory diagram.
Figure 10.2 Creating a factory for the Shape class.

In some ways, you can think of a factory as a wrapper. Consider the fact that there may be some significant logic involved in instantiating an object and you don’t want the programmer (user) to be concerned with this logic. It is almost like the concept of an accessor method (getters and setters) when the retrieval of a value is inside some logic (like when a password is required). Using a factory method is useful when you don’t know ahead of time which specific class you might need. For example, you may know that a shape is required, but you don’t know the specific shape (at least not yet). With this in mind, all possible classes must be in the same hierarchy; that is, all the classes in this example must be a subclass of Shape. In fact, a factory is used precisely because you don’t know what you need, allowing you to add some of the classes later. If you knew what you needed, you could simply “inject” the instance via a constructor or a setter method.

Basically, this is the definition of polymorphism.

We create an enum to contain the types of shapes. In this case, we will define CIRCLE, SQUARE, and TRIANGLE.

enum ShapeType {
    CIRCLE, SQUARE, TRIANGLE
}

We define the Shape class as abstract with just a constructor and an abstract method called generate().

abstract class Shape {

    private ShapeType sType = null;

    public Shape(ShapeType sType) {
        this.sType = sType;
    }

    // Generate the shape
    protected abstract void generate();

}

The child classes, CIRCLE, SQUARE, and TRIANGLE, extend the Shape class, identify themselves, and provide the concrete implementation of the generate() method.

class Circle extends Shape {

    Circle() {
        super(ShapeType.CIRCLE);
        generate();
    }

    @Override
    protected void generate() {
        System.out.println("Generating a Circle");
    }
}

class Square extends Shape {

    Square() {
        super(ShapeType.SQUARE);
        generate();
    }

    @Override
    protected void generate() {
        System.out.println("Generating a Square");
    }
}

class Triangle extends Shape {

    Triangle() {
        super(ShapeType.TRIANGLE);
        generate();
    }

    @Override
    protected void generate() {
        System.out.println("Generating a Triangle");
    }
}

The ShapeFactory class, as the name implies, is the actual factory. Focus on the generate() method. While a Factory provides many advantages, note that the generate() method is the only location within the application that actually instantiates a Shape.

class ShapeFactory {
    public static Shape generateShape(ShapeType sType) {
        Shape shape = null;
        switch (sType) {

        case CIRCLE:
            shape = new Circle();
            break;

        case SQUARE:
            shape = new Square();
            break;

        case TRIANGLE:
            shape = new Triangle();
            break;

        default:
            // throw an exception
            break;
        }
        return shape;
    }
}

The traditional approach to instantiating these individual objects is to have the programmer directly instantiate the objects using the new keyword as follows:

public class TestFactoryPattern {
    public static void main(String[] args) {

        Circle circle = new Circle();
        Square square = new Square();
        Triangle triangle = new Triangle();

    }
}

However, properly using the factory requires that the programmer use the ShapeFactory class to obtain any Shape object:

public class TestFactoryPattern {
    public static void main(String[] args) {

        ShapeFactory.generateShape(ShapeType.CIRCLE);
        ShapeFactory.generateShape(ShapeType.SQUARE);
        ShapeFactory.generateShape(ShapeType.TRIANGLE);

    }
}

Structural Patterns

Structural patterns are used to create larger structures from groups of objects. The following seven design patterns are members of the structural category:

  • Adapter

  • Bridge

  • Composite

  • Decorator

  • Facade

  • Flyweight

  • Proxy

As an example from the structural category, let’s take a look at the adapter pattern. The adapter pattern is also one of the most important design patterns. This pattern is a good example of how the implementation and interface are separated.

The Adapter Design Pattern

The adapter pattern is a way for you to create a different interface for a class that already exists. The adapter pattern basically provides a class wrapper. In other words, you create a new class that incorporates (wraps) the functionality of an existing class with a new and—ideally—better interface. A simple example of a wrapper is the Java class Integer. The Integer class wraps a single Integer value inside it. You might wonder why you would bother to do this. Remember that in an object-oriented system, everything is an object. In Java, primitives, such as ints, floats, and so on, are not objects. When you need to perform functions on these primitives, such as conversions, you need to treat them as objects. Thus, you create a wrapper object and “wrap” the primitive inside it. Thus, you can take a primitive like the following:

int myInt = 10;

and wrap it in an Integer object:

Integer myIntWrapper = new Integer (myInt);

Now you can do a conversion, so you can treat it as a string:

String myString = myIntWrapper.toString();

This wrapper enables you to treat the original integer as an object, thus providing all the advantages of an object.

As for the adapter pattern itself, consider the example of a mail tool interface. Let’s assume you have purchased some code that provides all the functionality you need to implement a mail client. This tool provides everything you want in a mail client, except you would like to change the interface slightly. In fact, all you want to do is change the API to retrieve your mail.

The following class provides a very simple example of a mail client for this example:

package MailTool;
public class MailTool {
   public MailTool () {
   }
   public int retrieveMail() {

    System.out.println ("You've Got Mail");

         return 0;
   }
}

When you invoke the retrieveMail() method, your mail is presented with the very original greeting “You’ve Got Mail.” Now let’s suppose you want to change the interface in all your company’s clients from retrieveMail() to getMail(). You can create an interface to enforce this:

package MailTool;
interface MailInterface {
   int getMail();
}

You can now create your own mail tool that wraps the original tool and provide your own interface:

package MailTool;
class MyMailTool implements MailInterface {
   private MailTool yourMailTool;
   public MyMailTool () {
    yourMailTool= new MailTool();
        setYourMailTool(yourMailTool);
   }
   public int getMail() {
      return getYourMailTool().retrieveMail();
   }
   public MailTool getYourMailTool() {
      return yourMailTool ;
   }
   public void setYourMailTool(MailTool newYourMailTool) {
      yourMailTool = newYourMailTool;
   }
}

Inside this class, you create an instance of the original mail tool that you want to retrofit. This class implements MailInterface, which will force you to implement a getMail() method. Inside this method, you literally invoke the retrieveMail() method of the original mail tool.

To use your new class, you instantiate your new mail tool and invoke the getMail() method.

package MailTool;
public class Adapter
{
  public static void main(String[] args)
  {
    MyMailTool myMailTool = new MyMailTool();

    myMailTool.getMail();

  }
}

When you invoke the getMail() method, you are using this new interface to invoke the retrieveMail() method from the original tool. This is a very simple example; however, by creating this wrapper, you can enhance the interface and add your own functionality to the original class.

The concept of an adapter is quite simple, but you can create new and powerful interfaces using this pattern.

Behavioral Patterns

The behavioral patterns consist of the following categories:

  • Chain of response

  • Command

  • Interpreter

  • Iterator

  • Mediator

  • Memento

  • Observer

  • State

  • Strategy

  • Template method

  • Visitor

As an example from the behavioral category, let’s take a look at the iterator pattern. This is one of the most commonly used patterns and is implemented by several programming languages.

The Iterator Design Pattern

Iterators provide a standard mechanism for traversing a collection, such as a vector. Functionality must be provided so that each item of the collection can be accessed one at a time. The iterator pattern provides information hiding, keeping the internal structure of the collection secure. The iterator pattern also stipulates that more than one iterator can be created without interfering with each other. Java provides its own implementation of an iterator. The following code creates a vector and then inserts a number of strings into it:

package Iterator;

import java.util.*;
public class Iterator {
    public static void main(String args[]) {

        // Instantiate an ArrayList.
        ArrayList<String> names = new ArrayList();

        // Add values to the ArrayList
        names.add(new String("Joe"));
        names.add(new String("Mary"));
        names.add(new String("Bob"));
        names.add(new String("Sue"));

        //Now Iterate through the names
        System.out.println("Names:");
        iterate(names);
    }

    private static void iterate(ArrayList<String> arl) {
        for(String listItem : arl) {
                    System.out.println(listItem.toString());
                }
    }
}

Then we create an enumeration so that we can iterate through it. The method iterate() is provided to perform the iteration functionality. In this method, we use the Java enumeration method hasMoreElements(), which traverses the vector and lists all the names.

Antipatterns

Although a design pattern evolves from experiences in a positive manner, antipatterns can be thought of as collections of experiences that have gone awry. It is well documented that most software projects are ultimately deemed unsuccessful. In fact, as indicated in the article “Creating Chaos” by Johnny Johnson, fully one-third of all projects are cancelled outright. It would seem obvious that many of these failures are caused by poor design decisions.

The term antipattern derives from the fact that design patterns are created to proactively solve a specific type of problem. An antipattern, on the other hand, is a reaction to a problem and is gleaned from bad experiences. In short, whereas design patterns are based on solid design practices, antipatterns can be thought of as practices to avoid.

In the November 1995 C++ Report, Andrew Koenig described two facets of antipatterns:

  • Those that describe a bad solution to a problem, which result in a bad situation.

  • Those that describe how to get out of a bad situation and how to proceed from there to a good solution.

Many people believe that antipatterns are more useful than design patterns. This is because antipatterns are designed to solve problems that have already occurred. This boils down to the concept of root-cause analysis. A study can be conducted with data that might indicate why the original design, perhaps an actual design pattern, did not succeed. It might be said that antipatterns emerge from the failure of previous solutions. Thus, antipatterns have the benefit of hindsight.

For example, in his article “Reuse Patterns and Antipatterns,” Scott Ambler identifies a pattern called a robust artifact and defines it as follows:

An item that is well-documented, built to meet general needs instead of project-specific needs, thoroughly tested, and has several examples to show how to work with it. Items with these qualities are much more likely to be reused than items without them. A Robust Artifact is an item that is easy to understand and work with.

However, there are certainly many situations when a solution is declared reusable and then no one ever reuses it. Thus, to illustrate an antipattern, he writes:

Someone other than the original developer must review a Reuseless Artifact to determine whether or not anyone might be interested in it. If so, the artifact must be reworked to become a Robust Artifact.

Thus, antipatterns lead to the revision of existing designs, and the continuous refactoring of those designs until a workable solution is found.

Some Good Examples of Antipatterns

  • Singleton

  • Service locator

  • Magic strings/magic numbers

  • Interface bloat

  • Coding by exception

  • Error hiding/swallowing

Conclusion

In this chapter, we explored the concept of design patterns. Patterns are part of everyday life, and this is just the way you should be thinking about object-oriented designs. As with many things pertaining to information technology, the roots for solutions are founded in real-life situations.

Although this chapter covered design patterns only briefly, you should explore this topic in greater detail by picking up one of the books referenced at the end of this chapter.

References

Alexander, Christopher, et al. 1977. A Pattern Language: Towns, Buildings, Construction. Cambridge, UK: Oxford University Press.

Ambler, Scott. “Reuse Patterns and Antipatterns.” 2000 Software Development Magazine.

Gamma, Erich, et al. 1995. Design Patterns: Elements of Reusable Object-Oriented Software. Boston, MA: Addison-Wesley.

Grand, Mark. 2002. Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, Second Edition, volume 1. Hoboken, NJ: Wiley.

Jaworski, Jamie. 1999. Java 2 Platform Unleashed. Indianapolis, IN: Sams Publishing.

Johnson, Johnny. “Creating Chaos.” American Programmer, July 1995.

Larman, Craig. 2004. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development, Third Edition. Hoboken, NJ: Wiley.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.238.161