Software Development Process 9 | Design Patterns

Series: Software Development Process

Software Development Process 9 | Design Patterns

  1. Design Patterns

(1) History of Design Patterns

  • 1977: Christopher Alexander, an American professor of architecture at UC Berkeley, introduces the idea of patterns as successful solutions to problems in his book called A Pattern Language. The book contains about 250 patterns.
  • 1987: Ward Cunningham and Kent Beck leveraged this idea of Alexander’s patterns in the context of an object-oriented language. In this specific case, the language was called “Small Talk”.
  • 1987: At the same time, Eric Gamma was working on his dissertation, whose topic was the importance of patterns and how to capture them.
  • 1987~1992: There were several workshops related to design patterns.
  • 1992: Jim Coplien compiled a catalog of C++ items, which are some sort of patterns, and he listed this catalog of patterns in his book, which was titled Advanced C++ Programming Styles and Idioms.
  • 1993~1994: There were several additional workshops focused on patterns. This workshop brought together many patterns folks, including Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. These guys are also known as the “Gang of 4”. The result of this collaboration was the famous book Design Patterns: Elements of Reusable Object-Oriented Software.

(2) Pattern Catalogue

The book Design Patterns: Elements of Reusable Object-Oriented Software we mentioned above describes five main classes of patterns,

  • Fundamental Patterns: the basic patterns (e.g. delegation pattern, interface pattern, proxy pattern)
  • Creational Patterns: the patterns that support object creation (e.g. abstract factory pattern, factory method pattern, lazy initialization pattern, singleton pattern)
  • Structural Patterns: the patterns that help compose objects and put objects together (e.g. adapter pattern, bridge pattern, decorator pattern)
  • Behavioral Patterns: the patterns that are mostly focused on realizing interactions among different objects (e.g. chain of responsibility pattern, iterator pattern, observer pattern, state pattern, strategy pattern, visitor pattern)
  • Concurrency Patterns: the patterns that support concurrency or relate to concurrency aspects (e.g. active object, monitor object, thread pool pattern)

(3) The Definition of Pattern Format Subset

In Gang of Four’s book, we can see that these definitions contain a lot of information, and what we are going to list here is just a subset of this information. So in this part, let’s focus on four essential elements of a design pattern. These are its,

  • Name
  • Intent: the goal of the pattern
  • Applicability: the least of situations or context in which the pattern is applicable
  • Structure and Participants: the static model that describes the elements (e.g. normally, the classes or the objects involved in the pattern). In addition to that, the structure also describes the relationships, responsibilities, and collaborations among these classes or objects.

An extra element we would like to mention is the sample code that illustrates the use of patterns.

(4) Elements of The Factory Method Pattern

Let’s now look at the first design pattern that we will discuss, the factory method pattern. We are going to start by discussing the intent of the pattern and its applicability.

As far as the intent is concerned, the factory method pattern allows for creating objects without specifying their class, by invoking what we call a factory method, which is a method whose main goal is to create class instances.

This pattern is applicable because of the following reasons,

  • A class cannot anticipate the type of object it must create. This means the type of an object is not known at the compile time and it is not known until the code runs (e.g. Frameworks)
  • A class wants its subclasses to specify the type of objects it creates
  • A class needs control over the creation of its objects (e.g. limited number of objects can be created)

The structure of this pattern is represented here, using the UML notation, includes three classes, the Creator, the ConcreteCreator, and the Product.

The participants for the factory method pattern should be,

The Creator provides the interface for the factory method. So when the interface for the factory method is invoked, it returns an object of type Product.

The ConcreteCreator provides the actual method for creating the Product. So the method factoryMethod() is a concrete implementation of the interface.

Finally, the Product is the object created by the factory method.

So summarizing, we have the interface for the factory method (i.e. factoryMethod() in Creator), the actual implementation of the summary method (i.e. factoryMethod() in ConcreteCreator), and the object that is created by the factory method (i.e. Product), when it is invoked.

(5) Factory Method Pattern: Example

The example I’m going to use consists of a class called ImageReaderFactory which provides the factory method createImageReader. As you can see the method takes an InputStream as input and returns an object of type ImageReader, and it’s static so that we can invoke it even if we don’t have an instance of the ImageReaderFactory.

