Credit: Concepts explained here are based on a book, “増補改訂版Java言語で学ぶデザインパターン入門”.
In an abstract factory pattern, different abstract components are combined to create an abstract product.
Let’s see a sample program which creates a HTML file. There are two packages: factory package and listfactory package. A factory package contains classes that create an abstract factory, components and products. A listfactory package contains classes that create a concrete factory, components and products.
Item class is a superclass of Link class and Tray class which are defined next. Its abstract method, make_html, is to return strings of HTML.
# item.py
from abc import ABC, abstractmethod
class Item(ABC):
def __init__(self, caption):
self._caption = caption
@abstractmethod
def make_html(self):
pass
Link class represents a hyperlink in HTML.
# link.py
from factory.item import Item
class Link(Item):
def __init__(self, caption, url):
super().__init__(caption)
self._url = url
Tray class represents a tray that holds a list of Link instances or Tray instances themselves.
# tray.py
from factory.item import Item
class Tray(Item):
def __init__(self, caption):
super().__init__(caption)
self._tray = []
def add(self, item):
self._tray.append(item)
A page class represents a HTML file. An add method appends an Item instance (Link instance or Tray instance) to a content attribute. An output method writes a content of HTML into a file.
# page.py
from abc import ABC, abstractmethod
class Page(ABC):
def __init__(self, title, author):
self._title = title
self._author = author
self._content = []
def add(self, item):
self._content.append(item)
def output(self):
filename = f"{self._title}.html"
with open(filename, "w") as f:
f.write(self.make_html())
print(f"{filename} is created.")
@abstractmethod
def make_html(self):
pass
Factory class returns a concrete factory instance.
# factory.py
from factory.helper import Class
from abc import ABC, abstractmethod
class Factory(ABC):
@classmethod
def get_factory(cls, class_name):
try:
factory = Class(class_name).new_instance()
except Exception as e:
raise Exception(e)
return factory
@abstractmethod
def create_link(self, caption, url):
pass
@abstractmethod
def create_tray(self, caption):
pass
@abstractmethod
def create_page(self, title, author):
pass
Now let’s define a main file which builds an abstract component and constructs an abstract product by using an abstract factory. For example, in oder to use ListFactory class, the following command is used: python main.py listfactory.ListFactory
# main.py
from factory import Factory
import sys
def main(*args):
try:
factory = Factory.get_factory(args[0][1])
except IndexError:
raise Exception("No class argument is given.")
assert isinstance(factory, Factory)
nytimes = factory.create_link("New York Times", "https://www.nytimes.com/")
financial_times = factory.create_link("Financial Times", "https://www.ft.com/")
yahoo_us = factory.create_link("Yahoo!", "https://www.yahoo.com/")
yahoo_jp = factory.create_link("Yahoo!Japan", "https://www.yahoo.co.jp/")
instagram = factory.create_link("Instagram", "https://www.instagram.com/")
twitter = factory.create_link("Twitter", "https://twitter.com/")
tray_news = factory.create_tray("News")
tray_news.add(nytimes)
tray_news.add(financial_times)
tray_yahoo = factory.create_tray("Yahoo!")
tray_yahoo.add(yahoo_us)
tray_yahoo.add(yahoo_jp)
tray_sns = factory.create_tray("SNS")
tray_sns.add(instagram)
tray_sns.add(twitter)
page = factory.create_page("LinkPage", "Mr.X")
page.add(tray_news)
page.add(tray_yahoo)
page.add(tray_sns)
page.output()
if __name__ == "__main__":
main(sys.argv)
We now define concrete classes. First one is ListFactory class, a subclass of Factory class. This class creates an instance of ListLink class, ListTray class and ListPage class.
# list_factory.py
from factory import Factory
from listfactory.list_link import ListLink
from listfactory.list_page import ListPage
from listfactory.list_tray import ListTray
class ListFactory(Factory):
def create_link(self, caption, url):
return ListLink(caption, url)
def create_tray(self, caption):
return ListTray(caption)
def create_page(self, title, author):
return ListPage(title, author)
The second one is ListLink class which extends Link class and overwrites an abstract method, make_html, to create a segment of html.
# list_link.py
from factory import Link
class ListLink(Link):
def __init__(self, caption, url):
super().__init__(caption, url)
def make_html(self):
return f" <li><a href='{self._url}'>{self._caption}<a></li>\n"
The next one is ListTray class which makes HTML by combining each ListLink instance.
# list_tray.py
from factory import Tray
from listfactory.string_buffer import StringBuffer
class ListTray(Tray):
def __init__(self, caption):
super().__init__(caption)
def make_html(self):
buffer = StringBuffer()
buffer.append("<li>\n")
buffer.append(f"{self._caption}\n")
buffer.append("<ul>\n")
for item in self._tray:
buffer.append(item.make_html())
buffer.append("</ul>\n")
buffer.append("</li>\n")
return buffer.to_string()
The last one is ListPage class which combines a list of items and returns strings used in HTML.
# list_page.py
from factory import Page, Item
from listfactory.string_buffer import StringBuffer
class ListPage(Page):
def __init__(self, title, author):
super().__init__(title, author)
def make_html(self):
buffer = StringBuffer()
buffer.append(f"<html><head><title>{self._title}</title></head>\n")
buffer.append(f"<body>\n")
buffer.append(f"<h1>{self._title}</h1>\n")
buffer.append("<ul>\n")
for item in self._content:
assert isinstance(item, Item)
buffer.append(item.make_html())
buffer.append("</ul>\n")
buffer.append(f"<hr><address>{self._author}</address>")
buffer.append("</body></html>\n")
return buffer.to_string()
When you run this program: python main.py listfactory.ListFactory , LinkPage.html is created.
# LinkPage.html
Yahoo!
<ul>
<li><a href='https://www.yahoo.com/'>Yahoo!<a></li>
<li><a href='https://www.yahoo.co.jp/'>Yahoo!Japan<a></li>
</ul>
</li>
<li>
SNS
<ul>
<li><a href='https://www.instagram.com/'>Instagram<a></li>
<li><a href='https://twitter.com/'>Twitter<a></li>
</ul>
</li>
</ul>
<hr><address>Mr.X</address> </body></html>
One benefit of Abstract Factory pattern: it is easy to add a concrete factory. When you need to create a new concrete factory along with subclasses of Factory, Link Tray and Page classes, there is no need to modify any source code in either AbstractFactory and Main class (main.py).