HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Design Patterns are blueprints that describe how to design class structures and object interactions to solve commonly encountered problems. A Design Pattern can be as simple as the practice of using an interface to achieve polymorphism and as complicated as designs used to solve intricate concurrency problems.

If you are a Java developer then you have certainly used Design Patterns in your development efforts in the past. This is an inescapable truth because the Java language itself and all of its associated APIs are designed to leverage some of the most common design patterns known. For example: the java.awt package utilizes the Strategy Pattern in choosing layout managers; the java.io package uses the Decorator Wrapper pattern and the java.sql package, commonly known as JDBC, leverages the Abstract Factory Pattern.

Design Patterns are simply a way of doing things. Every time you prepare to design a system you continually ask yourself, "OK, here is the problem, what is the solution?" All too often, engineers focus on the specific problem at hand without regard to future needs. This results in applications that are difficult to change and extend. Design Patterns can help you avoid this problem by providing you with a road map of how to structure your classes and their interactions to be as flexible and extensible as possible.

This article will demonstrate the power of a few Design Patterns by revealing them in commonly utilized Java APIs like the AWT and JDBC. Once you understand how easy it is to learn and use Design Patterns, a whole new awareness of OO development will open up for you. This is going to be a religious experience, so be prepared to laugh, cry and pound your computer keyboard in anger as you discover the power of Design Patterns!

The Strategy Pattern
One of the first things you do when learning the Java language is use a layout manager in an applet. Remember the layout managers? FlowLayout, CardLayout, GridLayout, even the impossible to use GridBagLayout. A layout manager is usually used in an applet in the following manner:

public void init( ){
this.setLayout(new FlowLayout());
add( new Button("OK"));
add( new Button("Cancel"));
...
}

Figure 1

If you have used a layout manager, then congratulations! You have already implemented the Strategy Pattern. The Strategy Pattern is a design pattern that allows the developer to easily swap different strategies that change how a system behaves without having to change the system's architecture. If, for example, you decided that you wanted to use a BorderLayout instead of a FlowLayout it would be a simple matter of swapping the FlowLayout constructor above with a BorderLayout's. Actually, the layout manager system also requires you to change the add(..) methods slightly depending on which layout is used, but direct interaction between the layout and the Applet (Container) does not change.

You can also use the Strategy Pattern in applications you design. When it is possible to have several different algorithms for performing a process, each of which is the best solution depending on the situation, then a Strategy Pattern may be in order. A Strategy Pattern has three important participants: Strategy, Concrete- Strategy and Context.

Strategy Participant
The Strategy Participant usually will be in the form of a Java interface or abstract class that is general enough so that all of the different types of algorithms you will need can effectively implement it. In most cases, this is a no-brainer because algorithms that serve a similar purpose usually require the same method calls. In the AWT layout manager system, the Strategy participant is the java.awt.LayoutManager interface. The LayoutManager interface defines a set of methods that any layout, regardless of how it functions, would need to implement in order to effectively interact with the Applet. This includes methods to add new components, remove components, re-align components when the Applet is reshaped and so on.

ConcreteStrategy Participant
The ConcreteStrategy participants are the actual algorithms programmed into Java classes. By implementing the same Strategy interface, each algorithm becomes a self contained object with exactly the same set of methods as all the others. In the AWTs layout manager system, the different types of layouts - BorderLayout, FlowLayout, etc. - are the ConcreteStrategy participants. Each of these classes represent a different algorithm for laying out visual components. They all implement the same methods for adding and removing and re-aligning components but they execute these methods in their own special way. The result is several different layouts for viewing the same visual widgets.

Context Participant
The Context Participant is the object that utilizes the services of the ConcreteStrategies. Since all of the ConcreteStrategies have the same interface, it eliminates the need to customize the Context for every strategy it may need to use. When we set the layout manager (applet.setLayout(LayoutManager lm)) in an Applet, we do so by passing in an object reference of type LayoutManager. The Applet doesn't know which ConcreteStrategy we passed to it and it shouldn't care. All it knows is, when it needs to add a component it calls lm.addComponent(..) and when it detects that the Applet has been re-sized it calls lm.layoutContainer(..). Regardless of which layout manager actually performs the task the result is the same; a component is successfully added and the layout is rearranged.

