Software Design Patterns Dhanush P
1MS10IS033
Design Patterns?
- Design patterns are a "solution to a problem in a context".
- It can also be defined as "a proven solution to a recurring problem".
Software architects develop patterns inorder to solve software problems that are encountered by them. they exactly know which pattern to use by carefully studying the requirements of the problem and the existing situation. This knowledge is gained to individuals by time and practice.
Why do require design patterns?
There may be many reasons and requirements to use patterns. a few of them are
- Design patterns help in designing models more quickly because the patterns predicte for software architects what ought to be there. They tell what the essential objects are and what to pay special attention to.
- Using patterns developer can communicate much more effectively with experts because they would have a more structured way to deal with the details and exceptions.
- Τhe patterns allows to develop better end-user training for the system under development because the patterns predicted the most important features of the system.
- Reuse solutions—By reusing already established designs, we get a head start on my problems and avoid gotchas. We get the benefit of learning from the experience of others. We do not have to reinvent solutions for commonly recurring problems.
- Establish common terminology—Communication and teamwork require a common base of vocabulary and a common viewpoint of the problem. Design patterns provide a common point of reference during the analysis and design phase of a project.
Key features of a pattern
The following key features are required to describe a pattern
Item | Description |
Name | All patterns have a unique name that identifies them |
Intent | The purpose of the pattern |
Problem | The problem that the pattern is trying to solve |
Solution | How the pattern provides a solution to the problem in the context in which it shows up |
Participants and Collaborators | The entities involved in the pattern |
Consequences | The consequences of using the pattern. Investigates the forces at play in the pattern |
Implementation | How the pattern can be implemented |
Although there are many design patterns we discuss only the basic and the frequently used patterns known as the "Gang of Four" patterns. They have been explained below along with examples.
The Facade Pattern
Intent
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.
Basically, this is saying that we need a new way to interact with a system that is easier than the current way, or we need to use the system in a particular way
Standard, simplified view of the Facade pattern.
Key features
Intent | You want to simplify how to use an existing system. You need to define your own interface. |
Problem | You need to use only a subset of a complex system. Or you need to interact with the system in a particular way |
Solution | The Facade presents a new interface for the client of the existing system to use |
Participants and Collaborators | It presents a specialized interface to the client that makes it easier to use |
Consequences | The Facade simplifies the use of the required subsystem. However, since the Facade is not complete, certain functionality may be unavailable to the client |
Implementation | • Define a new class (or classes) that has the required interface. • Have this new class use the existing system |
C++ sample code implementing the facade pattern
class A {
private B b; // Class A uses Class B, the "interface"
public int f() { return b.g(); }
};
class B {
private C c; // class B uses class C, a "subsystem"
private ... ...; // other subsystems can be added
public int g() { c.h(); return c.i(); }
};
class C { // a subsystem
public void h() { ... }
public int i() { return x; }
};
The Adapter Pattern
Intent
Convert the interface of a class into another interface that the clients expect. Adapter lets classes work together that could not otherwise because of incompatible interfaces.
Basically, this is saying that we need a way to create a new interface for an object that does the right stuff but has the wrong interface.
Key Features
Intent | Match an existing object beyond your control to a particular interface |
Problem | A system has the right data and behavior but the wrong interface. Typically used when you have to make something a derivative of an abstract class we are defining or already have. |
Solution | The Adapter provides a wrapper with the desired interface |
Participants and Collaborators | The Adapter adapts the interface of an Adaptee to mat ch that of the Adapter's Target (the class it derives from). This allows the Client to use the Adaptee as if it were a type of Target. |
Consequences | The Adapter pattern allows for preexisting objects to fit into new class structures without being limited by their interfaces. |
Implementation | Contain the existing class in another class. Have the containing class match the required interface and call the methods of the contained class. |
Standard and simplified view of adapter pattern
C++ code implementing the adapter pattern
typedef int Coordinate;
typedef int Dimension;
// Desired interface
class Rectangle
{
public:
virtual void draw() = 0;
};
// Legacy component
class LegacyRectangle
{
public:
LegacyRectangle(Coordinate x1, Coordinate y1, Coordinate x2, Coordinate y2)
{
x1_ = x1;
y1_ = y1;
x2_ = x2;
y2_ = y2;
cout << "LegacyRectangle: create. (" << x1_ << "," << y1_ << ") => ("
<< x2_ << "," << y2_ << ")" << endl;
}
void oldDraw()
{
cout << "LegacyRectangle: oldDraw. (" << x1_ << "," << y1_ <<
") => (" << x2_ << "," << y2_ << ")" << endl;
}
private:
Coordinate x1_;
Coordinate y1_;
Coordinate x2_;
Coordinate y2_;
};
// Adapter wrapper
class RectangleAdapter: public Rectangle, private LegacyRectangle
{
public:
RectangleAdapter(Coordinate x, Coordinate y, Dimension w, Dimension h):
LegacyRectangle(x, y, x + w, y + h)
{
cout << "RectangleAdapter: create. (" << x << "," << y <<
"), width = " << w << ", height = " << h << endl;
}
virtual void draw()
{
cout << "RectangleAdapter: draw." << endl;
oldDraw();
}
};
int main()
{
Rectangle *r = new RectangleAdapter(120, 200, 60, 40);
r->draw();
}
The Bridge Pattern
Intent
According to the gang of four the intent of bridge pattern is to “De-couple an abstraction from its implementation so that the two can vary independently".
- De-couple means to have things behave independently from each other or at least explicitly state what the relationship is, and
- Abstraction is how different things are related to each other conceptually.
Key Features
Intent | Decouple a set of implementations from the set of objects using them. |
Problem | The derivations of an abstract class must use multiple implementations without causing an explosion in the number of classes. |
Solution | Define an interface for all implementations to use and have the derivations of the abstract class use that. |
Participants and Collaborators | The Abstraction defines the interface for the objects being implemented. The Implementor defines the interface for the specific implementation classes. Classes derived from the Abstraction use classes derived from the Implementor without knowing which particular Concrete Implementor is in use. |
Consequences | The decoupling of the implementations from the objects that use them increases extensibility. Client objects are not aware of implementation issues. |
Implementation | • Encapsulate the implementations in an abstract class. • Contain a handle to it in the base class of the abstraction being implemented. |
Standard and simplifies view of bridge pattern
C++ code implementing the bridge pattern
class Program
{
static void Main(string[] args)
{
IAppliance tv = new TV("Bedroom TV"); //implementation object
IAppliance vacuum = new VaccumCleaner
("My Vacuum Cleaner"); //implementation object
Switch s1 = GetSwitch(tv); //convert to abstraction
Switch s2 = GetSwitch(vaccum); //convert to abstraction
//*** client code works only with the abstraction objects,
//not the implementation objects ***
s1.TurnOn();
s2.TurnOn();
}
//convert implementation object to abstraction object
static Switch GetSwitch(IAppliance a)
{
return new RemoteControl(a);
}
}
//the abstraction
public abstract class Switch
{
protected IAppliance appliance;
public abstract void TurnOn();
}
//the implementation
public interface IAppliance
{
void Run();
}
//concrete abstraction
public class RemoteControl : Switch
{
public RemoteControl(IAppliance i)
{
this.appliance = i;
}
public override void TurnOn()
{
appliance.Run();
}
}
//concrete implementation
public class TV : IAppliance
{
private string name;
public TV(string name)
{
this.name = name;
}
void IAppliance.Run()
{
Console.WriteLine(this.name + " is running");
}
}
//concrete implementation
public class VaccumCleaner : IAppliance
{
private string name;
public VaccumCleaner(string name)
{
this.name = name;
}void IAppliance.Run()
{
Console.WriteLine(this.name + " is running");
}
}
The Abstract Factory Pattern
Intent
To to provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Sometimes, several objects need to be instantiated in a coordinated fashion. For example, when dealing with user interfaces, the system might need to use one set of objects to work on one operating system and another set of objects to work on a different operating system. The Abstract Factory pattern ensures that the system always gets the correct objects for the situation.
Key Features
Intent | You want to have families or sets of objects for particular clients. |
Problem | Families of related objects need to be instantiated. |
Solution | Coordinates the creation of families of objects. Gives a way to take the rules of how to perform the instantiation out of the client object that is using these created objects.. |
Participants and Collaborators | The AbstractFactory defines the interface for how to create each member of the family of objects required. Typically, each family is created by having its own unique ConcreteFactory. |
Consequences | The pattern isolates the rules of which objects to use from the logic of how to use these objects. |
Implementation | Define an abstract class that specifies which objects are to be made. Then implement one concrete class for each family. Tables or fi les can also be used to accomplish the same thing. |
Standard and simplified view of Abstract factory pattern
C++ code implementing the Abstract factory pattern
#include <iostream>
#include <string>
using namespace std;
// Abstract base class
class Mobile {
public:
virtual string Camera() = 0;
virtual string KeyBoard() = 0;
void PrintSpecs() {
cout << Camera() << endl;
cout << KeyBoard() << endl;
}
};
// Concrete classes
class LowEndMobile : public Mobile {
public:
string Camera() {
return "2 MegaPixel";
}
string KeyBoard() {
return "ITU-T";
}
};
// Concrete classes
class HighEndMobile : public Mobile {
public:
string Camera() {
return "5 MegaPixel";
}
string KeyBoard() {
return "Qwerty";
}
};
// Abstract Factory returning a mobile
class MobileFactory {
public:
Mobile* GetMobile(string type);
};
Mobile* MobileFactory::GetMobile(string type) {
if ( type == "Low-End" ) return new LowEndMobile();
if ( type == "High-End" ) return new HighEndMobile();
return NULL;
}
void main()
{
MobileFactory* myFactory = new MobileFactory();
Mobile* myMobile1 = myFactory->GetMobile("Low-End");
myMobile1->PrintSpecs();
Mobile* myMobile2 = myFactory->GetMobile("High-End");
myMobile2->PrintSpecs();
}
The Strategy Pattern
Intent
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
The Strategy pattern is based on a few principles:
- Objects have responsibilities.
- Different, specific implementations of these responsibilities are manifested through the use of polymorphism.
- There is a need to manage several different implementations of what is, conceptually, the same algorithm.
- It is a good design practice to separate behaviors that occur in the problem domain from each other— that is, to decouple them. This allows me to change the class responsible for one behavior without adversely affecting another.
Key Features
Intent | Allows you to use different business rules or algorithms depending upon the context in which they occur. |
Problem | The selection of an algorithm that needs to be applied depends upon the client making the request or the data being acted upon. If you simply have a rule in place that does not change, you do not need a Strategy pattern.. |
Solution | Separates the selection of algorithm from the implementation of the algorithm. Allows for the selection to be made based upon context. |
Participants and Collaborators | • The strategy specifies how the different algorithms are used. Collaborators • The concreteStrategies implement these different algorithms. • The Context uses the specific ConcreteStrategy with a reference of type Strategy. The strategy and Context interact to implement the chosen algorithm (sometimes the strategy must query the Context). The Context forwards requests from its Client to the Strategy. |
Consequences | • The Strategy pattern defines a family of algorithms. • Switches and/or conditionals can be eliminated. • You must invoke all algorithms in the same way (they must all have the same interface). The interaction between the ConcreteStrategies and the Context may require the addition of getstate type methods to the Context. |
Implementation | Have the class that uses the algorithm (the Context) contain an abstract class (the stragegy) that has an abstract method specifying how to call the algorithm. Each derived class implements the algorithm as needed. Note: this method wouldn't be abstract if you wanted to have some default behavior. |
Standard and simplified structure of strategy pattern
C++ code implementing strategy pattern
class Interface { //this class and all sorting clases could be templated to be able to deal with sorting lists of different data types
std::unique_ptr<ISort> sorter_;
public:
Interface():sorter_(new CQuickSorter()){ //CQuickSorter is the default sorter
}
template<typename T>
setSorter(){ //one could also use perfect forwarding to pass arguments to T's constructor
sorter_.reset(new T());
}
void sort(std::list<string> &list){
sorter_->sort(list);
}
};
int main(){
std::list<int> li;
Interface cn;
cn.sort(li); //using a default sort
cn.setSorter<CBubbleSort>();
cn.sort(li); //using bubble sort
}
Decorator Pattern
Intent
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
The Decorator pattern works by allowing me to create a chain of objects that starts with the decorator objects— the objects responsible for the new function— and ends with the original object.
Key Features
Intent | Attach additional responsibilities to an object dynamically. |
Problem | The object that you want to use does the basic functions you require. However, you may need to add some additional functionality to the object, occurring before or after the object's base functionality. Note that the Java foundation classes use the Decorator pattern extensively for I/O handling. |
Solution | Allows for extending the functionality of an object without resorting to subclassing. |
Participants and Collaborators | The ConcreteComponent is the class having function added to it by the Decorators. Sometimes classes derived from ConcreteComponent are used to provide the core functionality, in which case Concrete-Component is no longer concrete, but rather abstract. The Component defines the interface for all of these classes to use.. |
Consequences | Functionality that is to be added resides in small objects. The advantage is the ability to dynamically add this function before or after the functionality in the ConcreteComponent. Note: While a decorator may add its functionality before or after that which it decorates, the chain of instantiation always ends with the ConcreteComponent. |
Implementation | Create an abstract class that represents both the original class and the new functions to be added to the class. In the decorators, place the new function calls before or after the trailing calls to get the correct order. |
Standard and simplified view of decorator pattern
Java code implementing decorator pattern
class SalesTicket extends Component
{ public void prtTicket () {
// sales ticket printing code here
}
abstract class Decorator extends Component
{ private Component myComp; public Decorator
(Component myC) { myComp= myC;
}
public void prtTicket () { if
(myComp != null)
myComp.prtTicket(); }
}
class Headerl extends Decorator
{ public void prtTicket () {
// place printing header 1 code here
super.prtTicket(); } }
class Header2 extends Decorator
{ public void prtTicket () {
// place printing header 2 code here
super.prtTicket(); } }
class Footerl extends Decorator
{ public void prtReport ()
{ super.prtTicket();
// place printing footer 1 code here } }
class Footer2 extends Decorator
{ public void prtReport ()
{ super.prtTicket();
// place printing footer 2 code here } }
class SalesOrder { void
prtTicket () {
Component myST;
// Get chain of Decorators and SalesTicket built by
// another object that knows the rules to use.
// This may be done in constructor instead of
// each time this is called.
myST= Configuration.getSalesTicket()
// Print Ticket with headers and footers as needed
myST.prtTicket 0 ; } }
The Singleton Pattern
Intent
Ensure a class only has one instance, and provide a global point of access to it.
The Singleton pattern works by having a special method that is used to instantiate the desired object. When this method is called, it checks to see if the object has already been instantiated. If it has, the method simply returns a reference to the object. If not, the method instantiates it and returns a reference to the new instance.
To ensure that this is the only way to instantiate an object of this type, I define the constructor of this class to be protected or private.
Standard and simplified structure of singleton pattern
Key Features
Intent | You want to have only one of an object but there is no global object that controls the instantiation of this object. |
Problem | Several different client objects need to refer to the same thing and you want to ensure that you do not have more than one of them. |
Solution | Guarantees one instance. |
Participants and Collaborators | Clients create an get Instance of the Singleton solely through the instance method. |
Consequences | Clients need not concern themselves whether an instance of the Singleton exists. This can be controlled from within the Singleton.. |
Implementation | • Add a private static member of the class that refers to the desired object (initially, it is NULL). • Add a public static method that instantiates this class if this member is NULL (and sets this member's value) and then returns the value of this member. • Set the constructor's status to protected or private so that no one can directly instantiate this class and bypass the static constructor mechanism. |
C++ code implementing singleton pattern
Class USTax
{ public:
static USTax* getlnstance();
private:
USTax(};
static USTax* instance;
}
USTax::USTax ()
{ instance= 0;
}
USTax* USTax::getlnstance () { if
(instance== 0) {
instance= new USTax;
} return
instance;
}
The Observer Pattern
Intent
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Standard and simplified structure of observer pattern
Key Features
Intent | Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. |
Problem | You need to notify a varying list of objects that an event has occurred. |
Solution | Observers delegate the responsibility for monitoring for an event to a central object: the Subject. |
Participants and Collaborators | The Subject knows its Observers because the Observers register with it. The Subject must notify the Observers when the event in question occurs. The Observers are responsible both for registering with the Subject and for getting the information from the Subject when notified. |
Consequences | Subjects may tell Observers about events they do not need to know if some Observers are interested in only a subset of events. Extra communication may be required if Subjects notify Observers which then go back and request additional information. |
Implementation | • Have objects (Observers) that want to know when an event happens attach themselves to another object (Sub j ect) that is watching for the event to occur or that triggers the event itself. • When the event occurs, the Subject tells the Observers that it has occurred. • The Adapter pattern is sometimes needed to be able to implement the Observer interface for all of the Observer-type objects. |
C++ code implementing observer pattern
//observer.h
#ifndef OBSERVER_H_INCLUDED
#define OBSERVER_H_INCLUDED
#include <string>
class Publisher;
class Observer {
public:
virtual void eventOnProperty(Publisher* source,
std::string event_name, std::string value) = 0;
};
//publisher.h
#ifndef PUBLISHER_H_INCLUDED
#define PUBLISHER_H_INCLUDED
#include "Observer.h"
#include <list>
#include <string>
class Publisher {
public:
void notify_observers(std::string, std::string);
void add_observer(Observer*);
protected:
std::list<Observer*> my_observers;
};
publisher.cpp
#include "Publisher.h"
#include "Observer.h"
#include <string>
#include <list>
void Publisher::notify_observers(std::string event_name, std::string value) {
std::list<Observer*>::iterator it;
for(it = my_observers.begin(); it != my_observers.end(); it++) {
(*it)->eventOnProperty(this, event_name, value);
}
}
void Publisher::add_observer(Observer* obs) {
my_observers.push_back(obs);
}
C++ code implementing the factory method pattern
#include <iostream>
//observer.h
#ifndef OBSERVER_H_INCLUDED
#define OBSERVER_H_INCLUDED
#include <string>
class Publisher;
class Observer {
public:
virtual void eventOnProperty(Publisher* source,
std::string event_name, std::string value) = 0;
};
//publisher.h
#ifndef PUBLISHER_H_INCLUDED
#define PUBLISHER_H_INCLUDED
#include "Observer.h"
#include <list>
#include <string>
class Publisher {
public:
void notify_observers(std::string, std::string);
void add_observer(Observer*);
protected:
std::list<Observer*> my_observers;
};
publisher.cpp
#include "Publisher.h"
#include "Observer.h"
#include <string>
#include <list>
void Publisher::notify_observers(std::string event_name, std::string value) {
std::list<Observer*>::iterator it;
for(it = my_observers.begin(); it != my_observers.end(); it++) {
(*it)->eventOnProperty(this, event_name, value);
}
}
void Publisher::add_observer(Observer* obs) {
my_observers.push_back(obs);
}
The Template Method Pattern
Intent
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Redefine the steps in
an algorithm without changing the algorithm's structure.
In other words, although there are different methods for connecting and querying Oracle databases and SQL Server databases, they share the same conceptual process. The Template Method gives us a way to capture this common ground in an abstract class while encapsulating the differences in derived classes. The Template
Method pattern is about controlling a sequence common to different processes.
Standard and simplified structured view of template method pattern
Key Features
Intent | Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Redefine the steps in an algorithm without changing the algorithm's structure. |
Problem | There is a procedure or set of steps to follow that is consistent at one level of detail, but individual steps may have different implementations at a lower level of detail. |
Solution | Allows for definition of substeps that vary while maintaining a consistent basic process.. |
Participants and Collaborators | The Template Method consists of an abstract class that defines the basic TemplateMethod (see figure below) classes that need to be overridden. Each concrete class derived from the abstract class implements a new method for the Template. |
Consequences | Templates provide a good platform for code reuse. They also are helpful in ensuring the required steps are implemented. They bind the overridden steps together for each Concrete class, and so should only be used when these variations always and only occur together. |
Implementation | Create an abstract class that implements a procedure using abstract methods. These abstract methods must be implemented in subclasses to perform each step of the procedure. If the steps vary independently, each step may be implemented with a Strategy pattern. |
C++ code implementing the template method pattern
using namespace std;
class Base
{
void a()
{
cout << "a ";
}
void c()
{
cout << "c ";
}
void e()
{
cout << "e ";
}
// 2. Steps requiring peculiar implementations are "placeholders" in base class
virtual void ph1() = 0;
virtual void ph2() = 0;
public:
// 1. Standardize the skeleton of an algorithm in a base class "template method"
void execute()
{
a();
ph1();
c();
ph2();
e();
}
};
class One: public Base
{
// 3. Derived classes implement placeholder methods
/*virtual*/void ph1()
{
cout << "b ";
}
/*virtual*/void ph2()
{
cout << "d ";
}
};
class Two: public Base
{
/*virtual*/void ph1()
{
cout << "2 ";
}
/*virtual*/void ph2()
{
cout << "4 ";
}
};
int main()
{
Base *array[] =
{
&One(), &Two()
};
for (int i = 0; i < 2; i++)
{
array[i]->execute();
cout << '\n';
}
}
The Factory Method Pattern
Intent
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
Standard and simplifies view of factory method pattern
Key Features
Intent | Define an interface for creating an object, but let subclasses decide which class to instantiate. Defer instantiation to subclasses. |
Problem | A class needs to instantiate a derivation of another class, but doesn't know which one. Factory Method allows a derived class to make this decision. |
Solution | A derived class makes the decision on which class to instantiate and how to instantiate it. |
Participants and Collaborators | Product is the interface for the type of object that the Factory Method creates. Creator is the interface that defines the Factory Method. |
Consequences | Clients will need to subclass the Creator class to make a particular ConcreteProduct. |
Implementation | Use a method in the abstract class that is abstract (pure virtual in C++). The abstract class' code refers to this method when it needs to instantiate a contained object but does not know which particular object it needs. |
C++ code implementing the factory method pattern
#include <iostream>
#include <string>
using namespace std;
// Abstract base class
class Mobile {
public:
virtual string Camera() = 0;
virtual string KeyBoard() = 0;
void PrintSpecs() {
cout << Camera() << endl;
cout << KeyBoard() << endl;
}
};
// Concrete classes
class LowEndMobile : public Mobile {
public:
string Camera() {
return "2 MegaPixel";
}
string KeyBoard() {
return "ITU-T";
}
};
// Concrete classes
class HighEndMobile : public Mobile {
public:
string Camera() {
return "5 MegaPixel";
}
string KeyBoard() {
return "Qwerty";
}
};
// Abstract Factory returning a mobile
class MobileFactory {
public:
Mobile* GetMobile(string type);
};
Mobile* MobileFactory::GetMobile(string type) {
if ( type == "Low-End" ) return new LowEndMobile();
if ( type == "High-End" ) return new HighEndMobile();
return NULL;
}
void main()
{
MobileFactory* myFactory = new MobileFactory();
Mobile* myMobile1 = myFactory->GetMobile("Low-End");
myMobile1->PrintSpecs();
Mobile* myMobile2 = myFactory->GetMobile("High-End");
myMobile2->PrintSpecs();
}
Labels: Design Patterns