public class ImageReaderFactory{
public static ImageReader createImageReader(InputStream iS) {
int imageType = getImageType(iS);
switch(imageType){
case ImageReaderFactory.GIF
return new GifReader(iS);
case ImageReaderFactory.JPEG
return new JpegReader(iS);
// ...
}
}
}

So why is this a situation in which it is appropriate to use the factory method pattern?

  • It corresponds exactly to the cases that we saw before of applicability.
  • It is a case in which we don’t know the type of object that we need to create until we run the code because it depends on the value and content of the InputStream. So, until we read the InputStream, we cannot figure out whether we need to create a GIF, a JPEG, or some other type of image. In this case, we want to simply delegate to this class the creation of the object once we know what type of object needs to be created.

Let’s now see the participants in this example,

  • ConcreteCreator: ImageReaderFactory
  • factoryMethod: createImageReader
  • Creator 1: GifReader
  • Creator 2: JpegReader
  • Product: image object of different types

(6) Strategy Pattern

The second pattern I want to discuss is the strategy pattern, which provides a way to configure a class with one of many behaviors. This pattern allows for defining a family of algorithms, encapsulating them into separate classes, so each algorithm is in one class, and making these classes interchangeable, but providing a common interface for all the encapsulated algorithms.

So in essence, the intent of a strategy pattern is to allow for switching between different algorithms for accomplishing a given task. For example, imagine having different sorting patterns with different space or time tradeoffs. You might want to be able to have them all available and use different ones in different situations.

This pattern is applicable when,

  • we have different variants of an algorithm
  • we have many related classes that differ only in their behavior

The structure of this pattern is represented here using the UML notation.

We have 3 types of participants for this pattern, the Context, the Strategy(or Algorithm), and the ConcreteStrategy..., which can be as many concrete strategies as the number of behaviors that we need to implement.

The Context is the interface to the outside world. It maintains a reference to the current strategy and allows for updating this reference at run time. Basically, the outside world will invoke the functionality provided by the different algorithms, by using the interface contextInterface(). Depending on which algorithm is currently selected, that’s the one that will be executed when the functionality is involved.

The Strategy (or Algorithm) is the common interface for the different algorithms, so all the algorithms implement this interface.

Finally, the concrete strategies ConcreteStrategy... are the actual implementations of the algorithms. So if I have 10 different variants of my algorithm, I will implement 10 different concrete strategies.

(7) Strategy Pattern: Example

Now, let’s see how this strategy pattern works within an example. We’re going to consider a program that takes an input as text file and produce an output as a filtered file. So basically it outputs a subset of the content of this text file based on some filters.

  • Input: Text File
  • Output: Filter File

And we’re going to have four different types of filters.

  • #1: NO Filter.
  • #2: Output only words that starts with t.
  • #3: Output only words that are longer than five characters.
  • #4: Output only words in the text file that are palindromes (e.g. the word “kayak”).

The implementation of this program should use some packages,

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.StringTokenizer;

So let’s see how this program could be implemented using a strategy pattern. The general algorithm interface is as follows,

interface CheckStrategy {
public boolean check(String s);
}

And one general implementation of this interface should be,

class All implements CheckStrategy {
@Override
public boolean check(String s) {
return true;
}
}

And we will start by looking at the Context class.

class Context {
private CheckStrategy strategy;
    public Context() {
this.strategy = new All();
}
    public void changeStrategy(CheckStrategy strategy) {
this.strategy = strategy;
}
    public void filter(String filename) throws IOException {
BufferedReader infile = new BufferedReader(new FileReader(filename));
String buffer = null;
while((buffer = infile.readLine()) != null) {
StringTokenizer words = new StringTokenizer(buffer);
while( words.hasMoreTokens() ) {
String word = words.nextToken();
if (strategy.check(word)) {
System.out.println(word);
}
}
}
}
}

The Context class contains a reference of the current strategy of type CheckStrategy. The construction function of the Context class Context() is called when the content is created by default. It will set the current strategy to the All strategy, which is a strategy that accepts all the inputs. Later on, if we want to specify a strategy of this context, we have to call changeStrategy and it simply replaces the current strategy with the one specified as the parameter. Also, in this form, the Context also performs filter , which is a function reads a file line by line and then apply the check method to utilize the corresponding strategy.

Then, let’s see how we implement the main method. The main method simply creates the Context by new Context(). Rethink profile from the arguments, and then what he does is simply as a demonstration, it will perform the filtering using all the different filters.