Summary
The beauty behind the Strategy Pattern is the flexibility it gives your system to change its behavior. The Strategy Pattern can be used to accommodate different searching algorithms in text editors, encryption algorithms in network applications, ray tracing algorithms in 3D graphics and ... well, you get the idea. The sky is the limit. Anytime you have a system where there are several different ways of performing the same task, consider the Strategy Pattern as a possible Design Pattern.

The Decorator Wrapper Pattern
If you have done any Java programming at all, you should be familiar with PrintSteam. When you used the System.out.println(..) in your very first Java program you were, in fact, using a PrintStream object.

System.out.println
("Hello World!");
// System.out is a
PrintStream object

Figure 2

Did you know when you wrote this that you were using the Decorator Wrapper Pattern? Pretty easy isn't it? The Decorator Wrapper Pattern has the general goal of enhancing the functionality of an object by wrapping it in another object. It is the opposite of a Strategy Pattern. Where a Strategy Pattern allows you to swap the underlying object while maintaining the same interface, the Wrapper Pattern allows you to change the interface while maintaining the underlying object. The Decorator Wrapper Pattern provide several advantages, but two of the most important are the ability to apply and withdraw functionality and the ability to dynamically wrap one wrapper inside another to provide different combinations of functionality.

The java.io package is a great example of the Decorator Wrapper Pattern. Let's say you want to write some information to a file using the java.io package. First you need to create an OutputStream to a file:

FileOutputStream fileOut =
new FileOutputStream("super_secret.txt");

A FileOutputStream allows you to write only in the form of bytes to a file by calling its write(byte [ ] b) method. We can make it easier to write our text to the file by wrapping the FileOutputStream in a PrintStream object (see Listing 1).

Wow! That was pretty easy. We took advantage of the Decorator Wrapper Pattern's ability to add new functionality by wrapping our FileOutputStream in a PrintStream to obtain a friendlier interface. Of course, now your super secret information is super easy for anyone to read. What if we could encrypt the text? We would need to add another wrapper to the FileOutputStream. This is easy with the Decorator Wrapper Pattern used in the java.io package.

First, we create a new wrapper that does the encoding. We call it the BitReverserOut. This filter reverses the bit values on every byte so that the first four bits and the last four are reversed. It's not rocket science but it will make the file unreadable (see Listing 2).

Now that we have our new wrapper we can include it in our application to achieve seamless encryption (see Listing 3).

Now that's the Decorator Wrapper Pattern really doing its job! Not only were we able to use the PrintStream as a wrapper to add functionality, but we also used the BitReverserOut and PrintStream together to obtain a new combination of functionality. The Decorator Wrapper Pattern has the additional advantage of limiting the number of subclasses needed. Instead of creating a huge family of OutputStream classes that serve every possible need we can leverage the Decorator Wrapper Pattern's dynamic design to obtain a truly unlimited number of combinations - now that's power! A complete program for writing and reading encrypted messages is included in Listing 4.

The Wrapper Pattern has the following participants: Component, ConcreteComponent, Decorator and ConcreteDecorator.

Component Participant
The Component Participant defines the interface for objects that can be enhanced. In the above example, the Component is the OuputStream. In most cases, the Component will be an abstract class. This is true in our example because the OutputStream is abstract.

ConcreteComponent Participant
ConcreteComponents are objects that extend the Component Participant but are not abstract. These are the objects whose functionality we are attempting to enhance. In our example, the ConcreteComponent was the FileOutputStream.

