Abstract Factory Design Pattern: Creational Pattern

Share the Article

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.ComponentTypes
1MonitorFlat
Curved
Touch
2CPUSingle Core
Dual Core
Quad Core
3MouseWired
Wireless Bluetooth
Wireless Wifi
4KeyboardWired
Wireledd Bluetooth
Wireless Wifi
Computer Components

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:

  1. Hide concrete classes of components behind concrete factory classes.
  2. The factories shall create different products. Internally, by using specific combination of concrete components.
  3. 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.FactoryDetails
1Basic Computer FactoryMonitor-Flat
Cpu– Single Core
Mouse -Wired
keyboard-Wired
2Smart Computer FactoryMonitor-Curved
Cpu– Dual Core
Mouse -Wireless Bluetooth
keyboard-Wireless Bluetooth
3Super Computer FactoryMonitor-Touch
Cpu– QuadCore
Mouse -Wireless wifi
keyboard-Wireless Wifi
Computer Factory

So, now we will be having above factories for Computer Class. Ultimately, the client needs to select only specific factory as per their requirements.

Abstract Factory UML diagram for creating computer class

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

  1. Responsibility and ownership lies with the Abstract factory. Hence, User or client need not bother about product details.
  2. Gives flexibility for future enhancements. Therefore, its easy to create more factories without impacting Users.
  3. 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.

Main Funda: The abstract factory enables client to not worry about low-level component details.

Advanced C++ Topics

Class Template Argument Deduction in C++17
What is a Tuple, a Pair and a Tie in C++
C++ Multithreading: Understanding Threads
What is Copy Elision, RVO & NRVO?
Lambda in C++11
Lambda in C++17
std::chrono in C++ 11
Thread Synchronization with Mutex
Template type deduction in functions
How std::forward( ) works?
How std::move() function works?
What is reference collapsing?

Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *