C H A P T E R  11

Design Patterns

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 this solution a million times over, without ever doing it the same way twice.

— Christopher Alexander1

Do you reinvent the wheel each time you write code? Do you have to re-learn how to iterate through an array every time you write a program? Do you have to re-invent how to fix a dangling else in every function you write? Do you need to relearn insertion sort or binary search every time you want to use them? Of course not!

Over the time you’ve spent writing programs you’ve learned a set of idioms that you employ whenever you’re writing code. For example, if you need to iterate through all the elements of an array in Java you’re likely to do the following:

for (int i = 0; i < myArray.length; i++) {
         System.out.printf(" %d ", myArray[i]);
}

or

for (int nextElement: myArray) {
        System.out.printf(" %d ", nextElement);
}

and the code just flows out of your fingertips as you type. These code patterns are sets of rules and templates for code that you accumulate as you gain more experience writing programs.

Design patterns are the same thing – but for your design. The famous architect Christopher Alexander, in his book A Pattern Language, defined patterns for design in architecture. The same ideas carry over into software design. If you go back and read the Alexander quote at the top of this chapter, you’ll see the following three key elements in Alexander’s definition of design pattern:

___________________

1 Alexander, C., S. Ishikawa, et al. A Pattern Language: Towns, Buildings, Construction. (Oxford, UK: Oxford University Press, 1977.)

  • Recurring: The problem that evokes the design pattern must be a common one.
  • Core solution: The pattern provides a template for the solution; it tries to extract out the essence of the solution.
  • Reuse: The pattern must be easily reusable when the same problem appears in different domains.

In fact, you’ve already seen at least one design pattern so far in this book: the Model-View-Controller pattern (MVC) that we discussed in Chapter 5 is one of the earliest published examples of a software design pattern.2 The MVC design pattern is used with programs that use graphical user interfaces. It divides the program into three parts: the Model that contains the processing rules for the program, the View that presents the data and the interface to the user, and the Controller that mediates communication between the Model and the View. In a typical object-oriented implementation, each of these abstractions becomes a separate object.

The Gang of Four (Gamma, Helm, Johnson, and Vlissides), in their seminal book on design patterns, Design Patterns: Elements of Reusable Object-Oriented Software,3 define a design pattern as something that “names, abstracts, and identifies the key aspects of a common design structure that makes it useful for creating a reusable object-oriented design.” In other words, a design pattern is a named abstraction from a concrete example that represents a recurring solution to a particular, but common, problem – recurring, core solution, reuse.

But why do we need design patterns in the first place? Why can’t we just get along with the object-oriented design principles we studied in Chapter 10 and with our old favorites, abstraction, inheritance, polymorphism, and encapsulation?

Well, it turns out that design is hard. That’s why. Design for re-use is even harder. Design is also much more of an art than a science or an engineering discipline. Experienced software designers rarely start from first principles; they look for similarities in the current problem to problems they’ve solved in the past. And they bring to the design table the set of design idioms that they’ve learned over time. Design patterns provide a shared vocabulary that makes this expert knowledge available to everyone.

Design Patterns and the Gang of Four

In their book, the Gang of Four describe design patterns as having four essential features:

  • The Pattern Name: “… a handle we can use to describe a design problem, its solution, and consequences in a word or two. Naming a pattern immediately increases our design vocabulary.”
  • The Problem: Describes when to use the pattern. “It explains the problem and its context.”
  • The Solution: “… describes the elements that make up the design, their relationships, responsibilities, and collaborations…the pattern provides an abstract description of a design problem and how a general arrangement of elements solves it.”
  • The Consequences: The results and trade-offs of applying the pattern to a problem. These include time and space trade-offs, but also flexibility, extensibility, and portability, among others.4

___________________

2 Krasner, G. E. and S. T. Pope. “A cookbook for using the Model-View-Controller user interface paradigm in Smalltalk-80.” Journal of Object-Oriented Programming 1(3): 26-49. 1988.

3 Gamma, E., Helm, R., Johnson, R., Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. (Boston, MA: Addison-Wesley, 1995.)

