Design Patterns - Introduction

Migue Donatien

Migue Donatien·

· 8 min read

These software design patterns are reusable solutions to common software development problems. They are aimed at making software development more accessible, more efficient, and more maintainable. Design patterns encapsulate decades of experience in software development and can be utilized across different software projects and platforms. This article will discuss some of the most popular design patterns in software development, along with examples.

Singleton Pattern

The singleton pattern is a creational design pattern that ensures the creation of a unique instance of an object in a system. It is used when we want to create a single instance of a class that can be shared across the entire application. One example of using the singleton pattern is in a logging system. In many applications, only one logger instance is needed to handle logging events from different system parts. Using the singleton pattern ensures only one logger instance is shared across the entire application.

Here is an example of implementing the Singleton pattern using a metaclass:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Logger(metaclass=Singleton):
    pass

In this example, Singleton the metaclass ensures that only one instance of the class exists, regardless of how many times it is instantiated. The Logger class is defined using the metaclass Singleton, which guarantees that only one instance of Logger can exist.

To use the Logger class, create an instance:

my_logger = Logger()

This will always return the same instance of the Logger class, regardless of how many times it is called. The Singleton pattern can be useful in many situations where you want to ensure that only one instance of a particular object can exist, such as for database connections, configuration settings, or logging.

Factory Pattern

The Factory pattern is a creational design pattern used to simplify the creation of objects, especially when the creation of an object is complex and the object creation may be conditional based on different parameters. One example of using the factory pattern is in a pizza ordering system. The system has a type of pizza, which can change depending on customer preferences. Rather than creating separate classes for every type of pizza, a factory pattern can be used to create the different pizza types based on the customer's preferences.

Here's an example of how you could use the Factory pattern in Python:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow."

class AnimalFactory:
    def create_animal(self, animal_type):
        if animal_type == "Dog":
            return Dog("Dog")
        elif animal_type == "Cat":
            return Cat("Cat")

factory = AnimalFactory()

animal = factory.create_animal("Dog")
print(animal.speak()) # Output: Woof!

animal = factory.create_animal("Cat")
print(animal.speak()) # Output: Meow.

In this example, we have the base Animal class and two subclasses, Dog and Cat. The AnimalFactory class is the Factory class that creates Dog Cat objects. The create_animal method takes a parameter animal_type , a string representing the type of object that should be created. The create_animal method checks the animal_type parameter and returns an instance of the correct subclass.

By using the Factory pattern, we have abstracted out the creation of the Dog and Cat objects and can easily add new subclasses without changing the client code that uses the factory. This makes our code more modular and easier to maintain.

Observer Pattern

The observer pattern is a behavioral pattern that offers a way to decouple the components of a system so that they can be modified independently. The observer pattern uses a subscribe/publish model, where subscribers register and receive updates from one or more publishers when events of interest occur. One example of using the observer pattern is in a stock market application. Different investors are interested in changes in the value of stocks or products they have invested in. Using the observer pattern, the investors can be notified when a change in the stock value happens.

Here is an example Python code for the Observer pattern:

class Observable(object):
    def __init__(self):
        self.observers = []
    def register(self, observer):
        if observer not in self.observers:
            self.observers.append(observer)
    def unregister(self, observer):
        if observer in self.observers:
            self.observers.remove(observer)
    def notify(self, *args, **kwargs):
        for observer in self.observers:
            observer.notify(self, *args, **kwargs)

class Observer(object):
    def notify(self, observable, *args, **kwargs):
        pass

class ExampleObservable(Observable):
    def do_something(self):
        # Do something interesting
        self.notify("some message")

class ExampleObserver(Observer):
    def notify(self, observable, *args, **kwargs):
        print("Got", args, kwargs, "From", observable)

observable = ExampleObservable()
observer = ExampleObserver()
observable.register(observer)

observable.do_something()

In this example, Observable is the subject, and Observer is the dependent. ExampleObservable is an instance of the Observable class and ExampleObserver is an instance of the Observer class. When the do_something() method of ExampleObservable is called, it notifies all its observers by calling the notify() method of each observer registered to it. The ExampleObserver overrides the notify() method of its parent Observer class to receive the state change message sent by ExampleObservable.

