Design Patterns

Creational

Singleton

Method for creating a single instance of a class.

(in python, __new__ created the object, __init__ initializes)

# Straightforward implementation of the Singleton Pattern

class Logger(object):
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            print('Creating the object')
            cls._instance = super(Logger, cls).__new__(cls)
            # Put any initialization here.
        return cls._instance

print(Logger())
print(Logger())
Creating the object
<__main__.Logger object at 0x7d39d3822120>
<__main__.Logger object at 0x7d39d3822120>

In java, more intuitive:

class Logger {
    private static Logger instance;
    private Logger() {}
    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println(Logger.getInstance());
        System.out.println(Logger.getInstance());
    }
}
Logger@8efb846
Logger@8efb846

Builder Pattern

With complex objects, instead of complex constructor, "build" the object step-by-step:

public class Car {
    private final String engine;
    private final int wheels;

    private Car(Builder builder) {
        this.engine = builder.engine;
        this.wheels = builder.wheels;
    }

    public static class Builder {
        private String engine = "default";
        private int wheels = 4;

        public Builder setEngine(String engine) {
            this.engine = engine;
            return this;
        }

        public Builder setWheels(int wheels) {
            this.wheels = wheels;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }
}

// Usage:
Car car = new Car.Builder()
    .setEngine("V8")
    .setWheels(6)
    .build();

In python, just used named parameters (not builder, but conventional).

class Car:
    def __init__(self, engine="default", wheels=4):
        self.engine = engine
        self.wheels = wheels

# Usage:
car = Car(engine="V8", wheels=6)

There is a more complex pattern with the director and more boilerplate , but in practice it is not frequently used outside of textbook examples.

Factory Pattern

Used for hiding the complexities of object creation. Use a simpler interface and the complex part is behind the scenes.

No factory

from abc import ABC, abstractmethod

class Car(ABC):
    @abstractmethod
    def go(self):
        pass

class Truck(Car):
    def __init__(self, engine):
        self.engine = engine
    def go(self):
        print(f"{self.engine} Truck goes slow")

class Sedan(Car):
    def __init__(self, engine):
        self.engine = engine
    def go(self):
        print(f"{self.engine} Sedan goes fast")

cartype = "Truck"
enginetype = "Electric"

car = None
if cartype == "Truck":
    car = Truck(enginetype)
else:
    car = Sedan(enginetype)

Factory:

class CarFactory:
    @staticmethod
    def createCar(cartype, enginetype):
        if cartype == "Truck":
            return Truck(enginetype)
        elif cartype == "Sedan":
            return Sedan(enginetype)
        else:
            raise ValueError(f"Unknown car type: {cartype}")

car = CarFactory.createCar(cartype, enginetype)

This gives us a cleaner "interface"

Last updated