Design patterns are classified using two criteria, scope and purpose. Scope deals with the relationships between classes and objects. Static relationships between classes are fixed at compile time, whereas, dynamic relationships apply to objects and these relationships can change at run-time. Purpose, of course, deals with what the pattern does with respect to classes and objects. Patterns can deal with object creation, composition of classes or objects, or the ways in which objects interact and distribute responsibilities in the program.

The Gang of Four describe 23 different design patterns in their book, dividing them into three different classes of patterns: creational, structural, and behavioral.

  • Creational design patterns are those that deal with when and how objects are created. These patterns typically create objects for you, relieving you of the need to instantiate those objects directly.
  • Structural design patterns are those that describe how objects are composed into larger groups,
  • Behavioral design patterns generally talk about how responsibilities are distributed in the design and how communication happens between objects.

The list is not meant to be complete, and over the 15 years since the publication of the Gang of Four’s Design Patterns book, many more patterns have been added to this original list by developers everywhere. A recent Google search for the phrase “design patterns” yielded 2.5 million hits, so lots of object-oriented developers have jumped on the design patterns bandwagon.

The Classic Design Patterns

The 23 (classic) design patterns described by the Gang of Four are (in the remainder of this chapter we’ll go over the six design patterns that are in italics):

Creational Patterns

  1. Abstract factory
  2. Builder
  3. Factory Method
  4. Prototype
  5. Singleton

    Structural Patterns

  6. Adapter
  7. Bridge
  8. Composite
  9. Decorator
  10. Façade
  11. Flyweight
  12. Proxy

    Behavioral Patterns

  13. Chain of responsibility
  14. Command
  15. Interpreter
  16. Iterator
  17. Mediator
  18. Memento
  19. Observer
  20. State
  21. Strategy
  22. Template method
  23. Visitor

___________________

4 Gamma et. al, 1995.

Patterns We Can Use

The six patterns in this section are a representative sample of the classic design patterns, and are six that you’ll find the most useful right away.

Creational Patterns

Creational Patterns all have to do with creating objects. If we think about class definitions as templates for producing objects, then these patterns are all about how to create those templates. The two patterns we'll look at next, Singleton and Factory show us two different ways of thinking about creating objects.

The Singleton Pattern

Singleton5 is almost certainly the easiest of the Design Patterns to understand and to code. The idea is simple. You are writing a program and you have a need for one – and only one – instance of a class. And you need to enforce that “and only one” requirement. Examples of programs that would use a Singleton pattern are things like print spoolers, window managers, device drivers, and the like.

So what are the implications of the “one, and only one” requirement? Well, first, it means your program can only say “new Singleton()” once, right? But what’s to stop other objects in your program (or objects in the program that you didn’t write) from issuing another “new Singleton()”? The answer is – nothing! As long as your class can be instantiated once, other objects should be able to instantiate it again and again. Well, bummer.

So what we need to do is to create a class that can be instantiated once and only once and which doesn’t use new to do the instantiation. Huh?

You heard me right: we need a class that can be instantiated without using new. Go ahead, think about it.

Here’s what we’ll do. The method that gets called when an object is instantiated is the constructor. In Java you can say new Singleton() because the Singleton() constructor is public – it’s visible from outside the class definition. If we want to keep the constructor so we can make instances of Singleton objects, but we don’t want anyone to be able to use new to do that, we must make the constructor private. “But wait!” you cry, “if the constructor is private then we can’t instantiate the object at all!” Au contraire, dear reader. If the constructor is private, then it can only be accessed from inside the class definition, so it’s entirely possible to instantiate the object from within the class definition itself!

“But wait again!” you say. “How do we get to the constructor from outside the class definition?” Well, in Java is there a way to access a method inside a class without having to have an instantiation of the class? (Think the Math class.)

Aha! Class methods! If you create a public method that is static (a class method) then that method is visible outside the class definition without having the object actually instantiated. So, if we create a class with a private constructor and then use a static method to create an instance of the class, you can control how many instances of the class you create. Here’s the code:

public class Singleton {
        // this is the instance that will hang around
        private static Singleton uniqueInstance;
        // the private constructor – can't be accessed from outside
        private Singleton() {
                // do stuff here to initialize the instance
        }
        // here's the static method we'll use to create the instance
        public static Singleton getInstance() {
                if (uniqueInstance == null) {
                        uniqueInstance = new Singleton();
                }
                return uniqueInstance;
        }
        // Other methods – after all Singleton is a real class
}

