Backend
October 15, 2023

Обзор паттернов проектирования: Singleton, Factory, Observer 

В этой статье я расскажу тебе про несколько самых полезных паттернов на Python. Готов? Поехали!

Паттерн Singleton

Первый паттерн, который стоит выучить - Singleton. Он нужен, когда в программе должен быть только один экземпляр какого-то класса. Например, класс настроек приложения.

Singleton гарантирует, что у тебя будет только один объект этого класса. Вот пример кода:

class Settings:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls) 
        return cls._instance

Здесь мы создали класс Settings и переопределили метод __new__. Он вызывается при создании нового объекта. Мы проверяем, существует ли уже экземпляр в _instance. Если нет - создаем. Если есть - возвращаем существующий.

Таким образом, сколько бы раз мы не вызывали Settings(), у нас будет только один объект. Проверим:

s1 = Settings()
s2 = Settings()

print(s1 == s2) # True - объект один и тот же

Как видишь, Singleton - простой и полезный паттерн, чтобы иметь только один экземпляр класса.

Паттерн Factory

Следующий важный паттерн - Factory (Фабрика). Он нужен, когда у тебя много похожих классов, и ты хочешь упростить их создание.

Например, в игре у тебя есть классы Goblin, Skeleton, Orc - разных монстров. Чтобы не писать в коде goblin = Goblin(), skeleton = Skeleton() и т.д., можно сделать фабрику:

class MonsterFactory:
    def create_monster(self, type):
        if type == "goblin":
            return Goblin()
        elif type == "skeleton":
            return Skeleton()
        elif type == "orc":
            return Orc()
            
factory = MonsterFactory()
monster = factory.create_monster("skeleton")

Теперь, чтобы создать монстра, достаточно вызвать метод create_monster и указать тип. Код становится чище и проще для понимания.

Паттерн Factory - очень удобный способ создавать схожие объекты в одном месте, не засоряя код.

Паттерн Observer

Еще один полезный паттерн - Observer (Наблюдатель). Он нужен, когда объекты в программе должны реагировать на изменения в других объектах.

Представь, в игре у тебя есть юниты и здания. И юниты должны автоматически атаковать вражеские здания, когда те появляются.

Мы можем сделать это с Observer:

from __future__ import annotations
from abc import ABC, abstractmethod
from random import randint

class Observable(ABC):
    def __init__(self):
        self.observers = []
        
    def notify(self):
        for observer in self.observers:
            observer.update(self)
            
    def attach(self, observer):
        self.observers.append(observer)
        
class Observer(ABC):
  @abstractmethod
  def update(self, subject: Observable) -> None:
    pass
    
class Unit(Observer):
    def update(self, building: Building):
        print(f"Unit is attacking {building}")
        building.hp -= randint(1, 10)
        
class Building(Observable): 
    def __init__(self):
        super().__init__()
        self.hp = 100
        
# Использование

building = Building()
unit = Unit()

building.attach(unit)

building.notify() # Unit attacks building

Здесь Building наследует от Observable и может уведомлять наблюдателей о событиях через notify(). А Unit реализует интерфейс Observer и получает уведомления в методе update().

Таким образом юниты могут автоматически реагировать на появление зданий. Это и есть Observer - мощный паттерн для реакции объектов друг на друга.

Другие паттерны

Я рассказал про самые важные паттерны. Но есть и другие полезные:

Command - помещает задачи в объекты-команды для многократного выполнения или отмены.

Strategy - позволяет легко переключать разные алгоритмы решения задачи.

Decorator - добавляет объектам новое поведение без изменения кода.

Все эти паттерны тоже пригодятся в больших проектах. Поэтому стоит разобраться с ними, когда появится время.

Итог

Ну вот мы и разобрали основные паттерны проектирования на Python. Как видишь, это очень полезные и мощные инструменты. Они помогут тебе писать правильный, гибкий и поддерживаемый код. А это ключевое умение для профессионального программиста.