- Updated summarization prompt to require Russian output and exclude non-textual elements - Upgraded ollama dependency to v0.6.1 - Enhanced run.sh script to support both single record and file-based ID input for MongoDB test generation - Updated documentation in scripts/README.md to reflect new functionality - Added verbose flag to generate_summarization_from_mongo.py for better debugging ``` This commit message follows the conventional commit format with a short title (50-72 characters) and provides a clear description of the changes made and their purpose.
3 lines
62 KiB
Plaintext
3 lines
62 KiB
Plaintext
Для написания чистого и эффективного кода важно понимать, когда и какие шаблоны проектирования следует использовать, причем использовать их корректно. Шаблонами проектирования предоставляются проверенные решения типичных проблем проектирования ПО, но их некорректное применение чревато запутанными и сложными в сопровождении кодовыми базами. Рассмотрим передовые практики по реализации шаблонов проектирования в Python, а также распространенные антипаттерны, не желательные для надежного, сопровождаемого, масштабируемого кода. Роль передовых практик в проектировании ПО В программной разработке передовые практики — это стандартные рекомендации, по которым разработчики пишут эффективный, сопровождаемый, масштабируемый код. Соблюдением передовых практик обеспечиваются корректное применение шаблонов проектирования, совершенствование общей архитектуры приложения без излишней сложности. Ключевые моменты Согласованность: передовые практики способствуют единообразию кода, облегчая совместную работу команд. Сопровождаемость: следование установленным рекомендациям упрощает будущие модификации и отладку. Масштабируемость: корректной реализацией шаблонов проектирования обеспечивается рост приложений без существенного рефакторинга. Антипаттерны и их влияние Антипаттерны — это типичные, неэффективные и контрпродуктивные подходы к решению периодически возникающих проблем. В контексте шаблонов проектирования антипаттерны представляют собой неправильное использование шаблонов или злоупотребление ими, из-за чего код получается трудным для понимания, сопровождения и расширения. Влияние антипаттернов Увеличивается сложность: излишне сложные решения затрудняют перемещение по кодовой базе. Низкая производительность: неэффективные реализации снижают производительность приложений. Проблемы сопровождения: код с антипаттернами более подвержен багам, его труднее обновлять. Передовые практики по реализации шаблонов проектирования Для эффективной реализации шаблонов проектирования требуется вдумчивый подход, обеспечивающий с их помощью решение намеченных проблем без появления новых. 1. Используйте шаблоны для решения актуальных проблем, а не ради самих шаблонов Шаблоны проектирования — это не универсальный подход, ими решаются конкретные проблемы. Применение шаблона без очевидной необходимости чревато ненужным усложнением. Пример: Пригодное использование: шаблон «Одиночка», которым управляется один экземпляр подключения к базе данных. Непригодное использование: шаблон «Одиночка» для управления несвязанными объектами, в итоге получается антипаттерн «Божественный объект». 2. Сохраняйте дизайн простым, избегайте чрезмерного усложнения Простота — основной принцип проектирования ПО. Чрезмерное усложнение реализаций шаблонами затрудняет понимание кодовой базы и ее сопровождение. Рекомендации: Принцип YAGNI / «Вам это не понадобится»: реализуйте только необходимое. Избегайте перегрузки шаблонов: используйте минимум шаблонов, необходимых для решения проблемы. 3. Поддерживайте гибкость и удобство восприятия кода Удобный для восприятия код проще сопровождать и расширять. Шаблоны проектирования должны совершенствовать структуру кода, не делая ее непонятной. Стратегии: Четкие соглашения об именовании: используйте информативные названия для классов и методов. Модульный дизайн: разбейте сложную функциональность на мелкие, управляемые модули. Слабая связанность: обеспечьте взаимодействие компонентов не через прямые зависимости, а четко определенные интерфейсы. Типичные антипаттерны при применении шаблонов проектирования Непродуманная реализация шаблонов проектирования чревата появлением антипаттернов. Чтобы избежать снижения качества кода, важно распознавать эти типичные ошибки: 1. «Божественный объект» Описание: «божественный объект» — это единственный класс, который слишком много «знает» или слишком много «делает» и где централизуются обязанности, которые должны распределяться между классами. Проблемы: Сильная связанность: другие классы становятся зависимыми от «божественного объекта». Слабая целостность: у «божественного объекта» нет единого, четкого назначения. Кошмар сопровождения: изменения в «божественном объекте» чреваты масштабными непредвиденными последствиями. Пример: class GodObject: def __init__(self): self.database = Database() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() # ... много других обязанностей def perform_all_operations(self): self.database.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() # ... много других операций 2. Неправильное использование синглтона Описание: в шаблоне «Одиночка» у класса имеется только один экземпляр, к которому и предоставляется глобальная точка доступа. При некорректном применении синглтоны превращаются в хранилища глобального состояния, что чревато сильной связанностью и трудностями тестирования. Проблемы: Глобальное состояние: из-за синглтонов появляются скрытые зависимости и побочные эффекты. Проблемы тестирования: синглтоны затрудняют написание изолированных модульных тестов. Проблемы конкурентности: конкурентный доступ может осуществляться синглтонами некорректно. Пример: class Logger: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Logger, cls).__new__(cls) cls._instance.log_file = open('log.txt', 'w') return cls._instance def log(self, message): self.log_file.write(message + '\n') 3. Злоупотребление наследованием Описание: применение наследования вместо композиции чревато жесткими и хрупкими иерархиями классов. Из-за злоупотребления наследованием изменить или расширить функциональность без ущерба для всей иерархии затруднительно. Проблемы: Сильная связанность: подклассы тесно связаны с реализациями своих суперклассов. Ограниченная гибкость: изменение поведения суперклассов сказывается на подклассах. Дублирование кода: схожая функциональность подклассов чревата дублированным кодом. Пример: class Animal: def eat(self): pass def sleep(self): pass class Dog(Animal): def bark(self): pass class Cat(Animal): def meow(self): pass 4. Загрязнение шаблонов Описание: загрязнение шаблонов происходит, когда шаблоны проектирования применяются без необходимости, из-за чего получаются запутанные и сложные в сопровождении структуры кода. Проблемы: Увеличивается сложность: шаблонов Крутая кривая обучения: новичкам трудно разобраться в переплетении шаблонов. Трудности сопровождения: отладка и расширение такого кода проблематичны. Пример: class Factory: def create_object(self, type): if type == 'A': return DecoratorA(ConcreteA()) elif type == 'B': return DecoratorB(ConcreteB()) # ... применено много шаблонов Как избежать антипаттернов Предотвращение антипаттернов связано с мерами упреждения на этапах проектирования и разработки. Благодаря передовым практикам и бдительности разработчики избегают типичных ошибок. 1. Регулярные проверки кода и рефакторинг Проверки кода: Цель: выявлять и устранять потенциальные антипаттерны на ранней стадии процесса разработки. Практика: для соблюдения принципов проектирования и соответствующего использования шаблонов проводятся экспертные оценки. Рефакторинг: Цель: постоянное совершенствование структуры кода без изменения его внешнего поведения. Практика: регулярным рефакторингом кода устраняются антипаттерны, повышаются удобство восприятия и гибкость. 2. Соблюдение принципа YAGNI Описание: согласно принципу «вам это не понадобится», реализуется только необходимый на текущий момент функционал, а упреждающие или преждевременные реализации шаблонов избегаются. Преимущества: Уменьшается сложность: избегается чрезмерное усложнение, поддерживается простота кодовой базы. Повышается гибкость: по мере возникновения актуальных потребностей упрощаются изменения и расширения. 3. Предпочтение четкому и сопровождаемому коду, а не заумным реализациям Ясность вместо заумности: Принцип: писать код, легко понятный и сопровождаемый, а не слишком заумный или абстрактный. Практика: используются простые решения, которыми четко передается суть, даже если они не столь сложны. Документирование и присвоение имен: Цель: повысить удобство восприятия и понимание кода. Практика: используйте информативные названия для классов, методов и переменных. При необходимости предоставляйте комментарии и документацию. Реальные примеры Фрагментами кода наглядно демонстрируются корректные и некорректные реализации шаблонов проектирования, благодаря этому осваиваются передовые практики и избегаются антипаттерны. 1. Корректная и некорректная реализации шаблона «Одиночка» Некорректная реализация с неправильным использованием синглтона: class Logger: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Logger, cls).__new__(cls) cls._instance.log_file = open('log.txt', 'w') return cls._instance def log(self, message): self.log_file.write(message + '\n') Проблемы: Глобальное состояние: в логгере содержится глобальное состояние, поэтому управление и тестирование затруднительны. Проблемы конкурентности: одновременные попытки потоков создать экземпляры чреваты состояниями гонок. Корректная реализация с использованием метакласса для потокобезопасности: import threading class SingletonMeta(type): _instance = None _lock: threading.Lock = threading.Lock() # Обеспечивается потокобезопасный синглон def __call__(cls, *args, **kwargs): with cls._lock: if cls._instance is None: cls._instance = super(SingletonMeta, cls).__call__(*args, **kwargs) return cls._instance class Logger(metaclass=SingletonMeta): def __init__(self): self.log_file = open('log.txt', 'w') def log(self, message): self.log_file.write(message + '\n') Доработки: Потокобезопасность: состояния гонок предотвращаются блокировкой. Контролируемый доступ: гарантируется наличие лишь одного экземпляра, глобальное состояние не предоставляется. 2. Избежание антипаттерна «Божественный объект» Некорректная реализация с «Божественным объектом»: class GodObject: def __init__(self): self.database = Database() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() # ... много других обязанностей def perform_all_operations(self): self.database.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() # ... много других операций Проблемы: Нарушение принципа единственной ответственности: в GodObject выполняются несвязанные задачи. Сильная связанность: другие части кода сильно зависят от GodObject. Корректная реализация с разделением обязанностей: class DatabaseManager: def connect(self): # Подключение к базе данных pass class Logger: def log(self, message: str) -> None: print(message) class UserManager: def create_user(self): # Создается пользователь pass class OrderManager: def create_order(self): # Создается заказ pass class Application: def __init__(self): self.database_manager = DatabaseManager() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() def perform_operations(self): self.database_manager.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() Доработки: Единственная ответственность: каждым классом выполняется конкретная задача. Слабая связанность: классом Application координируются взаимодействия, ни один класс при этом не перегружается. 3. Загрязнение шаблонов: злоупотребление декораторами Некорректная реализация с загрязнением шаблонов: class BaseComponent: def operation(self): pass class DecoratorA(BaseComponent): def __init__(self, component: BaseComponent): self.component = component def operation(self): # Дополнительное поведение «A» self.component.operation() # Дополнительное поведение «A» class DecoratorB(BaseComponent): def __init__(self, component: BaseComponent): self.component = component def operation(self): # Дополнительное поведение «B» self.component.operation() # Дополнительное поведение «B» class DecoratorC(BaseComponent): def __init__(self, component: BaseComponent): self.component = component def operation(self): # Дополнительное поведение «C» self.component.operation() # Дополнительное поведение «C» # Злоупотребление: применение декоратором без необходимости component = DecoratorA(DecoratorB(DecoratorC(BaseComponent()))) component.operation() Проблемы: Глубоко вложенные декораторы: затрудняют отслеживание и сопровождение кода. Излишняя сложность: чрезмерное усложнение компонента слоями. Корректная реализация с выборочным использование декораторов: class BaseComponent: def operation(self): pass class DecoratorA(BaseComponent): def __init__(self, component: BaseComponent): self.component = component def operation(self): # Дополнительное поведение «A» self.component.operation() class DecoratorB(BaseComponent): def __init__(self, component: BaseComponent): self.component = component def operation(self): # Дополнительное поведение «B» self.component.operation() # Применяются только необходимые декораторы component = DecoratorA(BaseComponent()) component.operation() Доработки: Упрощена структура: меньше декораторов — меньше сложность. Акцентированные доработки: декораторы используются только там, где действительно требуется дополнительное поведение. 4. Корректная и некорректная реализации шаблона «Строитель» Некорректная реализация: чрезмерное усложнение шаблоном «Строитель»: from dataclasses import dataclass, field from typing import Any, List, Optional @dataclass class Car: make: str model: str year: int color: str = "Black" engine: str = "V6" options: List[str] = field(default_factory=list) owner: Any = None insurance: Any = None maintenance_records: List[Any] = field(default_factory=list) # ... много других атрибутов class CarBuilder: def __init__(self): self.make: Optional[str] = None self.model: Optional[str] = None self.year: Optional[int] = None self.color: str = "Black" self.engine: str = "V6" self.options: List[str] = [] self.owner: Any = None self.insurance: Any = None self.maintenance_records: List[Any] = [] # ... много методов установки для каждого атрибута def set_make(self, make: str) -> 'CarBuilder': self.make = make return self def set_model(self, model: str) -> 'CarBuilder': self.model = model return self def set_year(self, year: int) -> 'CarBuilder': self.year = year return self def set_color(self, color: str) -> 'CarBuilder': self.color = color return self def set_engine(self, engine: str) -> 'CarBuilder': self.engine = engine return self def add_option(self, option: str) -> 'CarBuilder': self.options.append(option) return self def set_owner(self, owner: Any) -> 'CarBuilder': self.owner = owner return self def set_insurance(self, insurance: Any) -> 'CarBuilder': self.insurance = insurance return self def add_maintenance_record(self, record: Any) -> 'CarBuilder': self.maintenance_records.append(record) return self def build(self) -> Car: if self.make is None or self.model is None or self.year is None: raise ValueError("Make, model, and year are required fields.") return Car( make=self.make, model=self.model, year=self.year, color=self.color, engine=self.engine, options=self.options.copy(), owner=self.owner, insurance=self.insurance, maintenance_records=self.maintenance_records.copy() # ... инициализируется много других атрибутов ) # Чрезмерное применение builder = CarBuilder() car = (builder.set_make("Toyota") .set_model("Camry") .set_year(2022) .set_color("Red") .add_option("Sunroof") .add_option("Leather Seats") .set_owner(owner_object) .set_insurance(insurance_object) .add_maintenance_record(record1) .add_maintenance_record(record2) # ... много других конфигураций .build()) print(car) Анализ: Чрезмерное усложнение: «Строителем» обрабатывается слишком много атрибутов, получается громоздко. Проблемы сопровождения: при добавлении или удалении атрибутов изменения вносятся в несколько методов, риск ошибок увеличивается. Корректная реализация с упрощенным шаблоном «Строителя»: from dataclasses import dataclass, field from typing import List, Optional @dataclass class Car: make: str model: str year: int color: str = "Black" engine: str = "V6" options: List[str] = field(default_factory=list) class CarBuilder: def __init__(self): self.make: Optional[str] = None self.model: Optional[str] = None self.year: Optional[int] = None self.color: str = "Black" self.engine: str = "V6" self.options: List[str] = [] def set_make(self, make: str) -> 'CarBuilder': self.make = make return self def set_model(self, model: str) -> 'CarBuilder': self.model = model return self def set_year(self, year: int) -> 'CarBuilder': self.year = year return self def set_color(self, color: str) -> 'CarBuilder': self.color = color return self def set_engine(self, engine: str) -> 'CarBuilder': self.engine = engine return self def add_option(self, option: str) -> 'CarBuilder': self.options.append(option) return self def build(self) -> Car: if self.make is None or self.model is None or self.year is None: raise ValueError("Make, model, and year are required fields.") return Car( make=self.make, model=self.model, year=self.year, color=self.color, engine=self.engine, options=self.options.copy() ) # Упрощенное применение builder = CarBuilder() car = (builder.set_make("Toyota") .set_model("Camry") .set_year(2022) .set_color("Red") .add_option("Sunroof") .add_option("Leather Seats") .build()) print(car) Преимущества: Простота: «Строителем» теперь обрабатываются только необходимые атрибуты, управление упрощается. Сопровождаемость: добавление функционала требует минимальных изменений в «Строителе». Инструменты и техники Чтобы корректно реализовать шаблоны проектирования, избегая антипаттернов и оставляя код Python чистым, эффективным, сопровождаемым, используются инструменты и техники. 1. Библиотеки и фреймворки шаблонов проектирования Цель: в этих библиотеках содержатся предопределенные реализации типовых шаблонов как справочный материал или основа для создаваемых реализаций. Примеры: Библиотека patterns: это набор реализаций шаблонов проектирования на Python, используемых как примеры или расширяемых под конкретные требования. pip install patterns PyPatterns: другая библиотека с питоническими реализациями шаблонов проектирования, полезная для их понимания и применения. Пример использования: from patterns.creational.singleton import Singleton class Database(metaclass=Singleton): def connect(self): print("Connecting to the database.") # Применение db1 = Database() db2 = Database() print(db1 is db2) # Вывод: True 2. Линтеры и инструменты статического анализа Цель: линтеры и инструменты статического анализа способствуют применению стандартов написания кода, выявлению потенциальных ошибок и кода с запашко́м — признака антипаттернов. Популярные инструменты: Pylint: код Python анализируется на наличие ошибок, обеспечивается применение стандартов написания кода, выявляется код с запашко́м. pip install pylint pylint your_code.py Flake8: проверка соответствия PEP8 сочетается с обнаружением ошибок. pip install flake8 flake8 your_code.py MyPy: проверяется корректность аннотаций типов, обеспечивается эффективное применение подсказок типов. pip install mypy mypy your_code.py Преимущества: Раннее обнаружение: потенциальные проблемы выявляются до того, как становятся багами. Обеспечивается применение стандартов: поддерживается согласованность кодовой базы. Предотвращение антипаттернов: выявляется код с запашко́м — признак антипаттернов — и запрашивается рефакторинг. 3. Фреймворки автоматизированного тестирования Цель: проверка корректности реализаций шаблонов проектирования и обнаружение регрессий, для этого создаются и выполняются тесты. Популярные фреймворки: Unittest: встроенный в Python фреймворк тестирования для написания и выполнения модульных тестов. import unittest class TestSingleton(unittest.TestCase): def test_singleton_instance(self): db1 = Database() db2 = Database() self.assertIs(db1, db2) if __name__ == '__main__': unittest.main() PyTest: расширенный фреймворк тестирования, которым упрощается написание тестов и поддерживаются тестовые конфигурации, параметризация, плагины. pip install pytest pytest Библиотеки мок-объектов: Unittest.mock: тестируемые части системы заменяются мок-объектами, делаются выводы об их использовании. from unittest.mock import MagicMock class TestDecorator(unittest.TestCase): def test_decorator_adds_behavior(self): base = MagicMock() decorator = DecoratorA(base) decorator.operation() base.operation.assert_called_once() Преимущества: Обеспечивается корректность: проверяется, что поведение реализаций шаблонов проектирования обходится без неожиданностей. Облегчается рефакторинг: в код уверенно вносятся изменения при помощи автоматизированных тестов, которыми выявляются регрессии. Применяются передовые практики: для написания тестопригодного, сопровождаемого кода. Дополнительные темы Рассмотрим применение шаблонов проектирования в микросервисах и распределенных системах, асинхронные реализации, показатели производительности и стратегии перехода от антипаттернов к передовым практикам. 1. Шаблоны микросервисов и распределенных систем В микросервисах и распределенных системах шаблонами проектирования осуществляется управление сложностью, обеспечиваются масштабируемость и надежность. Вот ключевые шаблоны: Регистрация и обнаружение сервисов: сервисы находят друг друга динамически. «Выключатель»: отслеживанием взаимодействий сервисов предотвращаются каскадные сбои. API-шлюз: это единая точка входа для клиентских запросов, здесь осуществляются маршрутизация, аутентификация и не только. Пример шаблона «Выключатель»: import requests from pybreaker import CircuitBreaker, CircuitBreakerError circuit_breaker = CircuitBreaker(fail_max=5, reset_timeout=60) @circuit_breaker def fetch_data(url: str): response = requests.get(url) response.raise_for_status() return response.json() # Использование try: data = fetch_data("https://api.example.com/data") except CircuitBreakerError: print("Service is unavailable. Please try again later.") 2. Реализации асинхронного шаблона Асинхронное программирование важно для приложений с ограничением скорости ввода-вывода и высокой конкурентностью. Шаблоны проектирования легко адаптируются для работы с asyncio на Python. Пример асинхронного шаблона «Наблюдатель»: import asyncio from abc import ABC, abstractmethod from typing import List class Observer(ABC): @abstractmethod async def update(self, message: str) -> None: pass class ConcreteObserver(Observer): async def update(self, message: str) -> None: await asyncio.sleep(1) print(f"Observer received: {message}") class Subject: def __init__(self): self._observers: List[Observer] = [] def register(self, observer: Observer) -> None: self._observers.append(observer) async def notify(self, message: str) -> None: await asyncio.gather(*(observer.update(message) for observer in self._observers)) # Использование async def main(): subject = Subject() observer1 = ConcreteObserver() observer2 = ConcreteObserver() subject.register(observer1) subject.register(observer2) await subject.notify("Hello, Observers!") asyncio.run(main()) 3. Показатели и сопоставления производительности Чтобы оптимизировать приложения, важно понимать влияние шаблонов проектирования на производительность. Реализации оцениваются сопоставлением плюсов и минусов. Пример сопоставления для шаблона «Декоратор»: import time from functools import wraps def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Дополнительное поведение return func(*args, **kwargs) return wrapper @decorator def compute(): return sum(range(1000)) # Сопоставление start = time.time() for _ in range(1000000): compute() end = time.time() print(f"Decorator pattern: {end - start:.4f} seconds") # Прямой вызов def compute_direct(): return sum(range(1000)) start = time.time() for _ in range(1000000): compute_direct() end = time.time() print(f"Direct call: {end - start:.4f} seconds") Пример вывода: Decorator pattern: 1.5000 seconds Direct call: 1.2000 seconds Анализ: Накладные расходы декоратора: дополнительными вызовами функции обусловливается небольшое снижение производительности декоратора. Компромиссы: декораторы, несмотря на их гибкость и расширенную функциональность, сказываются на производительности в высокочастотных сценариях. 4. Стратегии перехода от антипаттернов к передовым практикам При переходе от антипаттернов к передовым практикам имеющиеся проблемы решаются систематическим рефакторингом и применением соответствующих шаблонов проектирования. Пример пошагового перехода: рефакторинг «божественного объекта». Перед рефакторингом «божественного объекта»: class GodObject: def __init__(self): self.database = Database() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() # ... много других обязанностей def perform_all_operations(self): self.database.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() # ... много других операций После рефакторинга с разделением обязанностей: class DatabaseManager: def connect(self): # Подключение к базе данных pass class Logger: def log(self, message: str) -> None: print(message) class UserManager: def create_user(self): # Создается пользователь pass class OrderManager: def create_order(self): # Создается заказ pass class Application: def __init__(self): self.database_manager = DatabaseManager() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() def perform_operations(self): self.database_manager.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() Преимущества: Единственная ответственность: каждым классом выполняется конкретная задача. Слабая связанность: классом Application координируются взаимодействия, ни один класс при этом не перегружается. Повышенная сопровождаемость: изменения в одном компоненте не сказываются на несвязанных компонентах. Производительность Как шаблоны проектирования влияют на производительность В продвинутых шаблонах проектирования часто появляются дополнительные уровни абстракции, которые сказываются на производительности по-разному: Увеличением вызовов методов: в шаблонах вроде «Декоратора» и «Посетителя» имеется несколько обращений к методам, что потенциально чревато замедлением выполнения. Увеличением расхода памяти: на создание дополнительных объектов или оберток. Сложностью взаимодействий с объектами: в шаблонах вроде «Посредника» появляются разветвленные пути обмена данными, которые сказываются на производительности. Динамической диспетчеризацией: динамическое разрешение методов в шаблонах с полиморфизмом чревато накладными расходами. Однако все это обычно перевешивается преимуществами сопровождаемости, гибкости и масштабируемости кода, особенно в крупных приложениях. Оптимизация реализаций шаблонов в целях эффективности Влияние продвинутых шаблонов на производительность ограничивается такими стратегиями: (1) Отложенная инициализация: Описание: создание объектов откладывается до тех пор, пока они не понадобятся, так экономятся ресурсы. from dataclasses import dataclass, field from typing import Any @dataclass class LazyDecorator(CoffeeDecorator): def __init__(self, logger_factory: Any) -> None: self._logger_factory = logger_factory self._logger = None def log(self, message: str) -> None: if not self._logger: self._logger = self._logger_factory() self._logger.log(message) (2) Минимизация создаваемых объектов: Описание: где возможно, объекты переиспользуются, так сокращаются расход памяти и затраты на сборку мусора. from dataclasses import dataclass @dataclass class SingletonLoggerFactory: _instance: Any = None @classmethod def get_instance(cls) -> Any: if cls._instance is None: cls._instance = ConsoleLogger() return cls._instance (3) Эффективные структуры данных: Описание: применяются оптимизированные структуры данных, у которых выше производительность конкретных операций. from collections import defaultdict from dataclasses import dataclass, field from typing import Any, List @dataclass class EfficientObserver(Mediator): _observers: defaultdict[str, List[Any]] = field(default_factory=lambda: defaultdict(list)) def register_observer(self, event: str, observer: Any) -> None: self._observers[event].append(observer) def notify_observers(self, event: str, data: Any) -> None: for observer in self._observers[event]: observer.update(data) (4) Профилирование и сопоставление: Описание: для выявления узких мест производительности, обусловленных шаблонами проектирования, приложение регулярно профилируется. import cProfile def main(): # Логика приложения pass if __name__ == '__main__': cProfile.run('main()') (5) Избежание лишних абстракций: Описание: шаблоны реализуются только там, где ими предоставляются очевидные преимущества, чрезмерные усложнения избегаются. # Простой вариант применения без лишних шаблонов class SimpleLogger: def log(self, message: str) -> None: print(message) Пример сопоставления производительности Сравнение производительности двух реализаций: простой и с шаблоном «Декоратора»: import time from functools import wraps def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # Дополнительное поведение return func(*args, **kwargs) return wrapper @decorator def compute(): return sum(range(1000)) def compute_direct(): return sum(range(1000)) # Сопоставление start = time.time() for _ in range(1000000): compute() end = time.time() print(f"Decorator pattern: {end - start:.4f} seconds") start = time.time() for _ in range(1000000): compute_direct() end = time.time() print(f"Direct call: {end - start:.4f} seconds") Пример вывода: Decorator pattern: 1.5000 seconds Direct call: 1.2000 seconds Анализ: Накладные расходы декоратора: дополнительными вызовами функции обусловливается небольшое снижение производительности декоратора. Компромиссы: декораторы, несмотря на их гибкость и расширенную функциональность, сказываются на производительности в высокочастотных сценариях. Итоги: использование шаблонов проектирования, хотя и чревато снижением производительности, часто оправдано преимуществами в сопровождаемости, гибкости и масштабируемости кода. Важно приводить принципы проектирования в соответствие с требованиями по производительности и, где это необходимо, оптимизировать реализации. Стратегии перехода При переходе от антипаттернов к передовым практикам имеющиеся проблемы решаются систематическим рефакторингом и применением соответствующих шаблонов проектирования. 1. Выявление антипаттернов Выявление имеющихся антипаттернов начинается с анализа кодовой базы. Вот типичные их признаки: «Божественные объекты»: классы, которыми выполняется слишком много обязанностей. Чрезмерное наследование: глубокие и жесткие иерархии классов. Глобальное состояние: синглтоны или глобальные переменные, которыми управляется общее состояние. 2. Выбор шаблона проектирования Шаблон проектирования выбирается под конкретный выявленный антипаттерн. Например: «Божественный объект»: для управления взаимодействиями и обязанностями используется шаблон «Фасад» или «Посредник». Чрезмерное наследование: наследованию предпочитается композиция в шаблонах вроде «Стратегии» или «Декоратора». Глобальное состояние: для управления общими ресурсами синглтоны заменяются внедрением зависимостей. 3. Планирование процесса рефакторинга Чтобы реализовать выбранный шаблон без нарушения имеющейся функциональности, разрабатывается пошаговый план: Модульный рефакторинг: большие классы разбиваются на мелкие, специализированные классы. Постепенные изменения: они вносятся небольшими управляемыми этапами, при этом обеспечивается, что каждый этап будет проверен и стабилен. Резервное копирование и контроль версий: чтобы отслеживать изменения и при необходимости легко откатываться, используется система контроля версий. 4. Реализация и тестирование При выполнении плана рефакторинга обеспечивается, что: Функциональность остается неизменной: при внедрении новых структур имеющийся функционал сохраняется. Комплексное тестирование: что поведение перепроектированного кода обходится без неожиданностей, проверяется модульными и интеграционными тестами. 5. Проверки и повтор После реализации: Проверки кода: соблюдение передовых практик обеспечивается просмотром изменений коллегами. Мониторинг производительности: оценивается влияние новых шаблонов на производительность и при необходимости оптимизируется. Постоянное совершенствование: процесс рефакторинга повторяется, при этом решаются любые возникающие проблемы. Пример преобразования «божественного объекта» в модульные компоненты Перед рефакторингом «божественного объекта»: class GodObject: def __init__(self): self.database = Database() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() # ... много других обязанностей def perform_all_operations(self): self.database.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() # ... много других операций После рефакторинга с разделением обязанностей: class DatabaseManager: def connect(self): # Подключение к базе данных pass class Logger: def log(self, message: str) -> None: print(message) class UserManager: def create_user(self): # Создается пользователь pass class OrderManager: def create_order(self): # Создается заказ pass class Application: def __init__(self): self.database_manager = DatabaseManager() self.logger = Logger() self.user_manager = UserManager() self.order_manager = OrderManager() def perform_operations(self): self.database_manager.connect() self.logger.log("Connected to database.") self.user_manager.create_user() self.order_manager.create_order() Преимущества: Единственная ответственность: каждым классом выполняется конкретная задача. Слабая связанность: классом Application координируются взаимодействия, ни один класс при этом не перегружается. Повышенная сопровождаемость: изменения в одном компоненте не сказываются на несвязанных компонентах. Рекомендации по реализации Практическими рекомендациями по реализации шаблонов проектирования, в том числе обусловленными их контекстом разновидностями, устранением неполадок, компоновкой шаблонов и реальными сценариями, обеспечивается эффективное применение этих шаблонов разработчиками в своих проектах. 1. Обусловленные контекстом разновидности шаблонов Шаблоны проектирования часто адаптируют к конкретным контекстам или требованиям. Пример контекстуализации шаблона «Фабрика» для микросервисов: from abc import ABC, abstractmethod class Service(ABC): @abstractmethod def execute(self): pass class UserService(Service): def execute(self): print("Executing User Service") class OrderService(Service): def execute(self): print("Executing Order Service") class ServiceFactory: @staticmethod def get_service(service_type: str) -> Service: if service_type == 'user': return UserService() elif service_type == 'order': return OrderService() else: raise ValueError("Unknown service type") # Использование в архитектуре микросервисов service = ServiceFactory.get_service('user') service.execute() 2. Рекомендации по устранению типичных проблем Выявлением и устранением типичных проблем реализаций шаблонов экономится время, повышается качество кода. Проблема: экземпляр синглтона не переиспользуется. Этапы по устранению: Проверка реализации метакласса: обеспечивается, что создание экземпляра корректно управляется метаклассом синглтона. Проверка наличия определений метаклассов: избегается определение метаклассов, которые вмешиваются в поведение синглтона. Проверка потокобезопасности: для недопущения экземпляров в условиях многопоточности подтверждается, что реализация синглтона потокобезопасна. Решение: Реализуется потокобезопасный метакласс синглтона, а конкурентный доступ управляется блокировками. import threading class SingletonMeta(type): _instance = None _lock: threading.Lock = threading.Lock() def __call__(cls, *args, **kwargs): with cls._lock: if cls._instance is None: cls._instance = super(SingletonMeta, cls).__call__(*args, **kwargs) return cls._instance 3. Примеры компоновки шаблонов Сочетанием шаблонов проектирования сложные проблемы решаются эффективнее. Пример сочетания шаблонов «Фабрика» и «Декоратор» для большей гибкости: from abc import ABC, abstractmethod from functools import wraps class Component(ABC): @abstractmethod def operation(self): pass class ConcreteComponent(Component): def operation(self): print("ConcreteComponent Operation") class Decorator(Component): def __init__(self, component: Component): self._component = component @abstractmethod def operation(self): pass class ConcreteDecoratorA(Decorator): def operation(self): self._component.operation() self.add_behavior() def add_behavior(self): print("DecoratorA adds behavior") class ConcreteDecoratorB(Decorator): def operation(self): self._component.operation() self.add_behavior() def add_behavior(self): print("DecoratorB adds behavior") class ComponentFactory: @staticmethod def create_component(decorators: list = None) -> Component: component = ConcreteComponent() if decorators: for decorator_cls in decorators: component = decorator_cls(component) return component # Использование decorated_component = ComponentFactory.create_component([ConcreteDecoratorA, ConcreteDecoratorB]) decorated_component.operation() Вывод: ConcreteComponent Operation DecoratorA adds behavior DecoratorB adds behavior Заключение Важность постоянного изучения и отслеживания антипаттернов Шаблоны проектирования — это мощные инструменты, при корректной реализации которых значительно повышаются качество и сопровождаемость приложений Python. Однако неправильное использование или злоупотребление этими шаблонами чревато появлением антипаттернов, из-за которых снижается качество кода и затрудняется разработка. Благодаря передовым практикам — использование шаблонов для решения актуальных проблем, сохранение простоты дизайна и удобства кода — и бдительному отслеживанию типичных антипаттернов вроде «Божественных объектов», неправильного использования синглтона, злоупотребления наследованием и загрязнения шаблонами, эффективным применением шаблонов проектирования разработчики создают надежные, масштабируемые и сопровождаемые программные системы. Сбалансированный подход к использованию шаблонов проектирования Сбалансированный подход подразумевает понимание, когда и как применять шаблоны проектирования, избегая соблазна использовать все подряд. Это: Оценка потребностей: тщательно оценивается, решается ли шаблоном конкретная проблема в кодовой базе. Упрощение дизайна: в стремлении к простоте шаблоны используются для совершенствования, а не усложнения. Продуманный рефакторинг: структуры кода постоянно совершенствуются, антипаттерны заменяются добротно реализованными шаблонами проектирования. Развивая осознанный подход к шаблонам проектирования, разработчики создают надежные, сопровождаемые и масштабируемые приложения, которые выдерживают испытание временем. Ресурсы для дальнейшего обучения Книги: Приемы объектно-ориентированного проектирования. Паттерны проектирования / Э. Гамма, Р. Хелм, Р. Джонсон, Д. Влиссидес; Python и паттерны проектирования / Ч. Гиридхар. Онлайн-руководства: Refactoring Guru — исчерпывающие объяснения и примеры шаблонов проектирования. Шаблоны Python — практические реализации шаблонов проектирования на Python. Курсы: Шаблоны проектирования на Python в Udemy. Освоение шаблонов проектирования на Python в Coursera. Повышайте качество и эффективность кода на Python, применяя эти передовые практики и бдительно отслеживая антипаттерны. Читайте также: Python 4.0: программирование следующего поколения Аттестации: новое поколение подписей в PyPI Линейная регрессия — реализация на Python Читайте нас в Telegram, VK и Дзен Перевод статьи Paul Ammann: Python Design Patterns: Best Practices and Anti-Patterns
|
||
==============
|