Decorator Participant
The Decorator Participant is the namesake and key element in the Decorator Wrapper Pattern. It is responsible for maintaining a reference to the Component Participant and defining an interface that matches the Component Participant's interface. In Java, the Decorator will usually be a class that extends the Component class. The java.io.FilterOutputStream is the Decorator in the above example. If you look at the definition of the FilterOutputStream you will notice that it matches the OutputStream method for method, but it also includes a member variable, out, which is its internal reference to the OutputStream it decorates.

ConcreteDecorator Participant
The ConcreteDecorator is the group of objects that can add functionality to the ConcreteComponents. ConcreteDecorator extends the Decorator Participant and overrides the appropriate methods adding the functionality needed. The BitReverserOut is an excellent example of this type of participant. You can see in Listing 2 that the BitReverserOut overrides the FilterOutputStream's write(..) method to reverse the bit values before writing them to the OutputStream. This is classic ConcreteDecorator behavior. The fact that a ConcreteDecorator extends the Decorator which extends the component means that the ConcreteDecorator can also be a ConcreteComponent. This is why we can wrap one ConcreteDecorator inside another. In fact, the number of wrappings is unlimited. This kind of recursive wrapping was nicely demonstrated when we wrapped the BitReverserOut inside the PrintStream class.

Summary
You can use the Decorator Wrapper Pattern in your applications. When you design an object that has a rudimentary functionality that could be used in a variety of different situations, consider using the Decorator Wrapper Pattern. You also can use the Decorator Wrapper Pattern to reduce the need for huge hierarchies of classes or to provide the capability to enhance functionality dynamically. Some GUI systems use the Decorator Pattern to provide an unlimited number of widgets with out defining every possible component. This is accomplished by using the Decorator Pattern to add graphical embellishments to widgets. If, for example, TextArea is a widget, you could use the Decorator Wrapper Pattern to embellish it with a snazzy beveled border or a scroll bar. The important thing to remember is that the Decorator Wrapper Pattern allows you to enhance the functionality of a basic service by wrapping it inside of decorators. Decorators can be wrapped recursively (one inside the other), allowing for an unlimited number of combinations and added functionality.

The Abstract Factory Pattern
One of the most popular Enterprise APIs is the java.sql or JDBC API. This powerful collection of interfaces and classes makes it possible to change from one database's implementation to the next by changing only one line of code. The JDBC API is a perfect example of the Abstract Factory Pattern. Listing 5 is an example of the JDBC API in action.

Figure 3

In the Strategy Pattern you learned how to accommodate different algorithms by encapsulating them in classes that implemented the same type of interface. The Abstract Factory Pattern accomplishes the same thing, but on a much grander scale. With the Abstract Factory Pattern you can change the behavior of an entire API or family of related classes.

The Abstract Factory Pattern is a perfect Design Pattern when you need to accommodate different solutions using one uniform family of objects. The key to the Abstract Factory Pattern is how objects are created. In the Abstract Factory Pattern, all the objects in the family are obtained as products from other objects in the family. This hides the creation process from the application and allows the vendor to do all kinds of proprietary things in their code that have no impact on the customer's application.

In the JDBC API, the Statement object is always created from the Connection object. This avoids the need to explicitly create a vendor-specific Statement object by doing something like:

Statement stmt =
new CompanyXStatementObject(dbName, props);

The same holds true for the Connection object, which is produced by the Driver object; the ResultSet object which is produced by the Statement object, and so on. By hiding the creation process, the JDBC API allows you to change the type of Driver used - and possibly the database - without changing massive amounts of code in your application. In fact, you need only change one thing, the type of driver instantiated in the beginning of your program. The Abstract Factory Pattern has the following participants: AbstractFactory, ConcreteFactory, AbstractProduct, ConcreteProduct and Client.

AbstractFactory and AbstractProduct Participants
The AbstractFactory Participant is the most important member of this pattern. Usually implemented as an interface, the AbstractFactory is responsible for defining how to produce the other objects that we need. The AbstractProduct defines how the products produced by the AbstractFactory should look to our application. The AbstractProduct also is usually implemented as an interface in Java and defines a fixed set of methods that can be invoked to produce specific results.