and in order to use the Singleton class we’d do something like:

___________________

5 Gamma et. al, 1995

public class SingletonTest {
        
        public static void main(String [] args) {
                Singleton mySingle;
                mySingle = Singleton.getInstance();
                // and we do other stuff here
        }
}

When we instantiate the Singleton instance by calling the getInstance() method, it will test to see if we’ve done this before. If not, it creates the instance using the private constructor in the Singleton class. If the instance already exists (the uniqueInstance variable is not null) then we just return the reference to the object. Told you it was simple.

This version of the Singleton pattern isn’t without its problems; for one thing, it can fail if you are writing a multi-threaded Java program. The solution above is not “thread safe.” It's possible that in between the test for the existing of a Singleton instance and the actual creation of an instance that your program could be swapped out while another thread executes. When it swaps back in, it could erroneously create another instance of the Singleton. There are relatively easy solutions to this.

The simplest way to make your Singleton pattern thread-safe is to make the getInstance() method a synchronized method. That way it will execute to completion and not be swapped out. Here’s a version of the getInstance() method that is thread safe:

        public synchronized static Singleton getInstance() {
                if (uniqueInstance == null) {
                        uniqueInstance = new Singleton();
                }
                return uniqueInstance;
        }

Notice that the only difference is the inclusion of the synchronized keyword in the method signature.

On to the next pattern.

The Factory Pattern

Say you’ve got a small company and your company is making ice cream. Because you’re a small company, your factory can only make one type of ice cream at a time, so you have to tell the workers on any given day what kind of ice cream they’re making that day. Of course, you don’t change the different ice cream recipes every day, and you don’t change the basic makeup of the factory every day, but you do change which ice cream is being manufactured, packaged, and shipped each day. And, of course, you have a program to model your factory. So the question is, how do you write the program in such a way that you don’t have to change it every day? And how do you write it so you don’t need to change (most) of it when you add a new ice cream flavor? That’s where the Factory design pattern comes in.

A Factory pattern (also called a Factory Method pattern)6 creates objects for you. You use it when you need to create several types of objects that are usually related to each other – they usually have the same abstract parent class - but are different. You ask the factory to create an object of type X and it creates one; you ask it to create an object of type Y and it creates one. This forces the creation of concrete classes to be relegated to subclasses of an interface that knows how to create concrete classes, and keeps your other classes closed for modification. All without changing X or Y or the factory. In our example we can create Vanilla, Chocolate, or Strawberry ice cream objects, using an IceCreamFactory class to generate the different types of ice cream. The Factory pattern allows you to define an interface for creating a family of objects, and it allows subclasses to decide which members of the family to instantiate. It defers instantiation down into the subclasses.

___________________

6 Gamma, et. al, 1995.

In our example, we’ll have several classes:

IceCream: An interface that defines our ice cream objects

VanillaIceCream: A concrete class that inherits from IceCream

ChocolateIceCream: A concrete class that inherits from IceCream

StrawberryIceCream: A concrete class that inherits from IceCream

Factory: An interface that defines the methods used to make IceCream objects

IceCreamFactory: Our concrete implementation of the Factory interface that makes different IceCream objects

IceCreamStore: A driver class that lets us make and sell ice cream

Our IceCream interface could look like:

public interface IceCream {
        public  void yummy();
        // plus other methods
}

and our ice cream flavors end up as concrete classes:

public class VanillaIceCream implements IceCream {
        public void yummy() {
                System.out.println("Vanilla!");
        }
}

public class ChocolateIceCream implements IceCream {
        public void yummy() {
                System.out.println("Chocolate!")
        }

}
public class StrawberryIceCream implements IceCream {
        public void yummy() {
                System.out.println("Strawberry!");
        }
}

The Factory pattern depends on defining an interface for the factory and then allowing subclasses that implement that interface to actually create the objects; our Factory interface will look something like the following:

public interface Factory {
        public  IceCream makeIceCream(String type);
        // and other methods here to sell, package and transport
        // the ice cream
}