This is a simple example of how the Observer pattern works in Python. Still, the pattern can be applied in more complex scenarios to maintain the separation of concerns and enable loose coupling between objects in a system.

Adapter Pattern

The adapter pattern is a structural pattern used to convert an incompatible interface of a class into another interface, which clients can use comfortably without modifying the source code. One example of using the adapter pattern is in an audio system. The audio drivers are incompatible with several operating systems. A separate class is created using the adapter pattern to bridge between the operating system and the audio system.

Here is an example of how to implement the Adapter Pattern in Python:

First, create an interface that the client expects the Adaptee (an existing class) to conform to:

class Target:
    def request(self):
        pass

Next, create the Adaptee that needs to be adapted to work with the Target interface:

class Adaptee:
    def specific_request(self):
        return "Adaptee request"

To implement the Adapter Pattern, create an Adapter that adapts the Adaptee to the Target interface:

class Adapter(Target):
    def __init__(self, adaptee):
        self.adaptee = adaptee

    def request(self):
        return self.adaptee.specific_request()

Now, the client can use the Adapter to call the Adaptee's specific_request method through the Target interface:

adaptee = Adaptee()
adapter = Adapter(adaptee)

adapter.request() # returns "Adaptee request"

In this example, the Target interface is represented by the Target class, the Adaptee class represents the Adaptee, and the Adapter class represents the Adapter. The Adapter class takes an instance of the Adaptee class as a parameter and adapts its specific_request method to work with the Target interface's request method.

Decorator Pattern

The Decorator Pattern is a design pattern that dynamically adds behavior to an individual object without affecting the behavior of other objects from the same class. It is used to extend or alter the functionality of objects at runtime by wrapping them in an object of a decorator class.

Here is an example of how to implement the Decorator Pattern in Python:

class Beverage:
    def get_description(self):
        pass

    def get_cost(self):
        pass

class Espresso(Beverage):
    def get_description(self):
        return "Espresso"

    def get_cost(self):
        return 1.99

class BeverageDecorator(Beverage):
    def __init__(self, wrapped_beverage):
        self.beverage = wrapped_beverage

    def get_description(self):
        return self.beverage.get_description()

    def get_cost(self):
        return self.beverage.get_cost()

class Whip(BeverageDecorator):
    def get_description(self):
        return self.beverage.get_description() + ", Whip"

    def get_cost(self):
        return self.beverage.get_cost() + 0.10

class Mocha(BeverageDecorator):
    def get_description(self):
        return self.beverage.get_description() + ", Mocha"

    def get_cost(self):
        return self.beverage.get_cost() + 0.20

In this example, Beverage is the base class for all beverages, Espresso is a concrete implementation of a Beverage, and BeverageDecorator is an abstract class that also implements the Beverage interface. Whip and Mocha are concrete implementations of BeverageDecorator that wrap a Beverage object and add additional behavior.

Here's an example of how to use this code:

beverage = Espresso()
print(beverage.get_description(), "$" + str(beverage.get_cost()))

beverage = Whip(beverage)
print(beverage.get_description(), "$" + str(beverage.get_cost()))

beverage = Mocha(beverage)
print(beverage.get_description(), "$" + str(beverage.get_cost()))

This will output:

Espresso $1.99
Espresso, Whip $2.09
Espresso, Whip, Mocha $2.29

Conclusion

Design patterns are a set of reusable solutions to common programming problems. They offer a number of benefits, such as increasing the efficiency of developers, improving the maintainability of code, and reducing the likelihood of errors. Design patterns are used in a variety of applications, including web development, mobile app development, and desktop application development. By following established design patterns, developers can create reliable, efficient, and scalable software solutions that meet the needs of users and businesses alike.

Migue Donatien

About Migue Donatien

Donatien is an Entrepreneurship with many years as a Software engineer.

Copyright © 2023 Digital Press Daily. All rights reserved.
Made by Modern Design Agency