Types of Design Patterns

 <  Day Day Up  >  

Design Patterns features 23 patterns grouped into the three categories listed below. 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 section, we will discuss one example from each of these categories to provide a flavor of what design patterns actually are. For a comprehensive list and description of individual design patterns, please refer to the books listed at the end of this chapter.

Creational Patterns

  • 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 ”let's take a 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 Web site 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 actually hit. You want a counter object instantiated when the first hit is made, but after that, you want to use the existing object to simply increment the count.

Figure 15.2. The singleton model.

graphics/15fig02.gif

Although there might be other ways to regulate the creation of objects, the best way is to let the class itself take care of this issue.

Taking Care of Business

Remember, one of the most important OO rules is that an object should take care of itself. This means that issues regarding the life cycle of a class should be handled in the class, not delegated to language constructs like static, and so on.


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.

Figure 15.3. Singleton UML diagram.

graphics/15fig03.gif

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\n");    }    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\n");    }    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.

More Than One Reference

There may well be more than one reference to the singleton. If you create references in the application and each reference is referring to the singleton, you will have to manage the multiple references.


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() );  } } 

Two References to a Single Counter

Be aware that in this example, there are two separate references pointing to the counter.


This code actually 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 only output a single time. When counter2 is created, it receives a copy of the original object ”the same as counter1 .

Figure 15.4. Using the Counter singleton.

graphics/15fig04.jpg

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).

Figure 15.5. Using the updated Counter singleton.

graphics/15fig05.jpg

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 actually 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 actually 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 then 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 allows 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.

 
 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:

 
 interface MailInterface {   int getMail(); } 

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

 
 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 simply instantiate your new mail tool and invoke the getMail() method.

 
 public class Adapter {  public static void main(String[] args)  {   MyMailTool myMailTool = new MyMailTool();   myMailTool.getMail();  } } 

When you do invoke the getMail() method, you are using this new interface to actually invoke the retrieveMail() method from the original tool. This, of course, is a very simple example; however, by creating this wrapper, you can actually 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

  • 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 actually provides its own implementation of an iterator. The following code creates a vector and then inserts a number of strings into it.

 
 import java.util.*; public class Iterator {   public static void main(String args[]) {     // Instantiate a Vector.     Vector vector = new Vector();     // Add values to the vector.     vector.addElement(new String("Joe"));     vector.addElement(new String("Mary"));     vector.addElement(new String("Bob"));     vector.addElement(new String("Sue"));     // Iterate through the vector.     Enumeration names = vector.elements();     System.out.println("\n");     System.out.println("Names:");     iterate(names );         }         private static void iterate(Enumeration enum) {     while (enum.hasMoreElements()) {       System.out.println(enum.nextElement());     }   } } 

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

 <  Day Day Up  >  


Object-Oriented Thought Process
Object-Oriented Thought Process, The (3rd Edition)
ISBN: 0672330164
EAN: 2147483647
Year: 2003
Pages: 164
Authors: Matt Weisfeld

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net