And finally, our concrete IceCreamFactory which will implement the makeIceCream() method from the Factory interface and actually make an IceCream object looks like this:

public class IceCreamFactory implements Factory {
        public IceCream makeIceCream(String type) {
                IceCream newIceCream = null;
                if (type.equals("Vanilla")) {
                        newIceCream = new VanillaIceCream();
                } else if (type.equals("Chocolate")) {
                        newIceCream = new ChocolateIceCream();
                } else if (type.equals("Strawberry")) {
                        newIceCream = new StrawberryIceCream();
                }
                return newIceCream;
        }
}

In order to test our factory we create a driver – our ice cream store!

public class IceCreamStore {
        public static void main (String [] args) {
                IceCreamFactory myStore = new IceCreamFactory();
                IceCream vanilla = myStore.makeIceCream("Vanilla");
                vanilla.yummy();
                IceCream choco = myStore.makeIceCream("Chocolate");
                choco.yummy();
                IceCream straw = myStore.makeIceCream("Strawberry");
                straw.yummy();
        }
}

Figure 11-1 shows what these look like in a version of UML.

images

Figure 11-1. The Ice Cream Store using an IceCreamFactory

What to notice about this Factory pattern example? Well, how about the following:

  • The factory method (makeIceCream()) encapsulates the creation of the IceCream object. Our driver just tells the factory which ice cream to make.
  • The Factory interface provides the interface for the subclasses to create the actual objects.
  • The IceCreamFactory concrete class actually creates the objects by implementing the makeIceCream() method. This also implies that other concrete classes that implement Factory could do the same.
  • This leaves the IceCream classes alone, and makes it easier for the IceCreamStore to create new objects.
  • Notice also that the IceCreamStore class only deals with IceCream objects. It doesn’t have to know anything about particular types of ice cream. The concrete IceCream objects implement the methods from the IceCream interface and the IceCreamStore just uses them regardless of which type of IceCream you’ve created.
  • It also means that you can change the implementation of a particular type of IceCream without changing either the interface or the IceCreamStore. What a concept!

Structural Patterns

Structural patterns help you put objects together so you can use them more easily. They are all about grouping objects together and providing ways for objects to coordinate to get work done. Remember, composition, aggregation, delegation, and inheritance are all about structure and coordination. The Structural pattern we'll look at here – the Adapter – is all about getting classes to work together.

The Adapter Pattern

So here’s the problem. You’ve got a client program Foo that wants to access another class or library or package, Bar. The problem is, Foo is expecting a particular interface and that interface is different from the public interface that Bar presents to the world. What are you to do?

Well, you could rewrite Foo to change the interface it expects to conform to the interface that Bar is presenting. But if Foo is large, or if it’s being used by other classes, that may not be a viable possibility. Or you could rewrite Bar, so it presents the interface that Foo is expecting. But maybe Bar is a commercial package and you don’t have the source code?

That’s where the Adapter design pattern comes in.7 You use the Adapter pattern to create an intermediate class that wraps the Bar interface inside a set of methods that presents the interface that Foo is looking for. Here’s the idea: the Adapter can interface with Foo on one side and with Bar on the other. So the interface to Bar doesn’t have to change, and Foo users gets the interface they expects. Everyone is happy! By the way, the Adapter design pattern is also called the Wrapper pattern because it wraps an interface.8 See Figure 11-2.

images

Figure 11-2. The Adapter lets Foo use Bar

There are two ways to implement adapters: class adapters, where the adapter will inherit from the target class, and object adapters that use delegation to create the adapter. Note the difference: a class adapter subclasses an existing class and implements a target interface. An object adapter subclasses a target class and delegates to an existing class. Figure 11-3 is the UML for a generic class adapter.

___________________

7 Gamma et. al, 1995.

8 Gamma et. al, 1995.

images

Figure 11-3. A class adapter example

Note that the Adapter class inherits from the Adaptee class and implements the same Target interface that the Client class uses. Here’s the code for this example:

public class Client
{
    public static void main(String [] args) {
        Target myTarget = new Adapter();

        System.out.println(myTarget.sampleMethod(12));
    }
}

