EECE 4520 – Lecture 11
Design Patterns Part I
Roi Yehoshua
Agenda
Design patterns
Creational patterns
Factory method
Abstract factory
Prototype
Builder
Singleton
2 Roi Yehoshua, 2025
What are Design Patterns?
Design patterns are recurring solutions to design problems you see over and over
Promote reusability, extensibility and ease of maintenance
Patterns provide a shared language among developers
Patterns don’t give you code, they give you experience
Each design pattern is described by:
Intent of the pattern briefly describes both the problem and the solution
Motivation further explains the problem and the solution the pattern makes possible
Structure (UML) of classes shows each part of the pattern and how they are related
Code example in a programming language makes it easier to grasp the pattern
3 Roi Yehoshua, 2025
GoF Design Patterns
The 23 Gang of Four (GoF) patterns are considered the foundational design patterns
Divided into 3 groups:
Creational patterns: deal with object creation
Structural patterns: how classes are composed to form large structures
Behavioral patterns: deal with behaviors of objects
4 Roi Yehoshua, 2025
General Principles
Identify the parts of the application that vary and separate them from those that stay
the same. Encapsulate what varies.
Program to an interface, not an implementation
Favor composition over inheritance
Strive for loosely coupled designs between objects
5 Roi Yehoshua, 2025
Creational Design Patterns
Creational patterns provide various object creation mechanisms, which increase
flexibility and reuse of existing code
6 Roi Yehoshua, 2025
Factory Method
Provide an interface for creating objects, without exposing the creation logic to the
client
Lets subclasses decide which type of object to instantiate
Creator
Product
+FactoryMethod() product = FactoryMethod()
+AnOperation()
ConcreteProduct ConcreteCreator
+FactoryMethod() return new ConcreteProduct
7 Roi Yehoshua, 2025
Factory Method Example
Assume that we have a hierarchy of shapes that implement the Shape interface
We would like to allow the user to choose the type of shape to create
To that end, we create a factory class called ShapeFactory that creates a Shape object
based on the information passed to it
8 Roi Yehoshua, 2025
Example: Product Interface
# [Link]
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def draw(self):
pass
9 Roi Yehoshua, 2025
Example: Concrete Products
# [Link]
from shape import Shape
class Circle(Shape):
def draw(self):
print('Drawing a circle')
# [Link]
from shape import Shape
class Rectangle(Shape):
def draw(self):
print('Drawing a rectangle')
# [Link]
from shape import Shape
class Square(Shape):
def draw(self):
print('Drawing a square')
10 Roi Yehoshua, 2025
Example: Factory Class
# shape_factory.py
from shape import Shape
from circle import Circle
from square import Square
from rectangle import Rectangle
class ShapeFactory:
def get_shape(self, shape_type: str) -> Shape:
if shape_type == 'circle':
return Circle()
elif shape_type == 'square':
return Square()
elif shape_type == 'rectangle':
return Rectangle()
raise ValueError('Unknown shape type')
11 Roi Yehoshua, 2025
Example: Main Script
# [Link]
from shape_factory import ShapeFactory Drawing a circle
Drawing a square
factory = ShapeFactory() Drawing a rectangle
shape1 = factory.get_shape('circle')
[Link]()
shape2 = factory.get_shape('square')
[Link]()
shape3 = factory.get_shape('rectangle')
[Link]()
12 Roi Yehoshua, 2025
Factory Method
Pros:
Single Responsibility Principle: you can move the products construction code into one place
Open/Closed Principle: you can introduce new types of products into the program without
breaking existing client code
Cons:
Code may become more complicated
13 Roi Yehoshua, 2025
Abstract Factory
Lets you produce families of related objects without specifying their concrete classes
AbstractFactory
Client
+CreateProductA()
+CreateProductB()
AbstractProductA
ConcreteFactory1 ConcreteFactory2
+CreateProductA() +CreateProductA()
+CreateProductB() +CreateProductB()
ProductA1 ProductA2
AbstractProductB
ProductB1 ProductB2
14 Roi Yehoshua, 2025
Abstract Factory Example
15 Roi Yehoshua, 2025
Example: UI Elements Interface
# [Link]
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def draw(self):
pass
# [Link]
from abc import ABC, abstractmethod
class Checkbox(ABC):
@abstractmethod
def draw(self):
pass
16 Roi Yehoshua, 2025
Example: Windows Concrete Elements
# win_button.py
from button import Button
class WinButton(Button):
def draw(self):
print('Drawing a Windows button')
# win_checkbox.py
from checkbox import Checkbox
class WinCheckbox(Checkbox):
def draw(self):
print('Drawing a Windows checkbox')
17 Roi Yehoshua, 2025
Example: Mac Concrete Elements
# mac_button.py
from button import Button
class MacButton(Button):
def draw(self):
print('Drawing a Mac button')
# mac_checkbox.py
from checkbox import Checkbox
class MacCheckbox(Checkbox):
def draw(self):
print('Drawing a Mac checkbox')
18 Roi Yehoshua, 2025
Example: GUI Factory Interface
# gui_factory.py
from abc import ABC, abstractmethod
from button import Button
from checkbox import Checkbox
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
19 Roi Yehoshua, 2025
Example: Windows UI Factory
# win_factory.py
from gui_factory import GUIFactory
from win_button import WinButton
from win_checkbox import WinCheckbox
class WinFactory(GUIFactory):
def create_button(self) -> WinButton:
return WinButton()
def create_checkbox(self) -> WinCheckbox:
return WinCheckbox()
20 Roi Yehoshua, 2025
Example: Mac UI Factory
# mac_factory.py
from gui_factory import GUIFactory
from mac_button import MacButton
from mac_checkbox import MacCheckbox
class MacFactory(GUIFactory):
def create_button(self) -> MacButton:
return MacButton()
def create_checkbox(self) -> MacCheckbox:
return MacCheckbox()
21 Roi Yehoshua, 2025
Example: Application Configuration
# app_config.py
from gui_factory import GUIFactory
from win_factory import WinFactory
from mac_factory import MacFactory
class AppConfig():
""" Pick the factory type depending on the current configuration or environment settings"""
@staticmethod
def get_factory(os) -> GUIFactory:
if os == 'Windows':
return WinFactory()
elif os == 'Mac':
return MacFactory()
else:
raise ValueError('Unknown operating system')
22 Roi Yehoshua, 2025
Example: Main Script
# [Link]
from app_config import AppConfig Drawing a windows button
Drawing a Windows checkbox
os = 'Windows' # Read from a config file
factory = AppConfig.get_factory(os)
button = factory.create_button()
checkbox = factory.create_checkbox()
[Link]()
[Link]()
23 Roi Yehoshua, 2025
Abstract Factory
Pros
Single Responsibility Principle: you can extract the product creation code into one place
Open/Closed Principle: You can introduce new variants of products without breaking existing
client code
Cons
Requires introduction of many new interfaces and classes
24 Roi Yehoshua, 2025
Prototype
Lets you copy existing objects without making your code dependent on their classes
Client Prototype
+Operation() +Clone()
p=[Link]()
ConcretePrototype1 ConcretePrototype2
+Clone() +Clone()
return copy of this return copy of this
25 Roi Yehoshua, 2025
Copying Objects
There are two types of copy operations
Shallow copy is a bit-wise copy of an object
A new object is created that has an exact copy of the values in the original object
If the object contains references to other objects, just the reference addresses are copied
Deep copy copies the object along with all the objects to which it refers (recursively)
26 Roi Yehoshua, 2025
Copying Objects in Python
The copy module provides shallow and deep copy operations:
[Link](x) – returns a shallow copy of x
[Link](x) – returns a deep copy of x
27 Roi Yehoshua, 2025
Prototype Example
In this example, we’ll use the Prototype pattern to produce exact copies of geometric
objects, without coupling the code to their classes
A subclass may call the parent’s cloning method before copying its own field values
28 Roi Yehoshua, 2025
Example: Base Prototype
from abc import ABC, abstractmethod
class Shape(ABC):
def __init__(self, location, color):
[Link] = location
[Link] = color
def __str__(self):
return f'Location: {[Link]}, Color: {[Link]}'
@abstractmethod
def clone(self):
pass
29 Roi Yehoshua, 2025
Example: Concrete Prototype
from shape import Shape
import copy
class Circle(Shape):
def __init__(self, location, color, radius):
super().__init__(location, color)
[Link] = radius
def __str__(self):
return super().__str__() + f', Radius: {[Link]}'
def clone(self):
return [Link](self)
30 Roi Yehoshua, 2025
Example: Concrete Prototype
from shape import Shape
import copy
class Rectangle(Shape):
def __init__(self, location, color, width, length):
super().__init__(location, color)
[Link] = width
[Link] = length
def __str__(self):
return super().__str__() + f', Width: {[Link]}, Length: {[Link]}'
def clone(self):
return [Link](self)
31 Roi Yehoshua, 2025
Example: Main Script
from circle import Circle
from rectangle import Rectangle
c1 = Circle((5, 10), 'Red', 10)
c2 = [Link]()
print(c1)
print(c2)
r1 = Rectangle((2, 3), 'Black', 10, 20)
r2 = [Link]()
print(r1)
print(r2)
Location: (5, 10), Color: Red, Radius: 10
Location: (5, 10), Color: Red, Radius: 10
Location: (2, 3), Color: Black, Width: 10, Length: 20
Location: (2, 3), Color: Black, Width: 10, Length: 20
32 Roi Yehoshua, 2025
Prototype Registry
The prototype registry provides an easy way to access frequently-used prototypes
It stores a set of pre-built objects that are ready to be copied in a hash map
33 Roi Yehoshua, 2025
Prototype
Pros:
You can clone objects without coupling to their concrete classes
You can avoid repeated initialization code in favor of cloning pre-built prototypes
You can produce complex objects more conveniently
Cons:
Cloning complex objects that have circular references might be tricky
34 Roi Yehoshua, 2025
Builder
Handles the construction of complex objects step by step
Allows you to produce different representations of an object using the same
construction code
Director Builder
+Construct() +BuildPart()
foreach item in structure ConcreteBuilder
[Link]() Product
+BuildPart()
+GetResult()
35 Roi Yehoshua, 2025
Builder Example
We’ll use the builder pattern to build different types of report
Each report consists of a header, body and footer
We’ll define two types of reports: a simple TextReport and an HTMLReport
36 Roi Yehoshua, 2025
Example: Report Builder Interface
# report_builder.py
from abc import ABC, abstractmethod
class ReportBuilder(ABC):
@abstractmethod
def build_header(self):
pass
@abstractmethod
def build_body(self):
pass
@abstractmethod
def build_footer(self):
pass
@abstractmethod
def get_report(self) -> str:
pass
37 Roi Yehoshua, 2025
Example: Text Report Builder
# text_report_builder.py
from report_builder import ReportBuilder
class TextReportBuilder(ReportBuilder):
def __init__(self):
[Link] = ""
def build_header(self):
[Link] += "Header of the report\n"
def build_body(self):
[Link] += "Body of the report\n"
def build_footer(self):
[Link] += "Footer of the report\n"
def get_report(self) -> str:
return [Link]
38 Roi Yehoshua, 2025
Example: HTML Report Builder
# html_report_builder.py
from report_builder import ReportBuilder
class HTMLReportBuilder(ReportBuilder):
def __init__(self):
[Link] = ""
def build_header(self):
[Link] += "<h1>Header</h1>\n"
def build_body(self):
[Link] += "<p>Body</p>\n"
def build_footer(self):
[Link] += "<div>footer></div>\n"
def get_report(self) -> str:
return [Link]
39 Roi Yehoshua, 2025
Example: Director
# [Link]
from report_builder import ReportBuilder
class Director:
def construct(self, builder: ReportBuilder) -> str:
builder.build_header()
builder.build_body()
builder.build_footer()
return builder.get_report()
40 Roi Yehoshua, 2025
Example: Main Script
# [Link]
from director import Director Header of the report
from text_report_builder import TextReportBuilder Body of the report
from html_report_builder import HTMLReportBuilder Footer of the report
director = Director() <h1>Header</h1>
text_report = [Link](TextReportBuilder()) <p>Body</p>
print(text_report)
<div>footer></div>
html_report = [Link](HTMLReportBuilder())
print(html_report)
41 Roi Yehoshua, 2025
Builder
Pros
You can construct objects step-by-step, defer construction steps or run steps recursively
You can reuse the same construction code when building various representations of products
Single Responsibility Principle: You can isolate complex construction code from the business
logic of the product
Cons
The overall complexity of the code increases
42 Roi Yehoshua, 2025
Singleton
Ensures that a class has only one instance, while providing a global access point to it
43 Roi Yehoshua, 2025
Singleton: How to Implement in Python
We override the __new__() method that controls the creation of new instances
Normally, __new__ creates a new instance of the class by invoking the superclass’s
__new__() method and then invoking the new instance’s __init__() method
We intercept the __new__() method and tell it to create only one class instance
# [Link]
class Logger:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
print('Creating a new instance')
cls._instance = super().__new__(cls)
return cls._instance
44 Roi Yehoshua, 2025
Singleton: How to Implement in Python
The object is created on the first call to the class:
from logger import Logger
log1 = Logger()
print(log1)
Creating a new instance
<[Link] object at 0x00000216B0AE6F40>
But the second call returns the same instance:
log2 = Logger()
print(log2)
<[Link] object at 0x00000216B0AE6F40>
45 Roi Yehoshua, 2025
Thread-Safe Singleton
In multithreaded environments, we need to ensure that multiple threads don’t
create the singleton object multiple times
To reduce overhead of acquiring a lock, the double-checked locking pattern is used
Locking occurs only if an instance of the class has not been created yet
# therad_safe_logger.py
import threading
class ThreadSafeLogger:
_instance = None
_lock = [Link]()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
print('Creating a new instance')
cls._instance = super().__new__(cls)
return cls._instance
46 Roi Yehoshua, 2025
Singleton
Pros:
You can be sure that a class has only a single instance
You gain a global access point to that instance
The singleton object is initialized only when it’s requested for the first time (lazy initialization)
Cons:
Can mask bad design
Creates a coupling between all the classes that use the singleton: they share a global variable
Makes unit testing difficult
It’s hard to create mock objects of the singleton since you cannot override static methods
47 Roi Yehoshua, 2025