code-ai-installer 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,694 @@
1
+ ---
2
+ name: design_patterns_reference
3
+ description: Справочник паттернов проектирования с DO/DON'T примерами — SOLID, DRY/KISS/YAGNI, GoF (Strategy, Observer, Factory, Adapter, Facade, Decorator, Command, State, Template Method), архитектурные (Repository, Service Layer, DI, Event-Driven, CQRS), микросервисные (Saga, Circuit Breaker). Language-agnostic псевдокод. Используй при проектировании модулей, ревью архитектуры, или при вопросах «какой паттерн применить».
4
+ ---
5
+
6
+ # Skill: Design Patterns Reference
7
+
8
+ DO/DON'T справочник паттернов проектирования. Language-agnostic псевдокод.
9
+
10
+ **Разделы:**
11
+ 1. [SOLID](#1-solid)
12
+ 2. [Fundamental Principles](#2-fundamental-principles)
13
+ 3. [GoF Patterns](#3-gof-patterns)
14
+ 4. [Architectural Patterns](#4-architectural-patterns)
15
+ 5. [Microservices Patterns](#5-microservices-patterns)
16
+ 6. [Правило выбора паттерна](#6-правило-выбора)
17
+
18
+ ---
19
+
20
+ ## 1. SOLID
21
+
22
+ ### S — Single Responsibility Principle (SRP)
23
+ > Класс/модуль имеет ровно одну причину для изменения.
24
+
25
+ ```
26
+ // ✅ DO: каждый класс — одна ответственность
27
+ class UserValidator
28
+ validate(user) → ValidationResult
29
+
30
+ class UserRepository
31
+ save(user) → void
32
+
33
+ class EmailNotifier
34
+ sendWelcome(user) → void
35
+ ```
36
+
37
+ ```
38
+ // ❌ DON'T: один класс делает всё
39
+ class UserManager
40
+ validate(user) → ...
41
+ saveToDb(user) → ...
42
+ sendEmail(user) → ...
43
+ generateReport() → ...
44
+ // Изменение email-логики ломает валидацию и БД
45
+ ```
46
+
47
+ ### O — Open/Closed Principle (OCP)
48
+ > Открыт для расширения, закрыт для модификации.
49
+
50
+ ```
51
+ // ✅ DO: новый тип = новый класс, без изменения существующего кода
52
+ interface DiscountStrategy
53
+ calculate(order) → number
54
+
55
+ class PercentDiscount implements DiscountStrategy
56
+ calculate(order) → order.total * this.percent
57
+
58
+ class FixedDiscount implements DiscountStrategy
59
+ calculate(order) → this.amount
60
+
61
+ class OrderService
62
+ applyDiscount(order, strategy: DiscountStrategy)
63
+ order.discount = strategy.calculate(order)
64
+ ```
65
+
66
+ ```
67
+ // ❌ DON'T: каждый новый тип скидки — правка if/switch
68
+ class OrderService
69
+ applyDiscount(order, type)
70
+ if type == "percent" → ...
71
+ else if type == "fixed" → ...
72
+ else if type == "seasonal" → ... // каждый раз меняем этот метод
73
+ ```
74
+
75
+ ### L — Liskov Substitution Principle (LSP)
76
+ > Подтипы можно подставлять вместо базового типа без поломки.
77
+
78
+ ```
79
+ // ✅ DO: подтипы честно реализуют контракт
80
+ interface Shape
81
+ area() → number
82
+
83
+ class Rectangle implements Shape
84
+ area() → this.width * this.height
85
+
86
+ class Circle implements Shape
87
+ area() → π * this.radius²
88
+
89
+ // Любой Shape можно передать в calculateTotalArea(shapes[])
90
+ ```
91
+
92
+ ```
93
+ // ❌ DON'T: подтип нарушает контракт родителя
94
+ class Rectangle
95
+ setWidth(w), setHeight(h)
96
+
97
+ class Square extends Rectangle
98
+ setWidth(w) → this.width = w; this.height = w // сюрприз: setWidth меняет height
99
+ // Код, который ожидает Rectangle, ломается
100
+ ```
101
+
102
+ ### I — Interface Segregation Principle (ISP)
103
+ > Много маленьких интерфейсов лучше одного толстого.
104
+
105
+ ```
106
+ // ✅ DO: клиент зависит только от нужных методов
107
+ interface Readable
108
+ read() → Data
109
+
110
+ interface Writable
111
+ write(data) → void
112
+
113
+ interface Deletable
114
+ delete(id) → void
115
+
116
+ class FileStore implements Readable, Writable, Deletable
117
+ class ReadOnlyCache implements Readable
118
+ ```
119
+
120
+ ```
121
+ // ❌ DON'T: один толстый интерфейс — клиенты зависят от лишнего
122
+ interface Storage
123
+ read() → Data
124
+ write(data) → void
125
+ delete(id) → void
126
+ backup() → void
127
+ migrate() → void
128
+
129
+ class SimpleCache implements Storage
130
+ backup() → throw "Not supported" // нарушение контракта
131
+ migrate() → throw "Not supported"
132
+ ```
133
+
134
+ ### D — Dependency Inversion Principle (DIP)
135
+ > Завись от абстракций, не от конкретных реализаций.
136
+
137
+ ```
138
+ // ✅ DO: бизнес-логика зависит от интерфейса
139
+ interface PaymentGateway
140
+ charge(amount) → Result
141
+
142
+ class OrderService
143
+ constructor(gateway: PaymentGateway) // инъекция абстракции
144
+ checkout(order) → this.gateway.charge(order.total)
145
+
146
+ // Легко подменить: StripeGateway, PayPalGateway, TestGateway
147
+ ```
148
+
149
+ ```
150
+ // ❌ DON'T: прямая зависимость от конкретики
151
+ class OrderService
152
+ checkout(order)
153
+ stripe = new StripeClient(API_KEY) // жёстко привязан к Stripe
154
+ stripe.charge(order.total)
155
+ // Нельзя тестировать, нельзя заменить
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 2. Fundamental Principles
161
+
162
+ ### DRY — Don't Repeat Yourself
163
+ ```
164
+ // ✅ DO: переиспользуемая функция
165
+ function formatCurrency(amount, currency)
166
+ return currency.symbol + amount.toFixed(2)
167
+
168
+ // Используется в Invoice, Cart, Report
169
+ ```
170
+
171
+ ```
172
+ // ❌ DON'T: копипаст одной и той же логики
173
+ // В Invoice: "$" + amount.toFixed(2)
174
+ // В Cart: "$" + amount.toFixed(2)
175
+ // В Report: "$" + amount.toFixed(2)
176
+ // Изменение формата → правка в 3+ местах
177
+ ```
178
+
179
+ > ⚠️ **Осторожно:** DRY — про знание, не про код. Два одинаковых куска кода с разными причинами изменения — это НЕ дублирование.
180
+
181
+ ### KISS — Keep It Simple, Stupid
182
+ ```
183
+ // ✅ DO: простое и читаемое решение
184
+ function isAdult(age)
185
+ return age >= 18
186
+ ```
187
+
188
+ ```
189
+ // ❌ DON'T: переусложнение
190
+ function isAdult(age)
191
+ ageValidator = new AgeValidatorFactory.create("adult")
192
+ rule = ageValidator.getRuleEngine().getRule("minimum")
193
+ return rule.evaluate(new AgeContext(age))
194
+ ```
195
+
196
+ ### YAGNI — You Aren't Gonna Need It
197
+ ```
198
+ // ✅ DO: реализуй то, что нужно сейчас
199
+ class UserService
200
+ getUser(id) → User
201
+ createUser(data) → User
202
+
203
+ // ❌ DON'T: "на будущее"
204
+ class UserService
205
+ getUser(id) → User
206
+ createUser(data) → User
207
+ exportToXml() → ... // никто не просил
208
+ syncWithLdap() → ... // нет требования
209
+ generatePdfReport() → ... // "вдруг пригодится"
210
+ ```
211
+
212
+ ### Composition over Inheritance
213
+ ```
214
+ // ✅ DO: композиция — гибко, тестируемо
215
+ class Logger
216
+ log(msg) → ...
217
+
218
+ class HttpClient
219
+ constructor(logger: Logger)
220
+ get(url) → this.logger.log(...); fetch(url)
221
+
222
+ class CachedHttpClient
223
+ constructor(client: HttpClient, cache: Cache)
224
+ get(url) → cache.get(url) ?? client.get(url)
225
+ ```
226
+
227
+ ```
228
+ // ❌ DON'T: глубокая иерархия наследования
229
+ class BaseClient → class HttpClient → class AuthHttpClient → class CachedAuthHttpClient
230
+ // 4 уровня наследования — хрупко, сложно менять
231
+ ```
232
+
233
+ ### Fail Fast
234
+ ```
235
+ // ✅ DO: проверяй входы сразу
236
+ function transfer(from, to, amount)
237
+ if amount <= 0 → throw InvalidAmountError
238
+ if from.balance < amount → throw InsufficientFundsError
239
+ // ... основная логика только если всё валидно
240
+
241
+ // ❌ DON'T: ошибка всплывает где-то глубоко внутри
242
+ function transfer(from, to, amount)
243
+ from.balance -= amount // может стать отрицательным
244
+ to.balance += amount // данные уже испорчены
245
+ if from.balance < 0 → ... // поздно!
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 3. GoF Patterns
251
+
252
+ ### Strategy — заменяемые алгоритмы
253
+ ```
254
+ // ✅ DO: алгоритм как инъектируемая стратегия
255
+ interface SortStrategy
256
+ sort(items[]) → items[]
257
+
258
+ class BubbleSort implements SortStrategy
259
+ class QuickSort implements SortStrategy
260
+ class MergeSort implements SortStrategy
261
+
262
+ class DataProcessor
263
+ constructor(strategy: SortStrategy)
264
+ process(data) → this.strategy.sort(data)
265
+ ```
266
+
267
+ **Когда:** несколько вариантов алгоритма, выбор в runtime.
268
+
269
+ ### Observer — уведомления об изменениях
270
+ ```
271
+ // ✅ DO: подписка/уведомление без жёсткой связи
272
+ class EventBus
273
+ subscribers = Map<string, Function[]>
274
+ subscribe(event, handler)
275
+ publish(event, data) → subscribers[event].forEach(h => h(data))
276
+
277
+ // Компоненты подписываются, не зная друг о друге
278
+ bus.subscribe("order.created", sendEmail)
279
+ bus.subscribe("order.created", updateInventory)
280
+ bus.subscribe("order.created", logAudit)
281
+ ```
282
+
283
+ **Когда:** один источник события, много потребителей.
284
+
285
+ ### Factory Method — создание без привязки к конкретному классу
286
+ ```
287
+ // ✅ DO: фабрика выбирает реализацию
288
+ class NotificationFactory
289
+ static create(type) →
290
+ if type == "email" → new EmailNotification()
291
+ if type == "sms" → new SmsNotification()
292
+ if type == "push" → new PushNotification()
293
+ throw UnknownTypeError
294
+
295
+ // Клиент не знает про конкретные классы
296
+ notification = NotificationFactory.create(user.preference)
297
+ notification.send(message)
298
+ ```
299
+
300
+ **Когда:** тип объекта определяется в runtime; клиент не должен знать конкретику.
301
+
302
+ ### Adapter — совместимость несовместимого
303
+ ```
304
+ // ✅ DO: адаптер оборачивает чужой интерфейс в ваш
305
+ interface PaymentGateway
306
+ charge(amount, currency) → Result
307
+
308
+ class StripeAdapter implements PaymentGateway
309
+ constructor(stripeClient)
310
+ charge(amount, currency) →
311
+ this.stripeClient.createPaymentIntent({
312
+ amount: amount * 100, // Stripe принимает в центах
313
+ currency: currency
314
+ })
315
+ ```
316
+
317
+ **Когда:** интеграция с внешним API/библиотекой, интерфейс которой не совпадает с вашим.
318
+
319
+ ### Facade — простой интерфейс к сложной подсистеме
320
+ ```
321
+ // ✅ DO: фасад скрывает сложность
322
+ class OrderFacade
323
+ constructor(inventory, payment, shipping, notification)
324
+
325
+ placeOrder(cart, user) →
326
+ inventory.reserve(cart.items)
327
+ payment.charge(user, cart.total)
328
+ shipping.createShipment(user.address, cart.items)
329
+ notification.sendConfirmation(user, cart)
330
+
331
+ // Клиент вызывает один метод вместо четырёх подсистем
332
+ orderFacade.placeOrder(cart, user)
333
+ ```
334
+
335
+ **Когда:** сложная подсистема из нескольких компонентов; клиенту нужен простой вход.
336
+
337
+ ### Decorator — динамическое расширение поведения
338
+ ```
339
+ // ✅ DO: обёртки добавляют поведение, не меняя оригинал
340
+ interface DataSource
341
+ read() → Data
342
+ write(data) → void
343
+
344
+ class FileDataSource implements DataSource
345
+ class EncryptionDecorator implements DataSource
346
+ constructor(source: DataSource)
347
+ write(data) → this.source.write(encrypt(data))
348
+ read() → decrypt(this.source.read())
349
+
350
+ class CompressionDecorator implements DataSource
351
+ constructor(source: DataSource)
352
+ write(data) → this.source.write(compress(data))
353
+
354
+ // Комбинируем: сжатие + шифрование + файл
355
+ source = new CompressionDecorator(new EncryptionDecorator(new FileDataSource()))
356
+ ```
357
+
358
+ **Когда:** нужно комбинировать поведения в разных сочетаниях без взрыва подклассов.
359
+
360
+ ### Command — инкапсуляция действия как объекта
361
+ ```
362
+ // ✅ DO: каждое действие = объект с execute/undo
363
+ interface Command
364
+ execute() → void
365
+ undo() → void
366
+
367
+ class AddItemCommand implements Command
368
+ constructor(cart, item)
369
+ execute() → this.cart.add(this.item)
370
+ undo() → this.cart.remove(this.item)
371
+
372
+ class CommandHistory
373
+ stack = []
374
+ execute(cmd) → cmd.execute(); stack.push(cmd)
375
+ undo() → stack.pop().undo()
376
+ ```
377
+
378
+ **Когда:** undo/redo, очередь операций, отложенное выполнение.
379
+
380
+ ### State — поведение зависит от состояния
381
+ ```
382
+ // ✅ DO: каждое состояние = отдельный класс
383
+ interface OrderState
384
+ next(order) → void
385
+ cancel(order) → void
386
+
387
+ class PendingState implements OrderState
388
+ next(order) → order.setState(new PaidState())
389
+ cancel(order) → order.setState(new CancelledState())
390
+
391
+ class PaidState implements OrderState
392
+ next(order) → order.setState(new ShippedState())
393
+ cancel(order) → throw "Cannot cancel paid order"
394
+
395
+ class Order
396
+ state: OrderState = new PendingState()
397
+ next() → this.state.next(this)
398
+ cancel() → this.state.cancel(this)
399
+ ```
400
+
401
+ ```
402
+ // ❌ DON'T: switch/if на строковый статус
403
+ class Order
404
+ status = "pending"
405
+ next()
406
+ if status == "pending" → status = "paid"
407
+ else if status == "paid" → status = "shipped"
408
+ // растёт бесконечно при добавлении статусов
409
+ ```
410
+
411
+ **Когда:** объект с множеством состояний и разным поведением в каждом.
412
+
413
+ ### Template Method — каркас алгоритма с переопределяемыми шагами
414
+ ```
415
+ // ✅ DO: базовый класс определяет каркас, шаги переопределяются
416
+ abstract class DataImporter
417
+ import(source) // template method
418
+ data = this.read(source)
419
+ validated = this.validate(data)
420
+ transformed = this.transform(validated)
421
+ this.save(transformed)
422
+
423
+ abstract read(source) → RawData
424
+ abstract validate(data) → ValidData
425
+ abstract transform(data) → FinalData
426
+ save(data) → db.insert(data) // общая реализация
427
+
428
+ class CsvImporter extends DataImporter
429
+ read(source) → parseCsv(source)
430
+ validate(data) → validateCsvRows(data)
431
+ transform(data) → mapCsvToEntities(data)
432
+
433
+ class JsonImporter extends DataImporter
434
+ read(source) → parseJson(source)
435
+ validate(data) → validateJsonSchema(data)
436
+ transform(data) → mapJsonToEntities(data)
437
+ ```
438
+
439
+ **Когда:** одинаковый каркас алгоритма, разные детали реализации.
440
+
441
+ ---
442
+
443
+ ## 4. Architectural Patterns
444
+
445
+ ### Repository — изоляция доступа к данным
446
+ ```
447
+ // ✅ DO: репозиторий инкапсулирует работу с хранилищем
448
+ interface UserRepository
449
+ findById(id) → User | null
450
+ findByEmail(email) → User | null
451
+ save(user) → User
452
+ delete(id) → void
453
+
454
+ class PostgresUserRepository implements UserRepository
455
+ findById(id) → db.query("SELECT ... WHERE id = $1", [id])
456
+
457
+ class MongoUserRepository implements UserRepository
458
+ findById(id) → collection.findOne({ _id: id })
459
+
460
+ // Бизнес-логика не знает про SQL/Mongo
461
+ class UserService
462
+ constructor(repo: UserRepository)
463
+ getUser(id) → this.repo.findById(id)
464
+ ```
465
+
466
+ ```
467
+ // ❌ DON'T: SQL в бизнес-логике
468
+ class UserService
469
+ getUser(id)
470
+ result = db.query("SELECT * FROM users WHERE id = $1", [id])
471
+ // Привязан к PostgreSQL, нельзя тестировать без БД
472
+ ```
473
+
474
+ ### Service Layer — бизнес-логика не в контроллерах
475
+ ```
476
+ // ✅ DO: контроллер → сервис → репозиторий
477
+ class OrderController
478
+ constructor(orderService)
479
+ handleCreateOrder(req)
480
+ order = this.orderService.create(req.body)
481
+ return Response(201, order)
482
+
483
+ class OrderService
484
+ constructor(orderRepo, paymentGateway, inventory)
485
+ create(data)
486
+ this.inventory.reserve(data.items)
487
+ order = Order.create(data)
488
+ this.paymentGateway.charge(order.total)
489
+ this.orderRepo.save(order)
490
+ return order
491
+
492
+ // ❌ DON'T: бизнес-логика в контроллере
493
+ class OrderController
494
+ handleCreateOrder(req)
495
+ db.query("INSERT INTO orders ...") // SQL в контроллере
496
+ stripe.charge(...) // платёж в контроллере
497
+ sendEmail(...) // и email тоже
498
+ ```
499
+
500
+ ### Dependency Injection (DI)
501
+ ```
502
+ // ✅ DO: зависимости передаются извне
503
+ class Application
504
+ start()
505
+ db = new PostgresConnection(config.db)
506
+ userRepo = new PostgresUserRepository(db)
507
+ emailService = new SmtpEmailService(config.smtp)
508
+ userService = new UserService(userRepo, emailService)
509
+ controller = new UserController(userService)
510
+ server.register(controller)
511
+
512
+ // Каждый компонент принимает зависимости через конструктор
513
+ // Легко тестировать: подставляем mock-репозиторий
514
+ ```
515
+
516
+ ```
517
+ // ❌ DON'T: компоненты сами создают зависимости
518
+ class UserService
519
+ repo = new PostgresUserRepository(new PostgresConnection("hardcoded"))
520
+ email = new SmtpEmailService("smtp://hardcoded")
521
+ // Нельзя протестировать без реальных PostgreSQL и SMTP
522
+ ```
523
+
524
+ ### Event-Driven — реакция на события
525
+ ```
526
+ // ✅ DO: слабая связанность через события
527
+ // Сервис создаёт заказ и публикует событие
528
+ class OrderService
529
+ createOrder(data)
530
+ order = Order.create(data)
531
+ orderRepo.save(order)
532
+ eventBus.publish("order.created", { orderId: order.id })
533
+
534
+ // Независимые обработчики подписаны на событие
535
+ class InventoryHandler
536
+ on("order.created") → reserveItems(event.orderId)
537
+
538
+ class NotificationHandler
539
+ on("order.created") → sendConfirmation(event.orderId)
540
+
541
+ class AnalyticsHandler
542
+ on("order.created") → trackConversion(event.orderId)
543
+
544
+ // Добавляем новый обработчик → ни одна строка в OrderService не меняется
545
+ ```
546
+
547
+ ```
548
+ // ❌ DON'T: OrderService знает обо всех зависимых компонентах
549
+ class OrderService
550
+ createOrder(data)
551
+ order = Order.create(data)
552
+ orderRepo.save(order)
553
+ inventoryService.reserve(order) // жёсткая связь
554
+ notificationService.send(order) // жёсткая связь
555
+ analyticsService.track(order) // жёсткая связь
556
+ ```
557
+
558
+ ### CQRS — Command Query Responsibility Segregation
559
+ ```
560
+ // ✅ DO: разные модели для записи и чтения
561
+ // Command side — нормализованная модель; бизнес-правила
562
+ class CreateOrderCommand
563
+ execute(data)
564
+ order = Order.create(data)
565
+ orderRepo.save(order)
566
+ eventBus.publish("order.created", order)
567
+
568
+ // Query side — денормализованная модель; оптимизирована под чтение
569
+ class OrderQueryService
570
+ getOrderSummary(id) →
571
+ readDb.query("SELECT ... FROM order_summaries WHERE id = $1", [id])
572
+
573
+ getOrdersByUser(userId, page) →
574
+ readDb.query("SELECT ... FROM user_orders_view WHERE user_id = $1 LIMIT ...", [userId])
575
+
576
+ // Записи обновляют read-модель через events
577
+ on("order.created") → updateOrderSummaryView(event)
578
+ ```
579
+
580
+ **Когда:** паттерны чтения и записи сильно различаются; высокая нагрузка на чтение; нужна денормализация для быстрых запросов.
581
+
582
+ ---
583
+
584
+ ## 5. Microservices Patterns
585
+
586
+ ### Saga — распределённые транзакции через компенсации
587
+ ```
588
+ // ✅ DO: каждый шаг имеет компенсацию (откат)
589
+ class OrderSaga
590
+ steps = [
591
+ { action: reserveInventory, compensate: releaseInventory },
592
+ { action: chargePayment, compensate: refundPayment },
593
+ { action: createShipment, compensate: cancelShipment },
594
+ { action: sendConfirmation, compensate: sendCancellation },
595
+ ]
596
+
597
+ execute(orderData)
598
+ completed = []
599
+ for step in steps
600
+ try
601
+ step.action(orderData)
602
+ completed.push(step)
603
+ catch error
604
+ // Откатываем все выполненные шаги в обратном порядке
605
+ for s in completed.reverse()
606
+ s.compensate(orderData)
607
+ throw SagaFailedError(error)
608
+ ```
609
+
610
+ **Когда:** транзакция охватывает несколько сервисов; нельзя использовать распределённый 2PC.
611
+
612
+ ### Circuit Breaker — защита от каскадных сбоев
613
+ ```
614
+ // ✅ DO: Circuit Breaker перед вызовом внешнего сервиса
615
+ class CircuitBreaker
616
+ state = CLOSED // нормальная работа
617
+ failureCount = 0
618
+ threshold = 5
619
+ resetTimeout = 30s
620
+
621
+ call(fn)
622
+ if state == OPEN
623
+ if elapsed > resetTimeout → state = HALF_OPEN
624
+ else → throw ServiceUnavailableError // не шлём запрос
625
+
626
+ try
627
+ result = fn()
628
+ if state == HALF_OPEN → state = CLOSED; failureCount = 0
629
+ return result
630
+ catch error
631
+ failureCount++
632
+ if failureCount >= threshold → state = OPEN; startTimer()
633
+ throw error
634
+
635
+ // Использование
636
+ breaker = new CircuitBreaker()
637
+ userData = breaker.call(() => externalUserApi.getUser(id))
638
+ ```
639
+
640
+ **Когда:** зависимость от внешнего сервиса, который может упасть; предотвращение каскадного отказа.
641
+
642
+ ### Mediator — центральный координатор взаимодействий
643
+ ```
644
+ // ✅ DO: компоненты общаются через медиатор, не напрямую
645
+ class OrderMediator
646
+ handlers = Map<string, Handler>
647
+
648
+ register(commandType, handler)
649
+ send(command)
650
+ handler = this.handlers.get(command.type)
651
+ return handler.handle(command)
652
+
653
+ // Регистрация
654
+ mediator.register("CreateOrder", new CreateOrderHandler(orderRepo, inventory))
655
+ mediator.register("CancelOrder", new CancelOrderHandler(orderRepo, payment))
656
+
657
+ // Вызов — контроллер не знает про конкретные обработчики
658
+ class OrderController
659
+ handleCreate(req) → mediator.send(new CreateOrderCommand(req.body))
660
+ handleCancel(req) → mediator.send(new CancelOrderCommand(req.params.id))
661
+ ```
662
+
663
+ **Когда:** много взаимодействующих компонентов; нужно централизовать координацию и убрать прямые зависимости.
664
+
665
+ ---
666
+
667
+ ## 6. Правило выбора
668
+
669
+ | Проблема | Паттерн | Пример |
670
+ |----------|---------|--------|
671
+ | Несколько вариантов алгоритма | Strategy | Алгоритмы сортировки, расчёта скидок |
672
+ | Реакция на изменения | Observer / Event-Driven | Уведомления, аудит, аналитика |
673
+ | Создание объектов по условию | Factory Method | Нотификации: email/sms/push |
674
+ | Интеграция с чужим API | Adapter | Обёртка Stripe/PayPal |
675
+ | Сложная подсистема | Facade | Оформление заказа (inventory + payment + shipping) |
676
+ | Комбинации поведений | Decorator | Кэширование + шифрование + логирование |
677
+ | Undo/redo, очереди | Command | Текстовый редактор, batch-операции |
678
+ | Множество состояний объекта | State | Workflow заказа, задачи |
679
+ | Одинаковый каркас, разные детали | Template Method | Импорт CSV/JSON/XML |
680
+ | Изоляция хранилища | Repository | Работа с БД без SQL в бизнес-логике |
681
+ | Бизнес-логика не в контроллерах | Service Layer | REST API |
682
+ | Слабая связь компонентов | DI | Инъекция зависимостей через конструктор |
683
+ | Разные модели чтения/записи | CQRS | Dashboard + admin panel |
684
+ | Распределённые транзакции | Saga | Заказ: inventory → payment → shipping |
685
+ | Защита от каскадных сбоев | Circuit Breaker | Внешние API |
686
+ | Координация компонентов | Mediator | Request → Handler routing |
687
+
688
+ ---
689
+
690
+ ## См. также
691
+ - `architecture_doc` → где фиксировать выбранные паттерны
692
+ - `architecture_compliance_review` → проверка соответствия архитектуре
693
+ - `adr_log` → фиксация решений по выбору паттернов
694
+ - `code_review_checklist` → проверка реализации паттернов
@@ -7,6 +7,9 @@ description: TanStack (Query v5, Table v8, Virtual v3): кэширование,
7
7
 
8
8
  Копипаст-паттерны для предсказуемой работы с данными, таблицами и виртуализацией.
9
9
 
10
+ > [!IMPORTANT]
11
+ > **MCP-приоритет:** Если доступен MCP-сервер `tanstack-docs`, **всегда используй его инструменты** (`tanstack_doc`, `tanstack_search_docs`, `listTanStackAddOns`, `getAddOnDetails`, `createTanStackApplication`) **первыми** — они дают актуальную документацию прямо из официальных источников TanStack. Используй паттерны этого скилла как fallback или как дополнительный контекст по архитектуре (fetcher/adapter/hook, queryKey factory, оптимистичные обновления, виртуализация), когда MCP недоступен или не содержит нужной информации.
12
+
10
13
  **Разделы:**
11
14
  1. [Query: архитектура fetcher/adapter](#1-query-архитектура)
12
15
  2. [Query: кэширование и staleTime](#2-query-кэш)