Software Design Patterns
Florian Rappl, Fakultät für Physik, Universität Regensburg
Software Design Patterns
Introduction to modern software architecture
Structural patterns
Introduction
- Deal with the structure of a program
- Try to ease the design by identifying ways to realize certain relationships
- They are concerned with how classes and objects are composed to form larger structures
- Goal: Clean big picture, with a better (reliable, extensible, ...) layout
- Structural class patterns use inheritance to compose interfaces or implementations
The adapter pattern
- Old problem: One class wants to talk to another class by using a specific method, which is named differently
- Solution in the real world: An adapter is required!
- The adapter pattern tries to allow communication between two incompatible types
- The central class is called the Adapter
- This class knows about the Adaptee and the specific Target that is required from a client
Adapter diagram
Remarks
- The adapter pattern is always required when we want to enable communication between two boxed systems
- Sometimes this is also known as Wrapper
- It can create a reusable class that cooperates with unrelated classes that have incompatible interfaces
- One-way, two-way? Classically only a one-way solution is supported
- Two-way requires interfaces (Java, C#) or multiple-inheritance (C++)
A simple adapter
public interface ITarget
{
void MethodA();
}
public class Client
{
private readonly ITarget _target;
public Client(ITarget target)
{
_target = target;
}
public void Request()
{
_target.MethodA();
}
}
public class Adaptee
{
public void MethodB()
{
/* ... */
}
}
public class Adapter : ITarget
{
private readonly Adaptee _adaptee = new Adaptee();
public void MethodA()
{
_adaptee.MethodB();
}
}
public interface Target {
void methodA();
}
public class Client {
private final Target _target;
public Client(Target target) {
_target = target;
}
public void Request() {
_target.methodA();
}
}
public class Adaptee {
public void methodB() {
/* ... */
}
}
public class Adapter implements Target {
private final Adaptee _adaptee = new Adaptee();
public void methodA() {
_adaptee.MethodB();
}
}
class Target {
public:
virtual void MethodA() = 0;
};
class Client {
private:
Target* _target;
public:
Client(Target* target) {
_target = target;
}
void Request() {
_target->MethodA();
}
};
class Adaptee {
public:
void MethodB() {
/* ... */
}
};
class Adapter : public Target {
private:
Adaptee* _adaptee;
public:
void Adapter() {
_adaptee = new Adaptee();
}
public:
void MethodA() {
_adaptee->MethodB();
}
};
Practical considerations
- If the class inheriting from one class has a reference to the other class we call it an aggregate
- Otherwise if we do only method renaming for generating compatibility we call it a compatible
- In practice the adapter pattern is also used within the same library
- Some of the upcoming patterns are also helpful for generating a common communication platform
Do you see the adapter?
public class ButtonDemo {
public ButtonDemo() {
Button button = new Button("Press me");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
doOperation();
}
});
}
public void doOperation() {
/* ... */
}
}
The proxy pattern
- A proxy is an object that performs transparent communication by tunneling through a system
- Usually we are using proxies to hide the real data / implementation
- A proxy is a class with methods, which has a reference to another class with data
- Only the proxy can modify the data or execute methods of the data class
- This has security advantages, however, only at compiler level (not RT)
Proxy diagram
Remarks
- Proxies enable indirect access to data
- We have security and memory advantages
- The data has handled only by one class
- The data is encapsulated and passed by reference
- The abstraction is usually contained in an interface
- Implementation-wise an abstract class could be better (nested classes can access private members of parent class objects)
A transparent wrapper
public abstract class SubjectBase
{
public abstract void Operation();
}
public class ConcreteSubject : SubjectBase
{
public override void Operation()
{
/* ... */
}
}
public class Proxy : SubjectBase
{
private ConcreteSubject subject;
public override void Operation()
{
if (subject == null)
subject = new ConcreteSubject();
subject.Operation();
}
}
public abstract class SubjectBase {
public abstract void operation();
}
public class ConcreteSubject extends SubjectBase {
@Override
public void operation() {
/* ... */
}
}
public class Proxy extends SubjectBase {
private ConcreteSubject subject;
@Override
public void operation() {
if (subject == null)
subject = new ConcreteSubject();
subject.operation();
}
}
class SubjectBase {
public:
virtual void Operation() = 0;
};
class ConcreteSubject : public SubjectBase {
public:
void Operation() {
/* ... */
}
};
class Proxy : public SubjectBase {
private:
ConcreteSubject* subject;
public:
void Operation() {
if (subject == NULL)
subject = new ConcreteSubject();
subject->Operation();
}
};
Practical considerations
- Usually the proxy pattern is a way to minimize data duplication
- This is then a special case of the flyweight pattern (upcoming)
- Sometimes proxies come with a static instance counter
- Multiple proxies increase the instance counter and use the same data object
- Once no instances are left, the data object is disposed
- The reference counter pointer object is an implementation of this pattern
The bridge pattern
- A bridge allows us to reach the other side of a river or valley
- Here we connect to an object of a certain type
- The specific object could be changed
- Therefore we separate the abstraction from the implementation
- Changes in the implementation do not affect the abstraction
- This is very helpful as seen with the state pattern
Bridge diagram
Remarks
- The bridge pattern provides a cleaner implementation of real-world objects
- The implementation details can be changed easily
- Additionally it is possible to consider a variation of implementations
- Also the bridge pattern decouples the usage of a concrete implementation with a client, which ensures testability and robustness
A simple abstraction layer
abstract class Implementor
{
public abstract void Execute();
}
class Abstraction
{
protected Implementor implementor;
public Implementor Implementor
{
get { return implementor; }
set { implementor = value; }
}
public virtual void Operation()
{
implementor.Execute();
}
}
class RefinedAbstraction : Abstraction
{
public override void Operation()
{
implementor.Execute();
}
}
class ConcreteImplementor : Implementor
{
public override void Execute()
{
/* ... */
}
}
abstract class Implementor {
public abstract void execute();
}
class Abstraction {
protected Implementor implementor;
public void setImplementor(Implementor value) {
implementor = value;
}
public Implementor getImplementor() {
return implementor;
}
public virtual void operation() {
implementor.execute();
}
}
class RefinedAbstraction extends Abstraction {
@Override
public void operation() {
implementor.execute();
}
}
class ConcreteImplementor extends Implementor {
@Override
public void execute() {
/* ... */
}
}
class Implementor {
public:
virtual void Execute() = 0;
};
class Abstraction {
protected:
Implementor* implementor;
public:
void SetImplementor(Implementor* value) {
implementor = value;
}
Implementor* GetImplementor() {
return implementor;
}
virtual void Operation() {
implementor->Execute();
}
};
class RefinedAbstraction : public Abstraction {
public:
void Operation() {
implementor->Execute();
}
};
class ConcreteImplementor : public Implementor {
public:
void Execute() {
/* ... */
}
};
Practical considerations
- Practically the bridge pattern is used quite often with interfaces
- Interfaces already bring in some advantages like testability (mocking)
- If the implementation is defined by an interface, then the bridge does not contain any unnecessary overhead
- The proxy might also be responsible for handling the resources
- This pattern is useful to share an implementation among multiple objects
An implementation changer
public abstract class MessageSenderBase {
public abstract void sendMessage(string title, string details, int importance);
}
public class WebServiceSender : MessageSenderBase {
@Override
public void sendMessage(string title, string body, int importance) {
out.println("Web Service\n" + title + "\n" + body + "\n" + importance);
}
}
public class EmailSender : MessageSenderBase {
@Override
public void sendMessage(string title, string body, int importance) {
out.println("Email\n" + title + "\n" + body + "\n" + importance);
}
}
public class MessageQueueSender : MessageSenderBase {
@Override
public void sendMessage(string title, string body, int importance) {
out.println("Message Queue\n" + title + "\n" + body + "\n" + importance);
}
}
public class Message {
protected string title;
protected string body;
protected int importance;
protected MessageSenderBase messageSender;
public Message() {
this.messageSender = new EmailSender();
}
public string getTitle() {
return title;
}
public void setTitle(string value) {
title = value;
}
public string getImportance() {
return importance;
}
public void setImportance(string value) {
importance = value;
}
public string getBody() {
return body;
}
public void setBody(string value) {
body = value;
}
public virtual void send() {
messageSender.sendMessage(title, body, importance);
}
}
public class UserEditedMessage : Message {
string userComments;
public UserEditedMessage(MessageSenderBase messageSender) {
this.messageSender = messageSender;
}
public string getUserComments() {
return userComments;
}
public void setUserComments(string value) {
userComments = value;
}
@Override
public void send() {
messageSender.sendMessage(title, body + "\n" + userComments, importance);
}
}
The facade pattern
- A facade is a nice wall that hides the underlying building
- Usually a facade is used to provide the API behind a complex system
- Also it might be useful to bundle dependencies on external libraries
- This reduces dependencies for other packages
- A facade can create a new API that calls the required methods of the other APIs
- Hence a facade bundles different classes to achieve a common goal
Facade diagram
Remarks
- Classically we have one top class that contains references to each class's instance
- Each class represents a set of subtasks
- The facade's methods use several methods of the contained instances
- This constructs a save way of communicating to instances that do not share a common basis, but a common goal
- Testing an API by only testing the top-level increases testability
- The facade pattern also enhances readability
Wrapping libraries
public class Facade
{
public void PerformAction()
{
var c1a = new Class1A();
var c1b = new Class1B();
var c2a = new Class2A();
var c2b = new Class2B();
var result1a = c1a.Func();
var result1b = c1b.Func(result1a);
var result2a = c2a.Func(result1a);
c2b.Action(result1b, result2a);
}
}
public class Class1A
{
public int Func()
{
Console.WriteLine("Class1A.Func return value: 1");
return 1;
}
}
public class Class1B
{
public int Func(int param)
{
Console.WriteLine("Class1B.Func return value: {0}", param + 1);
return param+1;
}
}
public class Class2A
{
public int Func(int param)
{
Console.WriteLine("Class2A.Func return value: {0}", param + 2);
return param+2;
}
}
public class Class2B
{
public void Action(int param1, int param2)
{
Console.WriteLine("Class2B.Action received: {0}", param1 + param2);
}
}
public class Facade {
public void performAction() {
Class1A c1a = new Class1A();
Class1B c1b = new Class1B();
Class2A c2a = new Class2A();
Class2B c2b = new Class2B();
int result1a = c1a.func();
int result1b = c1b.func(result1a);
int result2a = c2a.func(result1a);
c2b.action(result1b, result2a);
}
}
public class Class1A {
public int func() {
out.println("Class1A.func return value: 1");
return 1;
}
}
public class Class1B {
public int func(int param) {
out.println("Class1B.func return value: " + param + 1);
return param + 1;
}
}
public class Class2A {
public int func(int param) {
out.println("Class2A.func return value: " + param + 2);
return param + 2;
}
}
public class Class2B {
public void action(int param1, int param2) {
out.println("Class2B.action received: " + param1 + param2);
}
}
class Facade {
public void PerformAction() {
Class1A c1a;
Class1B c1b;
Class2A c2a;
Class2B c2b;
int result1a = c1a.Func();
int result1b = c1b.Func(result1a);
int result2a = c2a.Func(result1a);
c2b.Action(result1b, result2a);
}
};
class Class1A {
public:
int Func() {
cout << "Class1A.func return value: 1";
return 1;
}
};
class Class1B {
public:
int Func(int param) {
cout << "Class1B.func return value: " << param << 1;
return param + 1;
}
};
class Class2A {
public:
int Func(int param) {
cout << "Class2A.func return value: " << param << 2;
return param + 2;
}
};
class Class2B {
public:
void Action(int param1, int param2) {
cout << "Class2B.action received: " << param1 << param2;
}
};
Practical considerations
- Quite often poorly designed APIs are also wrapped with a facade
- A facade is used when one wants an easier or simpler interface to an underlying implementation object
- The difference to an adapter is that an adapter also respects a particular interface and supports polymorphic behavior
- A facade does not support polymorphic behavior
- Here a decorator (upcoming) should be used for extensions
The flyweight pattern
- Several objects containing unique data and shared data
- A static class containing the shared data objects (or Singleton)
- A class to represent the shared data
- This should reduce the memory footprint
- Reducing the memory requirement will increase the performance
- In certain cases this can improve consistency
Flyweight diagram
Remarks
- This pattern makes most sense when a huge amount of data is considered or a collection of immutable objects
- The objects should either not change at all or be shared among several clients
- An application is the string management in languages like Java or C#
- Here every (unique) string is stored in a string table
- Most strings therefore do not need to be instantiated, as they are available in the table (otherwise they are created and added)
Flyweight factory
public class FlyweightFactory
{
private readonly Dictionary<string, FlyweightBase> _flyweights;
public FlyweightFactory()
{
_flyweights = new Dictionary<string, FlyweightBase>();
}
public FlyweightBase GetFlyweight(string key)
{
if (_flyweights.ContainsKey(key))
{
return _flyweights[key];
}
else
{
var newFlyweight = new ConcreteFlyweight();
_flyweights.Add(key, newFlyweight);
return newFlyweight;
}
}
}
public class FlyweightFactory {
private final Map<string, FlyweightBase> _flyweights;
public FlyweightFactory() {
_flyweights = new HashMap<string, FlyweightBase>();
}
public FlyweightBase getFlyweight(string key) {
if (_flyweights.containsKey(key))
{
return _flyweights.get(key);
}
else
{
FlyweightBase newFlyweight = new ConcreteFlyweight();
_flyweights.put(key, newFlyweight);
return newFlyweight;
}
}
}
class FlyweightFactory {
private:
map<string, FlyweightBase*> _flyweights;
public:
FlyweightBase* GetFlyweight(string key) {
if (_flyweights.find(key) == _flyweights.end())
{
FlyweightBase* newFlyweight = new ConcreteFlyweight();
_flyweights[key] = newFlyweight;
}
return _flyweights[key];
}
};
Practical considerations
- The factory might follow the factory pattern
- However, instead of instantiating objects we just return them
- In some cases (lazy loading) we might return special objects ...
- ... or instantiate objects if certain criteria are met
- Sometimes it is useful to include resource management
- A perfect example is a
StringBuilder
pool - Here we can gain a lot of performance for creating (long) strings on multiple occasions
Sharing states
public Glyph
{
private int width;
private int height;
private int ascent;
private int descent;
private int pointSize;
public int Width
{
get { return width; }
}
public int Height
{
get { return height; }
}
public int Ascent
{
get { return ascent; }
}
public int Descent
{
get { return descent; }
}
public int PointSize
{
get { return pointSize; }
}
}
public Character
{
char letter;
int position;
Glyph properties;
public Character(char letter, int position)
{
this.letter = letter;
this.position = position;
this.properties = GlyphFactory.Get(letter);
}
public char Letter
{
get { return letter; }
}
public int Position
{
get { return position; }
}
public Glyph Properties
{
get { return properties; }
}
}
The decorator pattern
- Quite often we have to extend existing classes
- Sometimes however, these classes are sealed or cannot be extended directly
- A pattern that is helpful in such cases is the decorator pattern
- Here we need a decorator class that implements the same interface as the abstraction
- This decorator then requires an object that implements the interface as well
Decorator diagram
Remarks
- There might be classes inheriting from the decorator
- This concept enables more sophisticated ways of using the interface
- Actually this pattern is quite close to the builder pattern
- However, the decorator is more focused on the model itself than on modifying it / general behavior
- It decouples complexity by using inheritance to specialize objects
Decorating existing classes
public interface IComponent
{
void Operation();
}
public class ConcreteComponent : IComponent
{
public void Operation()
{
/* ... */
}
}
public class Decorator : IComponent
{
private readonly IComponent _component;
public Decorator(IComponent component)
{
_component = component;
}
public void Operation()
{
_component.Operation();
}
}
public interface Component {
void operation();
}
public class ConcreteComponent implements Component {
public void operation() {
/* ... */
}
}
public class Decorator implements Component {
private final Component _component;
public Decorator(Component component) {
_component = component;
}
public void operation() {
_component.operation();
}
}
class Component {
public:
virtual void Operation() = 0;
};
class ConcreteComponent : public Component {
public:
void Operation() {
/* ... */
}
};
class Decorator : public Component {
private:
const Component* _component;
public:
Decorator(Component* component) : _component(component) {
}
void Operation() {
_component->Operation();
}
}
Practical considerations
- The decorater pattern works best if the component is an interface
- In principle this is also a wrapper, however, a non-transparent one
- This wrapper might also extend the existing interface, by e.g. adding new operations or properties
- The big advantage is that this wrapper is pluggable, i.e. it can be used with any object of the given time at runtime
- A decorator could also be used as input for a decorator (stacking)
Sandwiches
public abstract class Sandwich
{
private string description;
public abstract double Price { get; }
public virtual string Description
{
get { return description; }
protected set { description = value; }
}
}
public class TunaSandwich : Sandwich
{
public TunaSandwich()
{
Description = "Tuna Sandwich";
}
public override double Price
{
get { return 4.10; }
}
}
public class SandwichDecorator : Sandwich
{
protected Sandwich sandwich;
private string description;
public SandwichDecorator(Sandwich sandwich)
{
this.sandwich = sandwich;
}
public override string Description
{
get { return sandwich.Description + ", " + description; }
protected set { description = value; }
}
public override double Price
{
get { return sandwich.Price; }
}
}
public class Cheese : SandwichDecorator
{
public Cheese(Sandwich sandwich) : base(sandwich)
{
Description = "Cheese";
}
public override double Price
{
get { return sandwich.Price + 1.23; }
}
}
class Sandwich {
private:
string description;
public:
virtual double GetPrice() = 0;
virtual string GetDescription() {
return description;
}
virtual void SetDescription(string value) {
description = value;
}
};
class TunaSandwich : public Sandwich {
public:
TunaSandwich() {
SetDescription("Tuna Sandwich");
}
double GetPrice() {
return 4.10;
}
};
class SandwichDecorator : public Sandwich {
protected:
Sandwich* _sandwich;
private:
string description;
public:
SandwichDecorator(Sandwich* sandwich) : _sandwich(sandwich) {
}
string GetDescription() {
return _sandwich->GetDescription() + ", " + description;
}
void SetDescription(string value) {
description = value;
}
virtual double GetPrice() {
return _sandwich->GetPrice();
}
};
class Cheese : public SandwichDecorator {
public:
Cheese(Sandwich* sandwich) : SandwichDecorator(sandwich) {
SetDescription("Cheese");
}
double GetPrice() {
return _sandwich->GetPrice() + 1.23;
}
};
The composite pattern
- How to efficiently create tree-like structures?
- The answer is to follow the composite pattern
- One object containing several other objects
- We have two different kind of objects in there, leafs (certainly without child nodes) and composites (possibly containing child nodes)
- However, both types are subtypes of a Component class
- Most UI systems follow the composite pattern
Composite diagram
Remarks
- Ideally the component defines the methods that are shared among all nodes
- Usually it makes sense to have this component defined as an interface
- The interface should have a method to allow enumeration
- The enumeration lists all children and their children
- This results in a recursive call at each node level
- Less redundancy and formal more correct than other approaches
Building a tree structure
public abstract class Component
{
protected readonly string name;
public Component(string name)
{
this.name = name;
}
public abstract void Operation();
public abstract void Show();
}
class Composite : Component
{
private readonly List<Component> _children;
public Composite(string name)
: base(name)
{
_children = new List<Component>();
}
public void AddChild(Component component)
{
_children.Add(component);
}
public void RemoveChild(Component component)
{
_children.Remove(component);
}
public Component GetChild(int index)
{
return _children[index];
}
public override void Operation()
{
Console.WriteLine("Composite with " + _children.Count + " child(ren).");
}
public override void Show()
{
Console.WriteLine(name);
foreach (Component component in _children)
{
component.Show();
}
}
}
public class Leaf : Component
{
public Leaf(string name)
: base(name)
{
}
public override void Operation()
{
Console.WriteLine("Leaf.");
}
public override void Show()
{
Console.WriteLine(name);
}
}
public abstract class Component {
protected final string name;
public Component(string name) {
this.name = name;
}
public abstract void operation();
public abstract void show();
}
class Composite extends Component {
private final List<Component> _children;
public Composite(string name) {
super(name);
_children = new ArrayList<Component>();
}
public void addChild(Component component) {
_children.add(component);
}
public void removeChild(Component component) {
_children.remove(component);
}
public Component getChild(int index) {
return _children.get(index);
}
@Override
public void operation() {
out.println("Composite with " + _children.size() + " child(ren).");
}
@Override
public void show() {
out.println(name);
for (Component component : _children) {
component.show();
}
}
}
public class Leaf extends Component {
public Leaf(string name) {
super(name);
}
@Override
public void operation() {
out.println("Leaf.");
}
@Override
public void show() {
out.println(name);
}
}
class Component {
protected:
const string name;
public:
Component(string _name) : name(_name) {
}
virtual void Operation() = 0;
virtual void Show() = 0;
};
class Composite : public Component {
private:
list<Component*> _children;
public:
Composite(string name) : Component(name) {
}
void AddChild(Component* component) {
_children.push_back(component);
}
void RemoveChild(Component* component) {
_children.remove(component);
}
Component* GetChild(int index) {
int i = 0;
for (list<Component*>::iterator it = _children.begin(); it != _children.end(); ++it) {
if (i++ == index) {
return (*it);
}
}
return NULL;
}
void Operation() {
cout << "Composite with " << _children.size() << " child(ren).";
}
void Show() {
cout << name;
for (list<Component*>::iterator it = _children.begin(); it != _children.end(); ++it) {
(*it)->Show();
}
}
};
class Leaf : public Component {
public:
Leaf(string name) : Component(name) {
}
void Operation() {
cout << "Leaf.";
}
void Show() {
cout << name;
}
};
Snapshot
The aggregate pattern
- Quite similar to the composite pattern is the aggregate pattern
- This pattern has emerged from Domain-Driven Design (DDD)
- Instead of treating objects as individuals we consider a single item
- One object is therefore called the aggregate root, which handles all calls
- The root ensures the integrity of the aggregate as a whole
- These aggregates should not be confused with collections
- Collections are generic, aggregates are specialized and might contain additional fields or multiple collections
Differences
- The aggregate pattern is based on composition
- The composite pattern is based on aggregation
- That being said, it should be obvious that the constructor of an aggregate requires the root object, since its existence is bound to a root
- An aggregate consists of 1..n objects
- A composite consists of 0..n objects
- The aggregate is destroyed when the root object is disposed
Practical considerations
- An explicit parent reference simplifies moving up the tree
- It makes sense to define the parent inside the component class
- Components might be shared unless more than one parent is possible
- If the number of children is small it can make sense to put the list with children inside the component class
- Caching might be useful if searching for children is expensive
- The composite class might contain any data structure from linked lists to trees, arrays and hash tables
Literature
- Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John (1995). Design Patterns: Elements of Reusable Object Oriented Software.
- Freeman, Eric; Freeman, Elisabeth; Kathy, Sierra; Bert, Bates (2004). Head First Design Patterns.
- Hannemann, Jan (2002). Design pattern implementation in Java and AspectJ.
- Fowler, Martin (2006). Writing Software Patterns.
- Liskov, Barbara; Guttag, John (2000). Program Development in Java: Abstraction, Specification, and Object-Oriented Design.