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. Many of these later books were written in Java.

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.

In the following sections, we 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 singleton pattern.

The Singleton Design Pattern

The singleton pattern, represented in Figure 15.2, is a creational pattern used to regulate the creation of objects from a class to a single object. For example, if you have a website that has a counter object to keep track of the hits on your site, you certainly do not want a new counter to be instantiated each time your web page is hit. You want a counter object instantiated when the first hit is made, but after that, you want to use the existing object to increment the count.

Image

Figure 15.2. The singleton model.

Although there might be other ways to regulate the creation of objects, it is often best to let the class itself take care of this issue. However, there are many times when using an external factory is very useful or even required—specifically, where patterns like the Factory, Abstract Factory, and Bridge are important.

Figure 15.3 shows the UML model for the singleton taken directly from Design Patterns. Note the property uniqueinstance, which is a static singleton object, and the method Instance(). The other properties and methods are there to indicate that other properties and methods will be required to support the business logic of the class.

Image

Figure 15.3. Singleton UML diagram.

Any other class that needs to access an instance of a singleton must interface through the Instance() method. The creation of an object should be controlled through the constructor, just like any other OO design. We can require the client to interface through the Instance() method, and then have the Instance() method call the constructor.

The following Java code illustrates what the code looks like for the general singleton:

public class ClassicSingleton {
  private static ClassicSingleton instance = null;

  protected ClassicSingleton() {
   // Exists only to defeat instantiation.
  }
  public static ClassicSingleton getInstance() {
   if(instance == null) {
     instance = new ClassicSingleton();
   }
   return instance;
  }
}

We can create a more specific example for the web page counter example that we used previously:

public class Counter
{
  private int counter;
  private static Counter instance = null;

  protected Counter()
  {
  }

  public static Counter getInstance() {
      if(instance == null) {
         instance = new Counter ();
        System.out.println("New instance created ");
      }
      return instance;
  }

  public void incrementCounter()
  {
    counter++;
  }

  public int getCounter()
  {
    return(counter);
  }


}

The main point to note about the code is the regulation of the object creation. Only a single counter object can be created. The code for this is as follows:

public static Counter getInstance() {
    if(instance == null) {
       instance = new Counter ();
      System.out.println("New instance created ");
    }
    return instance;
}

Note that if the instance is null, it means that an object has yet to be instantiated. In this event, a new Counter object is created. If the instance is not null, it indicates that a Counter object has been instantiated, and no new object is to be created. In this case, the reference to the only object available is returned to the application.

Although this code is certainly interesting, it is also valuable to see how the singleton is instantiated and managed by the application. Take a look at the following code:

public class Singleton
{
 public static void main(String[] args)
 {
  Counter counter1 = Counter.getInstance();
  System.out.println("Counter : " + counter1.getCounter() );

  Counter counter2 = Counter.getInstance();
  System.out.println("Counter : " + counter2.getCounter() );

 }
}

This code uses the Counter singleton. Take a look at how the objects are created:

Counter counter1 = Counter.getInstance();

The constructor is not used here. The instantiation of the object is controlled by the getInstance() method. Figure 15.4 shows what happens when this code is executed. Note that the message New instance created is output only a single time. When counter2 is created, it receives a copy of the original object—the same as counter1.

Image

Figure 15.4. Using the Counter singleton.

Let’s prove that the references for counter1 and counter2 are the same. We can update the application code as follows:

public class Singleton
{
  public static void main(String[] args)
  {
    Counter counter1 = Counter.getInstance();
    counter1.incrementCounter();
    counter1.incrementCounter();
    System.out.println("Counter : " + counter1.getCounter() );

    Counter counter2 = Counter.getInstance();
    counter2.incrementCounter();
    System.out.println("Counter : " + counter2.getCounter() );

  }
}

Figure 15.5 shows the output from the singleton application. Note that in this case, we are incrementing counter1 twice, so the counter will be 2. When we create the counter2 reference, it references the same object as counter1, so when we increment the counter, it’s now 3 (2+1).

Image

Figure 15.5. Using the updated Counter singleton.

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

• Façade

• 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. 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 we can treat it as a string:

String myString = myIntWrapper.toString();

This wrapper enables us 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.

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

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