Design Pattern

SOLID

S - Single Responsibility

  • one purpose one class

O - Open Close Principle

  • The modification of the class method is not encouraged for new feature

  • The creation of new class method or new child class is encouraged for new feature

L - Liskov Substitution

  • When designing your classes , It is needed to consider that any subclass can be used as a substitute for the base class without unexpected behavior.

  • If you have a base class Bird with a method fly(), and you create a subclass Penguin which also has a fly() method but throws an exception because penguins can't fly, you would be violating the Liskov Substitution Principle.

I - Interface segregation

  • the interface should consider the necessary class method, in order to make the class more focused and maintainable

D - Dependency injection

  • Loose coupled between the classes

Singleton

  • Single instance only for a class

  • Global configuration ( e.g: db connection, logger) is one of the use case

class Singleton {
    static instance;

    /**
     * The Singleton's constructor should always be private to prevent direct
     * construction calls with the `new` operator.
     */
    constructor() { }

    /**
     * The static getter that controls access to the singleton instance.
     *
     * This implementation allows you to extend the Singleton class while
     * keeping just one instance of each subclass around.
     */
    static get instance() {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }

        return Singleton.instance;
    }

    /**
     * Finally, any singleton can define some business logic, which can be
     * executed on its instance.
     */
     someBusinessLogic() {
        // ...
    }
}

/**
 * The client code.
 */
function clientCode() {
    const s1 = Singleton.instance;
    const s2 = Singleton.instance;

    if (s1 === s2) {
        console.log(
            'Singleton works, both variables contain the same instance.'
        );
    } else {
        console.log('Singleton failed, variables contain different instances.');
    }
}

clientCode();

Factory

  • Act as a agent to building the target class

  • Suitable when the class constructor is non-important and duplicated, the method can help to hide the detail

public abstract class Car {
    public static Car factory(Color color) {
        switch(color) {
            case "RED":
                return new RedCar();
            case "BLUE":
                return new BlueCar();
        }
    }
}
public class RedCar extends Car {...}
public class BlueCar extends Car {...}

Car redCar = car.factory("RED");

Builder

  • Suitable to apply when there are lots of attribute in class

  • Easier to understand and read

class House:
    def __init__(self):
        self.bedrooms = 0
        self.bathrooms = 0
        self.garage = False
        self.garden = False
        self.swimming_pool = False

    def __str__(self):
        return f"House with {self.bedrooms} bedrooms, {self.bathrooms} bathrooms, " \
               f"{'a garage, ' if self.garage else ''}" \
               f"{'a garden, ' if self.garden else ''}" \
               f"{'a swimming pool' if self.swimming_pool else ''}"

class HouseBuilder:
    def __init__(self):
        self.house = House()

    def add_bedrooms(self, count):
        self.house.bedrooms = count
        return self

    def add_bathrooms(self, count):
        self.house.bathrooms = count
        return self

    def add_garage(self):
        self.house.garage = True
        return self

    def add_garden(self):
        self.house.garden = True
        return self

    def add_swimming_pool(self):
        self.house.swimming_pool = True
        return self

    def build(self):
        return self.house

# Client code
builder = HouseBuilder()
house = (builder.add_bedrooms(3)
         .add_bathrooms(2)
         .add_garage()
         .add_garden()
         .build())

print(house)

Adaptor

  • Provide a convert class, to convert the format from a to b

class EuropeanSocket:
    def provide_230v(self):
        return "Providing 230V"

class AmericanPlug:
    def connect_to_120v(self):
        return "Using 120V power"

class SocketAdapter:
    def __init__(self, european_socket):
        self.european_socket = european_socket

    def connect(self):
        # Convert the power from 230V to 120V
        if self.european_socket.provide_230v() == "Providing 230V":
            return "Adapter converting to 120V -> " + AmericanPlug().connect_to_120v()

# Client code
european_socket = EuropeanSocket()
adapter = SocketAdapter(european_socket)
print(adapter.connect())

Strategy

  • Applying dependency injection, A high order function/ class to make the low-level class be interchangable

from typing import List

# Strategy interface
class SortStrategy:
    def sort(self, data: List[int]) -> List[int]:
        pass

# Concrete strategies
class BubbleSortStrategy(SortStrategy):
    def sort(self, data: List[int]) -> List[int]:
        n = len(data)
        for i in range(n):
            for j in range(0, n-i-1):
                if data[j] > data[j+1]:
                    data[j], data[j+1] = data[j+1], data[j]
        return data

class QuickSortStrategy(SortStrategy):
    def sort(self, data: List[int]) -> List[int]:
        if len(data) <= 1:
            return data
        pivot = data[len(data) // 2]
        left = [x for x in data if x < pivot]
        middle = [x for x in data if x == pivot]
        right = [x for x in data if x > pivot]
        return self.sort(left) + middle + self.sort(right)

# high level class , you inject the suitable low class into it
class Context:
    def __init__(self, strategy: SortStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SortStrategy):
        self._strategy = strategy

    def sort(self, data: List[int]) -> List[int]:
        return self._strategy.sort(data)

# Client code
data = [5, 3, 8, 6, 2]

context = Context(BubbleSortStrategy())
print("Bubble Sort:", context.sort(data))

context.set_strategy(QuickSortStrategy())
print("Quick Sort:", context.sort(data))

Last updated

Was this helpful?