public interface Target
{
   int sampleMethod(int y);
}

public class Adapter extends Adaptee implements Target
{
    public int sampleMethod(int y) {
        return myMethod(y);
    }
}

public class Adaptee
{
   public Adaptee() {
   }

    public int myMethod(int y) {
        return y * y;
    }
}

The object adapter, on the other hand still implements the Target interface, but uses composition with the Adaptee class in order to accomplish the wrapping; it will look like:

public class Adapter implements Target
{
    Adaptee myAdaptee = new Adaptee();

    public int sampleMethod(int y) {
        return myAdaptee.myMethod(y);
    }
}

In both cases, the Client doesn’t have to change! That’s the beauty of Adapter. You can change which Adaptee you’re using, by changing the Adapter and not the Client.

Behavioral Patterns

Where creational patterns are all about how to create new objects, and structural patterns are all about getting objects to communicate and cooperate, behavioral patterns are all about getting objects to do things. They examine how responsibilities are distributed in the design and how communication happens between objects. The three patterns we’ll look at here all describe how to assign behavioral responsibilities to classes. The Iterator pattern is about how to traverse a collection of objects. The Observer pattern tells us how to manage push and pull state changes. The Strategy pattern lets us select different behaviors behind a single interface.

The Iterator Pattern

If you’ve programmed in Java, you have seen iterators. We’ll get to that, but let's start at the beginning. If you have a collection of elements, you can organize them in many different ways. They can be arrays, linked lists, queues, hash tables, sets, and so on. Each of these collections will have its own unique set of operations, but there’s usually one operation that you might want to perform on all of them – traverse the entire collection from beginning to end, one element at a time. Oh, and you want to traverse the elements in such a way that you don’t need to know the internal structure of the collection. And you may want to be able to traverse the collection backwards, and you may want to have several traversals going on at the same time.

That’s what the Iterator pattern is for.9 The Iterator pattern creates an object that allows you to traverse a collection, one element at a time.

Because of the requirement that you don’t need to know about the internal structure of the collection, an Iterator object doesn’t care about sorting order; it just returns each object as it’s stored in the collection, one at a time from first to last. The simplest iterator needs just two methods

  • hasNext(): Which returns a true if there is an element to be retrieved, i.e. we’ve not reached the end of the collection yet; and false if there’s no elements left.
  • getNextElement(): Which returns the next element in the collection.

___________________

9 Gamma et. al, 1995.

In the Iterator Pattern, we have an Iterator interface that is implemented to make a concrete Iterator object that is used by a concrete Collections object. Both of these are used by a client that creates the Collection and gets the iterator from there. Figure 11-4 is the UML version of this from Gamma et. al.

images

Figure 11-4. An example of using the Iterator Pattern

You can see that the client class uses the Collection and the Iterator interfaces, and the Concrete_Iterator is part of and uses the Concrete_Collection. Note that the Collection_Interface will contain an abstract method to create an iterator for the Collection. This method is implemented in the Concrete_Collection class and when the client calls the method, a Concrete_Iterator is created and passed to the client to use.

Starting in version 1.2, Java contained the Java Collections Framework (JCF) that included a number of new classes and interfaces to allow you to create collections of objects, including an Iterator interface. All of these new types contained iterators. Java even included (just for collections of type List) an expanded Iterator called a ListIterator. With the ListIterator you can go backwards through the list.

Typical Iterator code in Java using both the Iterator and the ListIterator implementations:

/**
 * Iterate through elements Java ArrayList using an Iterator
 * We then use ListIterator to go backwards through the same
 * ArrayList
*/

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;

public class ArrayListIterator {
    public static void main(String[] args) {
        //create an ArrayList object
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        //Add elements to Arraylist
        arrayList.add(1);
        arrayList.add(3);
        arrayList.add(5);
        arrayList.add(7);
        arrayList.add(11);
        arrayList.add(13);
        arrayList.add(17);

        //get an Iterator object for ArrayList
        Iterator iter = arrayList.iterator();

        System.out.println("Iterating through ArrayList elements");
        while(iter.hasNext()) {
            System.out.println(iter.next());
}

        ListIterator list_iter = arrayList.listIterator(arrayList.size());

        System.out.println("Iterating through ArrayList backwards");
        while(list_iter.hasPrevious()) {
            System.out.println(list_iter.previous());
        }
    }
}

