What
An Abstract factory pattern is a type of creational design pattern. This pattern supports creation of objects related to families of products. Here, the family is a representation of high-level product requirements. An abstract factory hides all details related to low-level concrete classes. Basically, this will only expose one interface to user for creation of desired products.
The abstract factory offers another abstraction layer over the factory method design pattern. This abstraction layer finally represents high-level product requirements. Therefore, the client need not worry about low-level implementation details.
Why
In order to understand Abstract Factory in detail lets try to take an example of creating a Computer class.
As, all of us know, a computer consists of various components, such as, a monitor, a CPU, mouse ,keyboard, etc. embedded inside it. Internally, each component may consist of several implementations or “Types”. This is depicted in below Table-
Serial No. | Component | Types |
1 | Monitor | Flat Curved Touch |
2 | CPU | Single Core Dual Core Quad Core |
3 | Mouse | Wired Wireless Bluetooth Wireless Wifi |
4 | Keyboard | Wired Wireledd Bluetooth Wireless Wifi |
Design a Computer Class: without an Abstract factory design pattern
For the sake of simplicity and understanding, as of now, we assume the computer contains only 2 components. i.e., a Monitor and a Cpu. This is explained in below C++ example –
#include<iostream> //main header
using namespace std;//for namespace
enum MonitorType //available monitors
{
flatMonitor,
touchMonitor
};
enum CpuType //available CPUs
{
singleCore,
dualCore
};
class Monitor
{
public:
virtual void monitorSelected()
{
cout<<" Base Monitor"<<endl;
};
};
class FlatMonitor:public Monitor
{
public:
void monitorSelected()
{
cout<<" Flat Monitor"<<endl;
};
};
class TouchMonitor:public Monitor
{
void monitorSelected()
{
cout<<" Touch Monitor"<<endl;
};
};
class Cpu
{
public:
virtual void cpuSelected()
{
cout<<" Base Cpu"<<endl;
};
};
class SingleCore:public Cpu
{
public:
void cpuSelected()
{
cout<<" Single Monitor"<<endl;
};
};
class DualCore:public Cpu
{
void cpuSelected()
{
cout<<" Touch Monitor"<<endl;
};
};
class computer //creates all types of computers
{
public:
Monitor* createMonitor(MonitorType monitorType)
{
if(monitorType == flatMonitor)
{
return new FlatMonitor;
}
else if(monitorType == touchMonitor)
{
return new TouchMonitor;
}
}
Cpu* createCpu(CpuType cpuType)
{
if(cpuType == singleCore)
{
return new SingleCore;
}
else if(cpuType == dualCore)
{
return new DualCore;
}
}
};
int main()
{
//Client-Code
computer *c1 = new computer;
Monitor *m1 =c1->createMonitor(flatMonitor);
Cpu *p1 = c1->createCpu(singleCore);
m1->monitorSelected();
p1->cpuSelected();
}
Problem with above approach
In the above example, the User or Client is interacting with raw interfaces of individual components. The Client needs to understand the internal details of the product and selects them accordingly. Therefore, all responsibility of compatibility of compontents lies with the client himself. The client must check if the components and types belong to one family.
Client shall adapt his code in case, when any items found to be not related to each other. Example, there may be a possibility that a curved monitor may not deliver good performance with single core CPU. Therefore, client code has to ensure that they use only compatible combination of low-level computer components. In other words, the client must know which component classes shall constitute a computer. This is too much getting in detail!
For cases, where we have families of product, an Abstract factory design pattern is the best fit. This is because, the Abstract design pattern shall hide concrete classes details inside.
How
Lets try to adapt above Computer class by using Abstract factory pattern.
For enabling this, we need to follow specific rules:
- Hide concrete classes of components behind concrete factory classes.
- The factories shall create different products. Internally, by using specific combination of concrete components.
- Let user select or create factories of its choice instead of creating all product in detail.
For Computer class, we will now create factories as depicted in following table:
S.No. | Factory | Details |
1 | Basic Computer Factory | Monitor-Flat Cpu– Single Core Mouse -Wired keyboard-Wired |
2 | Smart Computer Factory | Monitor-Curved Cpu– Dual Core Mouse -Wireless Bluetooth keyboard-Wireless Bluetooth |
3 | Super Computer Factory | Monitor-Touch Cpu– QuadCore Mouse -Wireless wifi keyboard-Wireless Wifi |
So, now we will be having above factories for Computer Class. Ultimately, the client needs to select only specific factory as per their requirements.
Computer Class: Using Abstract Factory design pattern
#include<iostream> //main header
using namespace std;//for namespace
enum MonitorType //Available Monitors
{
flatMonitor,
touchMonitor
};
enum CpuType //Available CPUs
{
singleCore,
dualCore,
quadCore
};
enum ComputerType //Represent Product Requirements
{
BasicComputer,
SmartComputer,
SuperComputer
};
class Monitor
{
public:
virtual void monitorSelected()
{
cout<<" Base Monitor"<<endl;
};
};
class FlatMonitor:public Monitor
{
public:
void monitorSelected()
{
cout<<" Flat Monitor"<<endl;
};
};
class CurvedMonitor:public Monitor
{
public:
void monitorSelected()
{
cout<<" Curved Monitor"<<endl;
};
};
class TouchMonitor:public Monitor
{
void monitorSelected()
{
cout<<" Touch Monitor"<<endl;
};
};
class Cpu
{
public:
virtual void cpuSelected()
{
cout<<" Base Cpu"<<endl;
};
};
class SingleCore:public Cpu
{
public:
void cpuSelected()
{
cout<<" Single Core Cpu"<<endl;
};
};
class DualCore:public Cpu
{
void cpuSelected()
{
cout<<" Dual Core Cpu"<<endl;
};
};
class QuadCore:public Cpu
{
void cpuSelected()
{
cout<<" Quad Core Cpu"<<endl;
};
};
class ComputerFactory
{
public:
static ComputerFactory* createComputerFactory
(ComputerType computerType);
// factory pattern
virtual void createComputer()=0;
};
class BasicComputerFactory:public ComputerFactory
{
public:
void createComputer()
{
Monitor *m1 = new FlatMonitor();
m1->monitorSelected();
Cpu *c1 = new SingleCore();
c1->cpuSelected();
}
};
class SmartComputerFactory:public ComputerFactory
{
public:
void createComputer()
{
Monitor *m1 = new CurvedMonitor();
m1->monitorSelected();
Cpu *c1 = new DualCore();
c1->cpuSelected();
}
};
class SuperComputerFactory:public ComputerFactory
{
public:
void createComputer()
{
Monitor *m1 = new TouchMonitor();
m1->monitorSelected();
Cpu *c1 = new QuadCore();
c1->cpuSelected();
}
};
ComputerFactory* ComputerFactory::createComputerFactory
(ComputerType computerType)
// factory pattern
{
switch(computerType)
{
case BasicComputer:
return new BasicComputerFactory;
case SmartComputer:
return new SmartComputerFactory;
case SuperComputer:
return new SuperComputerFactory;
};
};
int main()
{
//Client only working at Product Requirement-Level
ComputerFactory *c1 =
ComputerFactory::createComputerFactory
(SuperComputer);
// Factory pattern
c1->createComputer();
}
As the above code now works with Abstract factory. Therefore, the client do not need to bother about internals of product details. Client only shall select the high level requirement using the desired factory. Therefore, the relevant factory shall automatically creates all the products inside.
Abstract Factory design pattern – Pros & Cons
Pros
- Responsibility and ownership lies with the Abstract factory. Hence, User or client need not bother about product details.
- Gives flexibility for future enhancements. Therefore, its easy to create more factories without impacting Users.
- Reduces coupling between User and Product’s internals.
Cons
- Abstract factory is one of complex design pattern due to so many interfaces.
- The user consumes only existing factories. This means, a random combination of components is not allowed.