Creational Patterns: Enhancing Your Coding Arsenal
Hernán Chilabert
Posted on December 1, 2023
[UPDATED 12/7/2023]
✨ GitHub repo with real-world use cases: https://github.com/herchila/design-patterns/tree/main/creational_patterns
Singleton
Purpose: A singleton ensures that a class has only one instance and provides a global point of access to it.
Example: In applications that require thread management, a Singleton can be used to manage and optimize the use of threads, ensuring that threads are reused and not excessively created and destroyed.
Basic sample
class Singleton:
_instance = None
@staticmethod
def getInstance():
if Singleton._instance == None:
Singleton()
return Singleton._instance
def __init__(self):
if Singleton._instance != None:
raise Exception("This class is a singleton!")
else:
Singleton._instance = self
# Usage
s = Singleton.getInstance()
print(s)
s1 = Singleton.getInstance()
print(s1) # Will print the same instance as s
Advance sample
The Singleton pattern can be effectively used in scenarios involving message producers in systems like Apache Kafka. Apache Kafka is a distributed streaming platform that allows for high-throughput, fault-tolerant handling of streaming data. In such a system, a Singleton can be used to manage a Kafka producer instance to ensure that only one instance is used to send messages to a Kafka topic, which can be crucial for optimizing resource usage and managing connections.
Here's an example of how you might implement a Kafka producer as a Singleton in Python:
from kafka import KafkaProducer
import json
class KafkaProducerSingleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(KafkaProducerSingleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self, kafka_servers):
# This will only be executed once
self.producer = KafkaProducer(bootstrap_servers=kafka_servers,
value_serializer=lambda v: json.dumps(v).encode('utf-8'))
def send_message(self, topic, value):
self.producer.send(topic, value)
self.producer.flush()
# Usage example
kafka_servers = ['localhost:9092'] # Replace with your Kafka server addresses
producer1 = KafkaProducerSingleton(kafka_servers)
producer2 = KafkaProducerSingleton(kafka_servers)
print(producer1 == producer2) # True
# Sending a message
producer1.send_message('my_topic', {'key': 'value'})
In this implementation:
The __new__
method ensures that only one instance of KafkaProducerSingleton
is created. If an instance already exists, it returns that existing instance.
The __init__
method initializes the Kafka producer. This initialization only happens once since __new__
ensures only one instance is created.
The send_message
method is used to send messages to a specified Kafka topic. It uses the producer to send the message and then flushes the producer to ensure the message is sent immediately.
Abstract Factory Pattern
Purpose: To provide an interface for creating families of related or dependent objects without specifying their concrete classes.
Example: A UI library where we need to create UI elements that are consistent across different operating systems.
class Button:
def paint(self): pass
class LinuxButton(Button):
def paint(self):
return "Render a button in a Linux style"
class WindowsButton(Button):
def paint(self):
return "Render a button in a Windows style"
class GUIFactory:
def create_button(self): pass
class LinuxFactory(GUIFactory):
def create_button(self):
return LinuxButton()
class WindowsFactory(GUIFactory):
def create_button(self):
return WindowsButton()
# Usage
def client_code(factory: GUIFactory):
button = factory.create_button()
print(button.paint())
client_code(LinuxFactory()) # Output: Render a button in a Linux style
client_code(WindowsFactory()) # Output: Render a button in a Windows style
Builder Pattern
Purpose: To separate the construction of a complex object from its representation, allowing the same construction process to create different representations.
Example: Building different types of documents (Text, HTML, Markdown) with the same content.
class Document:
def __init__(self):
self.parts = []
def add(self, part):
self.parts.append(part)
def __str__(self):
return '\n'.join(self.parts)
class Builder:
def build_title(self, title): pass
def build_body(self, body): pass
def get_result(self): pass
class TextBuilder(Builder):
def __init__(self):
self.document = Document()
def build_title(self, title):
self.document.add(f"Title: {title}")
def build_body(self, body):
self.document.add(body)
def get_result(self):
return self.document
# Usage
builder = TextBuilder()
builder.build_title("My Title")
builder.build_body("Hello, world!")
document = builder.get_result()
print(document)
Factory Pattern
Purpose: To create objects without specifying the exact class of object that will be created.
Example: Generating different types of charts based on user input.
class Chart:
def draw(self): pass
class BarChart(Chart):
def draw(self):
return "Drawing a bar chart"
class PieChart(Chart):
def draw(self):
return "Drawing a pie chart"
def chart_factory(chart_type):
charts = {
"bar": BarChart,
"pie": PieChart
}
return charts[chart_type]()
# Usage
chart = chart_factory("bar")
print(chart.draw()) # Output: Drawing a bar chart
Prototype Pattern
Purpose: To create new objects by copying existing objects, thus reducing the cost of creation.
Example: Duplicating graphic objects in a graphic editor.
import copy
class Prototype:
def clone(self): pass
class ConcretePrototype(Prototype):
def __init__(self, number):
self.number = number
def clone(self):
return copy.deepcopy(self)
# Usage
prototype = ConcretePrototype(1)
cloned_prototype = prototype.clone()
print(cloned_prototype.number) # Output: 1
Posted on December 1, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 1, 2023