In the JDBC API, most of the interfaces are pulling double duty by acting as both AbstractFactorys and AbstractProducts. The Connection interface is returned by invoking the Driver interface's connect(..) method. So the Driver is the AbstractFactory Participant and the Connection is the AbstractProduct.

Connection cnct = drvr.connect( .. );

The Connection interface defines methods that can be used to manage a database connection. These connection management methods define the AbstractProduct aspect of the Connection interface - a fixed set of methods that can be invoked to produce specific results.

cnct.commit( );
cnct.getWarnings( );
cnct.close( );

The Connection interface, however, also defines itself as an AbstractFactory Participant. The Statement interface is returned by invoking the Connection interface's createStatement( ) method. So the Connection is now acting as an AbstractFactory to produce a Statement which is the AbstractProduct.

Statement stmt = cnct.createStatement( );

The Statement interface, like the Connection interface, is both an AbstractProduct and AbstractFactory participant. As an AbstractProduct the Statement interface defines methods for querying and modifying the database. As an AbstractFactory, the Statement interface produces the ResultSet interface, another AbstractProduct of the JDBC API.

ConcreteFactory and ConcreteProduct Participants
OK, here are the guys that do all the work. The ConcreteFactory and ConcreteProduct participants are the actual objects that implement the interfaces defined by AbstractFactory and AbstractProduct respectively. They define the algorithms and procedures that are actually carried out when a Factory method is invoked. In our JDBC API example we can change ConcreteFactorys by changing the Driver that is instantiated in the beginning of our program. You could, for example, change from CompanyX's driver to CompanyA's driver in the following manner:

// Driver drvr =
// new CompanyXProprietaryDriver( );
Driver drvr =
new CompanyAProretaryDriver( );

Voil‡! You're done. From now on, every time a new product is produced by an AbstractFactory call it will actually be a CompanyA ConcreteProduct that is implementing a JDBC AbstractProduct interface. Listing 6 is a look behind the scenes at the creation of a Statement object (ConcreteProduct) by a Connection object (ConcreteFactory).

Client Participant
The Client participant is the environment that the other participants (AbstractFactory, AbstractProduct, ConcreteFactory, and ConcreteProduct) operate in. The Client Participant may recognize only the Abstract participants and must be indifferent to the Concrete participants. In our JDBC example, the client is the application itself.

Summary
The AbstractFactory Pattern is an enormously powerful Design Pattern that can be used to decouple (disconnect or unbind) your applications from specific implementations. The new Java Foundation Classes (JFC) recently announced by JavaSoft will use the AbstractFactory Pattern to allow developers to change the look and feel of their applications from Macintosh, to Windows, to a X-Windows interface on any platform on the fly! Developers will even be able to define their own GUI implementations that can be used in the JFC. You can use the AbstractFactory Pattern yourself to decouple how your application accesses services from how they actually work. Next time you need to implement a family of objects and you know there are, or could be, different implementations of the same set of interfaces, consider the AbstractFactory Pattern as a possible design solution.

Conclusion
While you may not have experienced religious rapture while reading this article you should have gained a healthy appreciation for Design Patterns. Design Patterns provide object-oriented designers and architects with tried and proven techniques for solving difficult design problems. They offer solutions that are flexible, extensible and reusable - the three cornerstones of good object-oriented design.

If you are interested in learning more about Design Patterns, consider picking up a copy of Design Patterns: Elements of Reusable Object-Oriented Software. Written by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides - a.k.a. "The Gang of Four" - this Addison-Wesley publication is an excellent resource that contains 23 powerful and important Design Patterns.

About the Author
Richard Monson-Haefel is a Java consultant working in Minneapolis, MN. In addition to consulting, Richard is considered to be a leading Java evangelist in the Midwest. He was the founder and first president of the Wisconsin Java User Group and the founder and Managing Editor of Javology Magazine (www.javology.com). Richard can be reached at [email protected]

	

Listing 1.

