Design Patterns
- Abstract Factory Pattern
Abstract Factory - Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Abstract Factory pattern lets a class defer instantiation to subclasses. To name the method more descriptively, it can be named as Factory and Product Family Method.
If we want to create a Mac style scroll bar, we can write a code like this:
ScrollBar *sb = new MacScrollBar;But if we're going to make it work across any platform, we should write a code something similar to this:
ScrollBar *sb = guiFactory->createScrollBar();Because guiFactory is an instance of MacFactory class, the createScrollBar returns a new instance of Mac style scrollbar. The MacFactory itself is a subclass of GUIFactory which is an abstract class where general interface for widgets.
So, the product objects here, are widgets.
The instance variable guiFactory is initialized as:
GUIFactory *guiFactory = new MacFactory;
Diagram source: wiki
Here is the example code:
#include <iostream> class Button { public: virtual void paint() = 0; }; class WinButton : public Button { public: void paint (){ std::cout << " Window Button \n"; } }; class MacButton : public Button { public: void paint (){ std::cout << " Mac Button \n"; } }; class ScrollBar { public: virtual void paint() = 0; }; class WinScrollBar : public ScrollBar { public: void paint (){ std::cout << " Window ScrollBar \n"; } }; class MacScrollBar : public ScrollBar { public: void paint (){ std::cout << " Mac ScrollBar \n"; } }; class GUIFactory { public: virtual Button* createButton () = 0; virtual ScrollBar* createScrollBar () = 0; }; class WinFactory : public GUIFactory { public: Button* createButton (){ return new WinButton; } ScrollBar* createScrollBar (){ return new WinScrollBar; } }; class MacFactory : public GUIFactory { public: Button* createButton (){ return new MacButton; } ScrollBar* createScrollBar (){ return new MacScrollBar; } }; int main() { GUIFactory* guiFactory; Button *btn; ScrollBar *sb; guiFactory = new MacFactory; btn = guiFactory->createButton(); btn -> paint(); sb = guiFactory->createScrollBar(); sb -> paint(); guiFactory = new WinFactory; btn = guiFactory->createButton(); btn -> paint(); sb = guiFactory->createScrollBar(); sb -> paint(); return 0; }
The guiFactory object abstracts the process of creating not just MacScrollBar but also scroll bars for any look-and-feel standard. And guiFactory isn't limited to producing scroll bars or buttons. It can manufacture a full range of widgets including menus, text fields etc.
All this is possible because MacFactory is a subclass of GUIFactory, an abstract class that defines a general interface of creating widgets. It includes operations like createScrollBar and createButton for instantiating different kinds of widgets:
class GUIFactory { public: virtual Button* createButton () = 0; virtual ScrollBar* createScrollBar () = 0; };
Subclasses of GUIFactory such as MacFactory or WinFactory implement these operations to return widgets such as MacButton or WinScrollBar that implement a particular look and feel.
Which part of the code should be modified if we want to add iPhone look-and-feel?
Not much as you can see from the code below:
#include <iostream> class Button { public: virtual void paint() = 0; }; class WinButton : public Button { public: void paint (){ std::cout << " Window Button \n"; } }; class MacButton : public Button { public: void paint (){ std::cout << " Mac Button \n"; } }; class iPhoneButton : public Button { public: void paint (){ std::cout << " iPhone Button \n"; } }; class ScrollBar { public: virtual void paint() = 0; }; class WinScrollBar : public ScrollBar { public: void paint (){ std::cout << " Window ScrollBar \n"; } }; class MacScrollBar : public ScrollBar { public: void paint (){ std::cout << " Mac ScrollBar \n"; } }; class iPhoneScrollBar : public ScrollBar { public: void paint (){ std::cout << " iPhone ScrollBar \n"; } }; class GUIFactory { public: virtual Button* createButton () = 0; virtual ScrollBar* createScrollBar () = 0; }; class WinFactory : public GUIFactory { public: Button* createButton (){ return new WinButton; } ScrollBar* createScrollBar (){ return new WinScrollBar; } }; class MacFactory : public GUIFactory { public: Button* createButton (){ return new MacButton; } ScrollBar* createScrollBar (){ return new MacScrollBar; } }; class iPhoneFactory : public GUIFactory { public: Button* createButton (){ return new iPhoneButton; } ScrollBar* createScrollBar (){ return new iPhoneScrollBar; } }; int main() { GUIFactory* guiFactory; Button *btn; ScrollBar *sb; guiFactory = new MacFactory; btn = guiFactory->createButton(); btn -> paint(); sb = guiFactory->createScrollBar(); sb -> paint(); guiFactory = new WinFactory; btn = guiFactory->createButton(); btn -> paint(); sb = guiFactory->createScrollBar(); sb -> paint(); guiFactory = new iPhoneFactory; btn = guiFactory->createButton(); btn -> paint(); sb = guiFactory->createScrollBar(); sb -> paint(); return 0; }
As you see, we didn't have to edit any existing code. All we had to do was just adding the iPhone portion to the existing code!
Output:
Mac Button Mac ScrollBar Window Button Window ScrollBar iPhone Button iPhone ScrollBar
A factory method is simply a normal method call which can return an instance of a class. But they are often used in combination with inheritance such that a derived class overrides the factory method and return an instance of derived class. More often, we implement factories using abstract base classes.
In the following example, we want to render 3D scene using OpenGL, DirectX, oretc. First, we write a base class where derived classes provide the implementation of the pure virtual methods:
#include <iostream> class Renderer { public: virtual ~Renderer() {}; virtual void RenderIt() = 0; }; class OpenGLRenderer : public Renderer { void RenderIt() { std::cout << "OpenGL render \n"; } }; class DirectXRenderer : public Renderer { void RenderIt() { std::cout << "DirectX render \n"; } }; #include <string> class RendererFactory { public: Renderer *createRenderer(const std::string& type) { if(type == "opengl") return new OpenGLRenderer(); else if(type == "directx") return new DirectXRenderer(); else return NULL; } }; int main() { RendererFactory *factory = new RendererFactory(); factory->createRenderer("opengl")->RenderIt(); factory->createRenderer("directx")->RenderIt(); return 0; }
The method createRenderer() cannot return an instance of the specific type Renderer because it's an abstract base class, and cannot be instantiated. It can, however, return instance of derived class. Then, how can we make the derived class? We use the string argument to the methods, Renderer *createRenderer(const std::string& type); , to create an instance of derived class.
We have two concrete class derived from Renderer class:
class OpenGLRenderer : public Renderer {}; class DirectXRenderer : public Renderer {};
Then we provide the implementation of the factory method like this:
Renderer *RendererFactory::createRenderer(const std::string& type) { if(type == "opengl") return new OpenGLRenderer(); else if(type == "directx") return new DirectXRenderer(); else return NULL; }
The design works fine for now. But what should be changed if we want to add another type of renderer? Clearly, we need to modify the RendererFactory class due to its hardcoded knowledge of the available derived classes. Fortunately, in this case, our public API stays, not affected by the addition of renderer. But it means that we cannot add support for new derived class at run time, which means our users cannot add new renderer.
So, let's find more flexible/extensible design.
More to come...
What's the difference between Abstract Factory Pattern and Factory Method?
- Abstract Factory design pattern creates Factory.
- Factory design pattern creates Products.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization