Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Template Method и Hooks
template_method

Template Method и Hooks

Скелет алгоритма в базовом классе. Hooks для расширения поведения. ABC и @abstractmethod.

Template Method и Hooks

Template Method задаёт скелет. Hooks позволяют расширять без изменения.


#Template Method: скелет алгоритма

Template Method — поведенческий паттерн, определяющий скелет алгоритма в базовом классе, позволяя подклассам переопределять шаги без изменения структуры.

#Проблема: дублирование структуры

# ❌ ПЛОХО: Дублирование структуры алгоритма class CoffeeMaker: def brew(self): self.boil_water() self.brew_coffee() self.pour() self.add_condiments() def boil_water(self): print("Boiling water") def brew_coffee(self): print("Brewing coffee") def pour(self): print("Pouring into cup") def add_condiments(self): print("Adding sugar and milk") class TeaMaker: def brew(self): self.boil_water() # Дублирование self.brew_tea() self.pour() self.add_condiments() def boil_water(self): print("Boiling water") def brew_tea(self): print("Steeping tea") def pour(self): print("Pouring into cup") def add_condiments(self): print("Adding lemon and honey") # Дублирование boil_water() и pour()

#Решение: Template Method

# ✅ ХОРОШО: Скелет в базовом классе from abc import ABC, abstractmethod class CaffeineBeverage(ABC): """Шаблонный метод""" def prepare(self): """Скелет алгоритма — не переопределяется""" self.boil_water() self.brew() self.pour() self.add_condiments() def boil_water(self): print("Boiling water") def pour(self): print("Pouring into cup") @abstractmethod def brew(self): pass @abstractmethod def add_condiments(self): pass class Coffee(CaffeineBeverage): def brew(self): print("Brewing coffee grounds") def add_condiments(self): print("Adding sugar and milk") class Tea(CaffeineBeverage): def brew(self): print("Steeping tea bag") def add_condiments(self): print("Adding lemon and honey") # Использование coffee = Coffee() coffee.prepare() # Boiling water # Brewing coffee grounds # Pouring into cup # Adding sugar and milk tea = Tea() tea.prepare() # Boiling water # Steeping tea bag # Pouring into cup # Adding lemon and honey

#Hooks: точки расширения

#Hooks с реализацией по умолчанию

# ✅ Hooks позволяют расширять поведение from abc import ABC, abstractmethod class DataProcessor(ABC): def process(self, data: str): """Шаблонный метод с hooks""" if self.validate_input(data): data = self.preprocess(data) result = self.transform(data) result = self.postprocess(result) self.save_result(result) else: print("Validation failed") def validate_input(self, data: str) -> bool: """Hook — может быть переопределён""" return True # По умолчанию всегда True def preprocess(self, data: str) -> str: """Hook — может быть переопределён""" return data @abstractmethod def transform(self, data: str) -> str: pass def postprocess(self, result: str) -> str: """Hook — может быть переопределён""" return result def save_result(self, result: str): """Hook — может быть переопределён""" print(f"Saving: {result}") class UpperCaseProcessor(DataProcessor): def transform(self, data: str) -> str: return data.upper() class LoggingProcessor(DataProcessor): def transform(self, data: str) -> str: return data.upper() def validate_input(self, data: str) -> bool: print(f"Validating: {data}") return len(data) > 0 def preprocess(self, data: str) -> str: print(f"Preprocessing: {data}") return data.strip() def postprocess(self, result: str) -> str: print(f"Postprocessing: {result}") return f"***{result}***" # Использование proc = UpperCaseProcessor() proc.process("hello") # HELLO # Saving: HELLO proc2 = LoggingProcessor() proc2.process(" hello ") # Validating: hello # Preprocessing: hello # Postprocessing: HELLO # Saving: ***HELLO***

#Практическое применение

#API Client с Template Method

# ✅ Шаблон для API-клиентов from abc import ABC, abstractmethod import json import requests class APIClient(ABC): def __init__(self, base_url: str): self.base_url = base_url def request(self, method: str, endpoint: str, data: dict = None): """Шаблонный метод""" url = self._build_url(endpoint) headers = self._prepare_headers() if self._should_log(): self._log_request(method, url, data) response = self._send_request(method, url, headers, data) if self._should_cache(): self._cache_response(response) return self._parse_response(response) def _build_url(self, endpoint: str) -> str: return f"{self.base_url}/{endpoint}" def _prepare_headers(self) -> dict: return {"Content-Type": "application/json"} def _should_log(self) -> bool: return True def _log_request(self, method: str, url: str, data: dict): print(f"{method} {url}: {data}") @abstractmethod def _send_request(self, method: str, url: str, headers: dict, data: dict): pass def _should_cache(self) -> bool: return False def _cache_response(self, response): pass @abstractmethod def _parse_response(self, response): pass class RequestsClient(APIClient): def _send_request(self, method: str, url: str, headers: dict, data: dict): return requests.request(method, url, headers=headers, json=data) def _parse_response(self, response): return response.json() class CachedClient(RequestsClient): def __init__(self, base_url: str): super().__init__(base_url) self._cache = {} def _should_cache(self) -> bool: return True def _cache_response(self, response): key = response.request.url self._cache[key] = response.json() # Использование client = RequestsClient("https://api.example.com") response = client.request("GET", "users")

#ETL Pipeline

# ✅ ETL с Template Method from abc import ABC, abstractmethod from typing import List, Any class ETLPipeline(ABC): def run(self, source: str, destination: str): """Шаблонный метод ETL""" self._notify_start() data = self.extract(source) data = self.transform(data) self.load(data, destination) self._notify_complete() def _notify_start(self): print("ETL started") def _notify_complete(self): print("ETL completed") @abstractmethod def extract(self, source: str) -> List[Any]: pass @abstractmethod def transform(self, data: List[Any]) -> List[Any]: pass @abstractmethod def load(self, data: List[Any], destination: str): pass class CSVToDatabasePipeline(ETLPipeline): def extract(self, source: str) -> List[dict]: print(f"Reading CSV: {source}") # Чтение CSV return [{"id": 1, "name": "Alice"}] def transform(self, data: List[dict]) -> List[dict]: print("Transforming data") # Трансформация return data def load(self, data: List[dict], destination: str): print(f"Loading to DB: {destination}") # Загрузка в БД # Использование pipeline = CSVToDatabasePipeline() pipeline.run("input.csv", "postgresql://localhost/db")

#Report Generator

# ✅ Генератор отчётов from abc import ABC, abstractmethod from datetime import datetime class ReportGenerator(ABC): def generate(self, start_date: datetime, end_date: datetime): """Шаблонный метод""" data = self.fetch_data(start_date, end_date) grouped = self.group_data(data) formatted = self.format_report(grouped) if self.include_summary(): formatted = self.add_summary(formatted) return formatted @abstractmethod def fetch_data(self, start: datetime, end: datetime) -> list: pass def group_data(self, data: list) -> dict: """Hook — может быть переопределён""" return {"data": data} @abstractmethod def format_report(self, grouped: dict) -> str: pass def include_summary(self) -> bool: """Hook""" return True def add_summary(self, report: str) -> str: """Hook""" return report + "\n\n--- Summary ---" class SalesReport(ReportGenerator): def fetch_data(self, start: datetime, end: datetime) -> list: return [{"date": start, "amount": 100}] def format_report(self, grouped: dict) -> str: return f"Sales Report: {grouped}" class PDFReport(ReportGenerator): def fetch_data(self, start: datetime, end: datetime) -> list: return [] def format_report(self, grouped: dict) -> str: return f"%PDF-1.4\n{grouped}" def include_summary(self) -> bool: return False # Использование report = SalesReport() result = report.generate(datetime(2024, 1, 1), datetime(2024, 1, 31))

#Python в стандартной библиотеке

#collections.abc

# ✅ Template Method в ABC from collections.abc import Sequence class MySequence(Sequence): """Нужно реализовать только __getitem__ и __len__""" def __getitem__(self, index): pass def __len__(self): pass # Остальные методы (index, count, __contains__) # реализованы через Template Method # Использование class MyList(MySequence): def __init__(self, items): self._items = items def __getitem__(self, index): return self._items[index] def __len__(self): return len(self._items) ml = MyList([1, 2, 3]) print(2 in ml) # True — __contains__ реализован через __getitem__

#unittest.TestCase

# ✅ Template Method в unittest import unittest class MyTest(unittest.TestCase): """unittest использует Template Method""" def setUp(self): """Hook — вызывается перед каждым тестом""" pass def tearDown(self): """Hook — вызывается после каждого теста""" pass def test_something(self): """Тест""" pass # run() — шаблонный метод, который вызывает: # setUp() → test_*() → tearDown()

#Что дальше?

Изучите тему Visitor и Double Dispatch для операций над объектами без изменения классов.

Далее: Visitor и Double Dispatch