DEV Community

Hernán Chilabert
Hernán Chilabert

Posted on • Edited on

Creational Patterns: Enhancing Your Coding Arsenal

[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
Enter fullscreen mode Exit fullscreen mode

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'})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay