Пять паттернов — про границы: как компоновать классы и объекты в более крупные структуры, не ломая интерфейсы.
Этап недели 6 «Платёжной системы»: Repository + Adapter, команды консоли
Этап недели 6: хранилище через Repository; при необходимости Adapter для «старого» хранилища; команды консоли для оплаты и истории.
Связка: «старый» модуль хранения save_to_json/load_from_json (как в вашем «Университете») нужно спрятать за интерфейсом Storage — это Adapter; фасад для FraudChecker, Gateway, Ledger — Facade.
Подпись: структурные паттерны — про границы: между модулем и хранилищем, между консолью и подсистемой оплаты.
Какой паттерн спрячет старое хранилище? (не отвечаем — узнаем через 20 минут)
Не открывая конспект: что такое паттерн и каков критерий уместности?
Что такое паттерн проектирования? Назовите критерий уместности паттерна.
Подсказка: закончите фразу — «код с паттерном …»
Эталон: «типовое решение типовой задачи»; «код с паттерном проще менять, чем код без него» — этот критерий — лакмус для всех пяти паттернов недели.
К концу лекции вы сможете…
интегрировать несовместимые интерфейсы через Adapter;
упрощать подсистемы через Facade, не превращая его в класс-гигант;
добавлять кэш/контроль через Proxy и отличать его от Adapter;
расширять поведение обёртками (Decorator) и отличать их от @decorator;
обрабатывать деревья единообразно через Composite;
упрощать структурные паттерны за счёт duck typing и Protocol.
Все пять паттернов — про границы и интерфейсы; сегодня научитесь выбирать между ними по одному признаку.
Структурные паттерны компонуют классы; duck typing и Protocol упрощают их в Python
Определение: структурные паттерны показывают, как компоновать классы и объекты в более крупные структуры, не ломая интерфейсы.
Акцент для Python: duck typing и Protocolупрощают многие структурные паттерны: если объект «имеет нужный метод», интерфейс считается удовлетворённым без наследования.
Пример: Адаптер часто — просто функция-обёртка, а не класс из GoF; Protocol задаёт контракт на границе модулей (видели на неделе 5).
Правило: «показывать дух паттерна, а не дословную схему GoF».
Что значит «объект имеет нужный метод» и как это проверить? (duck typing — вызов работает; Protocol — статическая проверка)
Боль Adapter: старое хранилище говорит на другом языке
class OldStorage: # чужой/legacy кодdefsave_record(self, data: dict) -> None: ...
class NewClient:
defstore(self, storage, item) -> None:
storage.store(item.to_dict()) # у OldStorage нет store!
Клиент вызывает store(item), а класс умеет только save_record(data) — интерфейсы не совпадают.
Adapter: клиент — обёртка — legacy; клиент и legacy не меняются
Клиент знает только store — для него Adapter и есть хранилище; внутри Adapter зовёт legacy: store(item) → save_record(item.to_dict()).
Что случится, если в Adapter положить бизнес-логику? (он перестанет быть адаптером, станет сервисом — типичная ошибка)
Proxy vs Adapter: тот же интерфейс vs другой интерфейс
Главный критерий выбора недели: Proxy — тот же интерфейс, Adapter — другой. Если обёртка меняет интерфейс — это Adapter; если сохраняет и добавляет кэш/контроль — это Proxy.
Сформулируйте правило хором: «тот же — …, другой — …»
Предскажите результат
CourseRepositoryProxy — кэш: повторный get не ходит в «базу»
class Course:
def__init__(self, code: str) -> None:
self.code = code
class CourseRepository: # «дорогой» объектdef__init__(self) -> None:
self.calls = 0
defget(self, code: str) -> Course:
self.calls += 1 # имитация обращения к БДreturn Course(code)
class CourseRepositoryProxy:
def__init__(self, repo: CourseRepository) -> None:
self._repo = repo
self._cache: dict[str, Course] = {}
defget(self, code: str) -> Course:
if code not in self._cache:
self._cache[code] = self._repo.get(code) # дорогой вызовreturn self._cache[code]
Правило: интерфейс тот же — get(code); повторный запрос берётся из кэша, «база» не вызывается.
Когда не применять: объект дешёвый — заместитель лишний; если логику кэша проще положить в сам репозиторий.
Сколько раз будет вызван repo.get при двух запросах одного кода? (один — второй запрос из кэша)
Proxy: ленивая инициализация, кэш, контроль доступа, лог, удалённый доступ
Ленивая инициализация — отложить создание дорогого объекта до первого использования.
Кэш — сохранить результат дорогого вызова (пример на Слайде 10).
Контроль доступа — проверить права перед делегированием.
Логирование — писать в лог до/после вызова.
Удалённый доступ — локальный заместитель удалённого сервиса.
Типичная ошибка: кэш без инвалидации — устаревшие данные.
Какая задача из списка есть в вашем «Университете»? (например, кэш репозитория курсов)
Боль Facade: клиент разбирается во всей подсистеме оплаты
class FraudChecker:
defis_suspicious(self, card) -> bool: ...
class Gateway:
defcharge(self, amount: float, card) -> bool: ...
class Ledger:
defrecord(self, amount: float, card) -> None: ...
# клиент делает платёж «вручную»ifnot fraud.is_suspicious(card):
if gateway.charge(amount, card):
ledger.record(amount, card)
Боль: чтобы провести платёж, клиент должен знать три класса, их порядок и обработку ошибок; этот код дублируется в каждом сценарии; подсистема «дышит» в лицо клиенту.
Что будет, если добавится четвёртый класс — комиссия? (клиента придётся править везде)
PaymentFacade.pay() — три действия за одной строкой клиента
Правило: клиент не знает, лист перед ним или узел — операция узла рекурсивно применяется к детям. В клиенте больше нет isinstance.
Чему равен project.total_time()? (3 + (2 + 4) = 9)
Composite: дерево категорий — листья и узлы обрабатываются одинаково
project.total_time() = 3 + (2 + 4) = 9; клиент вызывает один метод — рекурсию делает узел. Считаем снизу вверх: лист возвращает оценку, узел — сумму детей.
Что вернёт пустой TaskGroup([])? (0 — сумма пустого списка; важно для граничного случая)
Composite: когда применять / когда не применять; где живут деревья
Когда применять
иерархии «часть — целое» с единой обработкой — категории, меню, файловая система, оргструктура, курсы и занятия.
базовый приём для деревьев в алгоритмах и структурах данных (обход, поиск, агрегация).
Когда не применять
плоские данные без вложенности — композит избыточен.
если поведение узла зависит от состояния — присмотритесь к State (неделя 7).
Типичные ошибки: клиент проверяет isinstance на лист/узел — теряется единообразие; рекурсия без ограничения глубины; узел без проверки детей.
Какие деревья вы уже видели в своих проектах? (категории, меню, файлы)
Live-coding
Live-coding: Adapter для «старого» хранилища
Задача: дан OldStorage с методом save_record(data); написать StorageAdapter под новый интерфейс store(item); показать, что клиент, написанный под новый интерфейс, работает со старым хранилищем без правок.
Ожидаемые результаты:adapter.store(Item(...)) печатает сохранённый dict; клиент вызывает только store.
Порядок:
показать «боль» — несовместимые методы;
написать класс-обёртку;
проверить, что клиент не менялся;
обсудить, почему бизнес-логика в адаптере — ошибка.
Повторите на своих данных: зачем адаптеру делегирование?
Live-coding
Live-coding: Facade для подсистемы оплаты
Задача: пишем с нуля классы FraudChecker, Gateway, Ledger (заглушки); PaymentFacade.pay(amount, card); клиент — одна строка.
Ключевая проверка: добавление нового шага (комиссия) меняет только фасад, не клиента.
Акцент: «фасад — тонкая дверь, без бизнес-логики».
Где граница между фасадом и классом-гигантом? (фасад делегирует, не реализует)
Проверь себя
Проверь себя: 4 вопроса перед семинаром
Чем Proxy отличается от Adapter? Чем Decorator-паттерн от @decorator?
Когда фасад уместен, а когда превращается в класс-гигант?
Как duck typing упрощает структурные паттерны в Python?
Приведите пример дерева, где Composite даёт единый интерфейс.
2 минуты письменно, затем короткий разбор; эталоны — в решения-6.md.
Разбор ошибки
Обёртка без делегирования: метод «съеден», клиент ломается
Разбор: формально метод есть и возвращает True, но настоящее поведение Payment не выполняется — обёртка «съела» делегирование. В большом коде это выглядит как «почему-то не работает оплата», и traceback не поможет — ошибка логическая.
Правило: обёртка обязана делегировать вызов реальному объекту; если метод обёртки возвращает результат сам — проверьте, не забыли ли вы self._payment.pay().
Как защититься? (тест, который проверяет, что внутренний объект реально вызван — например, через счётчик вызовов)
Типичные ошибки недели 6
Адаптер тащит бизнес-логику (должен только переводить вызовы); адаптация «на всякий случай» без реальной несовместимости.
Фасад превращается в класс-гигант — вся логика стекается в него.
Путаница Proxy (тот же интерфейс) и Adapter (другой).
Кэш без инвалидации — устаревшие данные.
Путаница паттерна Decorator и синтаксического @decorator; обёртка не сохраняет интерфейс; забыто делегирование.
isinstance на лист/узел в Composite — теряется единообразие.
Отметьте знакомые ошибки в своём коде — каждая задача семинара «ловит» одну из них.
Связка с проектом: этап недели 6 «Платёжной системы»
Этап недели 6: хранилище через Repository (Storage с save/load); при необходимости Adapter для «старого» хранилища (например, JSON-модуль из «Университета»); команды консоли для оплаты и истории.
Подпись: фасад и адаптер — это границы, которые в М3 станут слоями и портами/адаптерами.
Storage — это интерфейс, а Adapter прячет реализацию: завтра поменяете JSON на SQLite — клиент не заметит.
Какой паттерн спрячет старое хранилище? (Adapter — ответ на вопрос Слайда 2)
Сегодня вы научились…
интегрировать интерфейсы через Adapter;
упрощать подсистемы через Facade, не превращая его в класс-гигант;
добавлять кэш/контроль через Proxy;
отличать Proxy от Adapter по интерфейсу;
расширять поведение обёртками Decorator;
обрабатывать деревья через Composite.
Всё, что обещали в начале (Слайд 4), — сделали?
Рефлексия
One-minute paper: главное + один вопрос
Напишите (1 минута, не подписывая):
самое главное про структурные паттерны, что вы сегодня поняли;
один вопрос, который остался.
Соберите бумажки: преподаватель читает 2–3 анонимно и отвечает.
Это сигнал для семинара: что разобрать подробнее.
Что дальше: семинар недели 6, ДЗ task-06, анонс недели 7
Семинар (семинар-6.md): Adapter для «старого» хранилища, Facade для оплаты, Proxy с кэшем, Decorator-обёртки, Composite «Категории».
ДЗ (autograder): композит или адаптер — задачи 05–06 из банка задач, критерии 0–2.
Анонс недели 7: поведенческие паттерны — Strategy, Observer, Command, Template Method, State, Iterator («как распределять ответственность и взаимодействие»).