Note that when we create the ListIterator object, we pass it the number of elements in the ArrayList. This is to set the cursor that the ListIterator object uses to point to just past the last element in the ArrayList so it can then look backwards using the hasPrevious() method. In both the Iterator and ListIterator implementations in Java, the cursor always points between two elements so that the hasNext() and hasPrevious() method calls make sense; for example, when you say iter.hasNext(), you’re asking the iterator if there is a next element in the collection. Figure 11-5 is the abstraction of what the cursors look like.

images

Figure 11-5. Cursors in the Iterator Abstraction

Finally, some iterators will allow you to insert and delete elements in the collection while the iterator is running. These are called robust iterators. The Java ListIterator interface (not the Iterator) allows both insertion (via the add() method) and deletion (via the remove() method) in an iterator with restrictions. The add() method only adds to the position immediately before the one that would be the next element retrieved by a next() or immediately after the next element that would be returned by a previous() method call. The remove() method can only be called between successive next() or previous() method calls, it can’t be called twice in a row, and never immediately after an add() method call.

The Observer Pattern

I love NPR’s Talk of the Nation: Science Friday radio show (http://sciencefriday.com). But I hardly get to listen to it when it is broadcast because it’s on from 2:00–4:00 PM EST on Fridays and, because I work for a living (snicker), I can’t listen to it then. But I subscribe to the podcast and so every Saturday morning I get a new podcast of SciFri so I can listen to it on my iPod while I mow the lawn. If I ever get tired of SciFri, I can just unsubscribe and I won’t get any new podcasts. That, ladies and gentlemen, is the Observer Pattern.

According to the Gang of Four, the Observer Pattern “...defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.”10 So in my SciFri example, NPR is the “publisher” of the SciFri podcast, and all of us who “subscribe” (or register) to the podcast are the observers. We wait for the SciFri state to change (a new podcast gets created) and then we get updated automatically by the publisher. How the updates happen differentiates between two different types of Observer – push and pull. In a push Observer, the Publisher (also known as the Subject in object-oriented speak) changes state and then pushes the new state out to all the Observers. In a pull Observer, the Subject changes state, but doesn’t provide a full update until the Observers ask for it – they pull the update from the Subject. In a variation of the pull model, the Subject may provide a minimal update to all the Observers notifying them that the state has changed, but the Observers still need to ask for the details of the new state.

So with the Observer pattern, we need a Subject interface so that the Subject and the Observer and the Client all can tell what the state interface they’re using is. We also need an Observer interface that just tells us how to update an Observer. Our publisher will then implement the Subject interface and the different “listeners” will implement the Observer interface. Figure 11-6 is a UML diagram of this.

images

Figure 11-6. The canonical Observer Pattern

The client class is missing, but it will use both the ConcreteSubject and ConcreteObserver classes. Here’s a simple implementation of a push model version of all of these. Remember, it’s push model because the ConcreteSubject object is notifying all the Observers whether they request it or not.

First, the Subject interface that tells us how to register, remove, and notify the Observers.

public interface Subject
{
    public void addObserver(Observer obs);
    public void removeObserver(Observer obs);
    public void notifyAllObservers();
}

Next, the implementation of the Subject interface. This class is the real publisher, so it also needs the attributes that form the state of the Subject. In this simple version we use an ArrayList to hold all the Observers.

___________________

10 Gamma et. al, 1995.

import java.util.ArrayList;

public class ConcreteSubject implements Subject

    private ArrayList<Observer> observerList;
        // these two variables are our state
    private int foo;
    private String bar;
   
    public ConcreteSubject() {
        observerList = new ArrayList<Observer>();
        this.foo = 0;
        this.bar = "Hello";
    }

    public void addObserver(Observer obs) {
        observerList.add(obs);
    }
   
    public void removeObserver(Observer obs) {
        observerList.remove(obs);
    }
   
    public void notifyAllObservers() {
        for (Observer obs: observerList) {
            obs.update(this.foo, this.bar);
           }
    }
   
    public void setState(int foo, String bar) {
        this.foo = foo;
        this.bar = bar;
        notifyAllObservers();
    }
}

