~ 2013.04.03 ~
"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 Alexander, talking about patterns in buildings and towns
A design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. It is a description or template for how to solve a problem that can be used in many different situations.
...a "duck pond simulator" that can show a wide variety of duck species swimming and quacking.
But a request has arrived to allow ducks to also fly.
(We need to stay ahead of the competition!)
Easy fix: Add fly() to Duck ...and now all ducks can fly!
Whoops! Rubber ducks do not fly!
They don't quack, either, so we overrode quack() to make them squeak.
We could override fly() to make it do nothing, but that's less than ideal, especially...
...when we might find other Duck subclasses that would have to do the same thing.
What was supposed to be a good instance of reuse via inheritance has turned into a maintenance headache!
What about an Interface?
Here we define two interfaces and allow subclasses to inherit the interface they need.
What are the trade-offs?
With inheritance we get...
quack() method instead of multiple) --> goodWith interfaces we get...
fly() method get it) --> goodEncapsulate what varies:
the behaviors between Duck subclasses
Duck subclasses and pull them out of Duck.
Duck's will no longer have fly() and quack() methods directly.QuackBehavior: Quack, Squeak, SilenceFlyBehavior: FlyWithWings, CantFly, FlyWhenThrownTo take advantage of these new behaviors, we must modify Duck to delegate its flying and quacking behaviors to these other classes rather than implementing this behavior internally.
We'll add two attributes that store the desired behavior.
This is an instance of the principle
"Favor composition over inheritance".
(For clarity, we'll also rename fly() and quack() to performFly() and performQuack().)
FlyBehavior and QuackBehavior define a set of behaviors that provide behavior to Duck. Duck is composing each set of behaviors and can switch among them dynamically, if needed.
While now each subclass has a performFly() and performQuack() method, at least the user interface is uniform and those methods can point to "null" behaviors when required.
Not quite yet... Our duck simulator is not completely decoupled from the Duck subclasses:
Duck mallard = new MallardDuck();
Fortunately, we can eliminate this type of coupling if needed, using a design pattern called Factory.
(We'll get to talk about the Factory pattern another day...)
The solution that we applied to this design problem is known as the Strategy Design Pattern.
It features the following OO design concepts/principles:
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
AStrategy is pulled out of Context.Context only makes use of the public interface of AStrategy and is not tied to concrete subclasses.Context can change its behavior by switching among the various concrete algorithms.In general, a pattern has four essential elements:
Our ticket to creating one-of-a-kind objects for which there is only one instance.
Student: What use is that?
TA: There are many objects that we only need one of: thread pools, caches, dialog boxes, objects that handle preferences and registry settings, objects used for logging, and objects that act as device drivers to devices like printers and graphics cards.
TA: In fact, if we were to instantiate more than one, we'd run into all sorts of problems like incorrect program behavior, overuse of resources, or inconsistent results.
Student: Okay, so maybe there are classes that should only be instantiated once, but do I need a whole design pattern for this? Can't I just do this by convention or by global variables? You know, like in Java, I could do it with a static variable.
TA: In many ways, the Singleton Pattern is a convention for ensuring one and only one object is instantiated. It is a time-tested method, and gives us a global point of access, just like a global variable, but without the downsides.
Student: What downsides?
TA: Well, here's one example: if you assign an object to a global variable, then that object might be created when your application begins. Right? What if this object is resource intensive and your application never ends up using it? As you will see, with the Singleton Pattern, we can create our objects only when they are needed.
Student: This still doesn't seem like it should be so difficult...
TA: If you've got a good handle on static class variables and methods as well as access modifiers, it's not. But, as simple as it sounds, it's hard to get right. Just ask yourself: how do I prevent more than one object from being instantiated? It's not so obvious, is it?
new MyObject();MyObject? Could it call new on MyObject again?
public MyClass {
private MyClass() {}
}
MyClass is the only code that could call it. But that doesn't make much sense.
public MyClass {
public static MyClass getInstance() {}
}
MyClass is a class with a static method. We can call the static method like this: MyClass.getInstance()MyClass?
public MyClass {
private MyClass() {}
public static MyClass getInstance() {
return new MyClass();
}
}
MyClass.getInstance();MyClass is ever created?
public class Singleton {
private static Singleton uniqueInstance;
// other useful instance variables here
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
}
For most Java applications, we obviously need to ensure that the Singleton works in the presence of multiple threads. But, it looks fairly expensive to synchronize the getInstance() method, so what do we do?
Well, we have a few options...
getInstance() isn't critical to your application.
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) { uniqueInstance = new Singleton(); }
}
}
return uniqueInstance;
}
}
The Singleton pattern ensures a class has only one instance, and provides a global point of access to it.
Singleton is responsible for instantiating itself.Singleton provides a global point of access to that instance.Design patterns have been grouped into categories:
Create flexible designs that are maintainable and that can cope with change!
Design patterns allow you to exploit the wisdom and lessons learned by other developers who've encountered design problems similar to the ones you are encountering.
There are tons of design patterns!
A short list: Abstract Factory, Builder, Factory Method, Prototype, Singleton, Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy, Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
It is my hope that this exposure to design patterns will allow you to become outstanding software engineers!
While I've only given you a (very) brief look into the world of design patterns, you now have some of the best tools to create more flexible and maintainable software.
I'd be happy to come back and talk about some of the other popular design patterns you'll find out in the wild!
But in the meantime...
A reminder that these slides can be found at...