public class StrategyPattern {
    public static void main(String[] args) throws IOException {
Context context = new Context();
String filename = "foo.txt";
        System.out.println("\n* Default:");
context.filter(filename);
        System.out.println("\n* Longer than 5:");
context.changeStrategy(new LongerThan5());
context.filter(filename);
        System.out.println("\n*Palindromes:");
context.changeStrategy(new Palindrome());
context.filter(filename);
}
}

Now let’s look at the actual algorithm. This is the interface, the algorithm interface. We have all the different implementations of the algorithm, the simplest one is the All algorithm, the simple return is always true, so all the words will be printed. The second one StartWithT, and again, without looking at the details of implementations that don’t really matter, what it does is basically check in that the first character is t, and returns true in that case and false otherwise.

class StartWithT implements CheckStrategy {
@Override
public boolean check(String s) {
if( s == null || s.length() == 0) {
return false;
}
return s.charAt(0) == 't';
}
}

For the LongerThan5 algorithm, this will implement the check strategy interface, and the check will be performed. By checking that the word is longer than five characters and returning true in that case and false otherwise.

class LongerThan5 implements CheckStrategy {
@Override
public boolean check(String s) {
if(s == null) {
return false;
}
return s.length() > 5;
}
}

Finally, the Palindrome check is a little more complicated, but basically it just checks whether the word is a Palindrome and returns true in that case.

class Palindrome implements CheckStrategy {
@Override
public boolean check(String s) {
if(s == null) {
return false;
}
int length = s.length();
if(length < 2) {
return true;
}
int half = length/2;
for(int i = 0; i < half; ++i) {
if(s.charAt(i) != s.charAt(length - 1 - i)) {
return false;
}
}
return true;
}
}

(8) Other Common Patterns

Finally, let’s see a few more patterns. Although it will take too long to cover them in detail, we would like to at least mention and quickly discuss a few more of these more commonly-used patterns. We are going to introduce the following patterns,

  • Visitor Pattern: a way of separating an algorithm from an object structure on which it operates. And a practical result of this separation is the ability to add the new operation to existing object structures, without modifying the structures (e.g. decouple operations from a graph object)
  • Decorator Pattern: a wrapper that adds functionality to a class. For all the functionality that was already in the original class, it will simply invoke this functionality and for the new one, you will implement it using the services of the class. A nice property of the decorator pattern is that it’s stackable.
  • Iterator Pattern: the iterator allows you to do, is basically access elements of a collection without knowing the underlying representation. So the iterator will allow you to just go through a set of objects without worrying about how the objects are stored. So you basically just ask the iterator to give you the first object, the next object, and so on. This is broadly used in many standard libraries.
  • Observed Pattern: this pattern is very useful when you have an object of interest and a set of other objects that are interested in the changes that might occur in this first object. So what the observer pattern allows you to do is to register these objects, so that they let the system know that they’re interested in changes in this first object. And then, every time that there is a change, these other objects will be automatically notified. So basically, the observer pattern allows for notifying dependents when an object of interest changes. (e.g. File system)
  • Proxy Pattern: a pattern in which a surrogate controls access to an object. So what the proxy allows you to do is to control how this object, that is behind the proxy, is actually accessed, for example, by filtering some calls. In a sense, the proxy allows you for masking some of the functionality of the object that is behind the proxy.

For more information, you can find an interesting paper of non-software examples of software design pattern at this link.

(9) Approach for Choosing A Pattern

With so many patterns, we can choose them based on a possible approach,

  • make sure you understand your design context
  • examine the patterns catalog or think about / identify the possible patterns that you could use
  • study the pattern you want to use and study the related patterns
  • apply that pattern

(10) Pitfalls for Using Patterns

When you choose a pattern, be mindful that there are pitfalls in the use of patterns.

  • One obvious one is the fact that you might select the wrong pattern and make your design worse instead of better.
  • The second one is that if you get too excited about patterns, then you might be abusing patterns, so just using too many patterns, and end up with a design that is more complicated rather than less complicated.

So always be careful, spend the time to figure out which one is the right pattern to apply, and make sure that you don’t use patterns that you don’t actually need.

(11) Negative Design Patterns

The concept of negative design patterns (also anti-patterns or bad smells) is the pattern that should be avoided. So negative design patterns are basically guidelines on how not to do things. We will discuss it in later sections.