Next, the Observer interface that tells us how to update our Observers.

public interface Observer
{
    public void update(int foo, String bar);
}

And then the implementation of the Observer interface.

public class ConcreteObserver implements Observer
{
    private int foo;
    private String bar;
    Subject subj;

    /**
     * Constructor for objects of class ConcreteObserver
     */
    public ConcreteObserver(Subject subj) {
        this.subj = subj;
        subj.addObserver(this);
    }
   
   public void update(int foo, String bar)
    {
        this.foo = foo;
        this.bar = bar;
        show();
    }
   
    private void show() {
        System.out.printf("Foo = %s Bar = %s ", this.foo, this.bar);
    }
}

And finally, the driver program that creates the publisher and each of the observers and puts them all together.

public class ObserverDriver
{
    public static void main(String [] args) {
        ConcreteSubject subj = new ConcreteSubject();

        ConcreteObserver obj = new ConcreteObserver(subj);

        subj.setState(12, "Monday");
        subj.setState(17, "Tuesday");
    }
}

And the output of executing the driver (which all comes from the show() method in the ConcreteObserver object will look like:


Foo = 12 Bar = Monday
Foo = 17 Bar = Tuesday

In many ways, the Observer design pattern works like the Java events interface. In Java you create a class that registers as a “listener” (our Observer) for a particular event type. You also create a method that is the actual observer and which will respond when the event occurs. When an event of that type occurs, the Java events object (our Subject) notifies your observer by making a call to the method you wrote, passing the data from the event to the observer method – Java events use the push model of the Observer pattern.

For example, if you create a Button object in a Java program, you use the addActionListener() method of the Button object to register to observe ActionEvents. When an ActionEvent occurs all the ActionListeners are notified by having a method named actionPerformed() called. This means that your Button object must implement the actionPerformed() method to handle the event.

The Strategy Pattern

Sometimes you have an application where you have several ways of doing a single operation or you have several different behaviors, each with a different interface. One of the ways to implement something like this is using a switch statement like so:

switch (selectBehavior) {
        case Behavior1:
                Algorithm1.act(foo);
                break;
        case Behavior2:
                Algorithm2.act(foo, bar);
                break;
        case Behavior3:
                Algorithm3.act(1, 2, 3);
                break;
}

The problem with this type of construct it that if you add another behavior, you need to change this code and potentially all the other code that has to select different behaviors. Not good.

The Strategy design pattern gets you around this. It says that if you have several behaviors (algorithms) you need to select from dynamically, you should make sure that they all adhere to the same interface – a Strategy interface – and then that they are selected dynamically via a driver, called the Context, that is told which to call by the client software. The Strategy pattern embodies two of our fundamental object-oriented design principles—encapsulate the idea that varies and code to an interface, not an implementation. Figure 11-7 is what a Strategy setup will look like.

images

Figure 11-7. A typical Strategy Pattern layout

Some examples of when you might use the Strategy pattern are

  • Capture video using different compression algorithms
  • Compute taxes for different types of entities (people, corporations, non-profits)
  • Plot data in different formats (line graphs, pie charts, bar graphs)
  • Compress audio files using different formats

In each of these examples you can think of having the application program telling a driver – the Context – which of the strategies to use and then asking the Context to perform the operation.

As an example, let’s say you are a newly minted CPA and you’re trying to write your own software to compute your customers tax bills. (Why a CPA would write her own tax program, I have no idea; work with me on this.) Initially, you’ve divided your customers into individuals who only file personal income taxes, corporations who file corporate income taxes, and not-for-profit organizations who file hardly any taxes at all. Now, all of these groups have to compute taxes so the behavior of a class to compute taxes should be the same for all; but they’ll compute taxes in different ways. So what we need is a Strategy setup that will use the same interface – to encapsulate what varies in our application, and to code the concrete classes to an interface – and allow our client class to select which type of tax customer to use. Figure 11-8 is a diagram of what our program will look like.

images

Figure 11-8. Using the Strategy pattern to select a Tax behavior

We create a TaxStrategy interface that all the concrete TaxStrategy classes will implement.

public interface TaxStrategy {
  public double computeTax(double income);
}

Since the only thing that varies here is how the tax is computed, our TaxStrategy interface just includes the computeTax() method.

Then we create each of the concrete TaxStrategy classes, each of which implement the tax computation for that particular type of customer

public class PersonalTaxStrategy implements TaxStrategy {
    private final double RATE = 0.25;
  
    public double computeTax(double income) {
        if (income <= 25000.0) {
            return income * (0.75 * RATE);
        } else {
            return income * RATE;
        }
    }
}

public class CorpTaxStrategy implements TaxStrategy {
    private final double RATE = 0.45;
  
    public double computeTax(double income) {
        return income * RATE ;
    }
}

public class NFPTaxStrategy implements TaxStrategy {
    private final double RATE = 0.0;
 
    public double computeTax(double income) {
        return income * RATE;
    }
}

Next, we create the Context class that does the heavy lifting of creating strategy objects requested by the client program and executing the correct ones.

public class TaxPayerContext {
    private TaxStrategy strategy;
    private double income;
    /** constructor for Context */
    public TaxPayerContext(TaxStrategy strategy, double income) {
        this.strategy = strategy;
        this.income = income;
    }
    public double getIncome() {
        return income;
    }
    public void setIncome(double income) {
        this.income = income;
    }
    public TaxStrategy getStrategy() {
        return strategy;
    }
    public void setStrategy(TaxStrategy strategy) {
        this.strategy = strategy;
    }
    public double computeTax() {
        return strategy.computeTax(income);
    }
}

Note that here we write a separate version of the computeTax() method (we’re not overriding the method because we’re not extending any of the concrete classes – the Strategy pattern uses composition, not inheritance). This version calls the computeTax() method of the strategy that the client has selected.

Finally, we implement the client that controls who gets instantiated and when.

public class StrategyClient {
        public static void main(String [] args) {
                double income;
                TaxPayerContext tp;

                income = 35000.00;
                tp = new TaxPayerContext(new PersonalTaxStrategy();
                                                                                 income);
                System.out.println("Tax is " + tp.computeTax());

                tp.setStrategy(new CorpTaxStrategy());
                System.out.println("Tax is " + tp.computeTax());
        }
}

The client class selects which algorithm to use and then gets the context object to execute it. This way we’ve encapsulated the tax computation in separate classes. We can easily add new customer types just by adding new concrete TaxStrategy classes and making the change in the client to use that new concrete type. Piece of cake!

Conclusion

Design Patterns are a reusable, commonly occurring core solution to a design problem. They are not a finished design. Rather a design pattern is a template you can use to solve similar problems in many different domains. Design patterns offer you proven strategies for solving common proglems and so they can help speed up your design process. And because these patterns describe proven solutions they can help reduce defects in your design as well.

Be careful, though. Like all design techniques, design patterns are heuristics and so there will be cases where they just don't fit. Trying to squeeze a pattern into a problem where it just doesn't belong is adking for trouble.

The goal of design patterns is to define a common vocabulary for design. They may not get us all the way there but design patterns, plus the design principles described in Chapter 10, get us a long way down that road.

References

Alexander, C., S. Ishikawa, et al.  A Pattern Language: Towns, Buildings, Construction. (Oxford, UK: Oxford University Press, 1977.)

Freeman, E. and E. Freeman Head First Design Patterns. (Sebastopol, CA: O’Reilly Media, Inc., 2004.)

Gamma, E., Helm, R., Johnson, R., Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. (Boston, MA: Addison-Wesley, 1995.)

Krasner, G. E. and S. T. Pope. “A cookbook for using the Model-View-Controller user interface paradigm in Smalltalk-80.” Journal of Object-Oriented Programming 1(3): 26-49. 1988.

Lieberherr, K., I. Holland, et al. Object-Oriented Programming: An Objective Sense of Style. OOPSLA ’88, Association for Computing Machinery, 1988.

Martin, R. C. Agile Software Development: Principles, Patterns, and Practices. (Upper Saddle River, NJ: Prentice Hall, 2003.)

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

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