FileOutputStream fileOut = new FileOutputStream("super_secret.txt");
PrintStream printStream = new PrintStream(fileOut );
printStream.println("Code: The blue pig has no legs.  Reply: Corn grows in stone");
printStream.close();

Listing 2

class BitReverserOut extends FilterOutputStream {
	byte trueByte = (byte)255;

	public BitReverserOut(OutputStream out){
		super(out);
	}

	private byte flip(byte b){
	   return (byte)( ((b << 4) & 0xF0) + ((b >> 4) & 0x0F) );
	}

    public void write(byte b[], int off, int len) throws IOException {
        byte temp;
        for(int i = off; i < len ; i++) {
            temp = flip(b[i]);  //(byte)(b[i] ^ trueByte); // cause
           // System.out.print(temp);
            out.write(temp);
        }
    }
}

Listing 3.

FileOutputStream fileOut = new FileOutputStream("super_secret.txt");
BitReverserOut bitReverser = new BitReverserOut(fileOut);
PrintStream printStream = new PrintStream(bitReverser);
printStream.println("Code: The blue pig has no legs.  Reply: Corn grows in stone");
printStream.close();

Listing 4.

import java.io.*;

public class SuperSecretApp {

    public static void main(String [] args){

        try{

            if(args[0].equals("out"))
            {
     FileOutputStream fileOut = new FileOutputStream("super_secret.txt");
     BitReverserOut bitReverser = new BitReverserOut(fileOut);
     PrintStream printStream = new PrintStream(bitReverser);
     printStream.println("Code: The blue pig has no legs.  Reply: Corn grows in stone");
     printStream.close();
     }else if(args[0].equals("in")){
     FileInputStream fileIn = new FileInputStream("super_secret.txt");
     BitReverserIn bitReverser = new BitReverserIn(fileIn);
     DataInputStream dis = new DataInputStream(bitReverser);
     String str = dis.readLine( );
     System.out.println(str);
     }else
     System.out.println("wrong arguments");

        }catch(IOException e){System.out.println(e);}
    }
}


class BitReverserOut extends FilterOutputStream {
	byte trueByte = (byte)255;

	public BitReverserOut(OutputStream out){
		super(out);
	}

	private byte flip(byte b){
	   return (byte)( ((b << 4) & 0xF0) + ((b >> 4) & 0x0F) );
	}

    public void write(byte b[], int off, int len) throws IOException {
        byte temp;
        for(int i = off; i < len ; i++) {
            temp = flip(b[i]);  //(byte)(b[i] ^ trueByte); // cause
           // System.out.print(temp);
            out.write(temp);
        }
    }
}

class BitReverserIn extends FilterInputStream {
	byte trueByte = (byte)255;

	public BitReverserIn(InputStream in){
		super(in);
	}

	private int flip(int i){
	   byte b = (byte)i;
	   return ( ((b << 4) & 0xF0) + ((b >> 4) & 0x0F) );

	}

    public int read() throws IOException {
        int r;
	    r = in.read();
	   // System.out.println(r);
	    if(r != -1)
	    {
	        r = flip(r);//((byte)r ^ trueByte);
	    }
	    return r;

    }

    public int read(byte b[], int off, int len) throws IOException {
        int r;
	    r = in.read(b, off, len);

        for (int i = off; i < len ; i++) {
            System.out.println(b[i]);
            b[i] = (byte)(b[i] ^ trueByte); // cause

        }
        return r;
    }
}

Listing 5.

Driver drvr = new CompanyXPropretaryDriver( );
Connection cnct = drvr.connect(dbName,props);
...
public foo {
	Statement stmt  = cnct.createStatement( );
	ResultSet rslt = stmt.executeQuery("select * from employees");
	ResultSetMetaData = rslt.getMetaData( );
	....
}

Listing 6.

public class CompanyAConnection implements java.sql.Connection{
	...
	public Statement createStatement( ){
		return new CompanyAStatement( );
	}
...
}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.