Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке патерни grasp». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**GRASP-патерни** (General Responsibility Assignment Software Patterns) - дев'ять принципів розподілу відповідальностей між класами в ООП. Центральне правило: призначай задачу класу, який має потрібні дані (Information Expert). Не перевіряється компілятором, застосовується під час проектування. **Головне:** GRASP відповідає на питання хто за що відповідає, а SOLID і GoF - як влаштовані класи і які патерни обрати.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**GRASP** (General Responsibility Assignment Software Patterns) - набір дев'яти принципів для визначення того, який клас повинен відповідати за яку частину логіки в об'єктно-орієнтованому дизайні. ## Теорія ### TL;DR - Аналогія: GRASP схожий на розподіл ролей на кухні. Шеф-кухар (Information Expert) готує страви, бо знає рецепти, а не офіціант. - Центральна ідея: призначай задачу класу, який має потрібні дані. - Головна різниця від SOLID: GRASP відповідає на питання *хто що робить*, SOLID відповідає *як влаштовані класи*. - Три принципи дають 80% цінності на практиці: Information Expert, Low Coupling, High Cohesion. - Не підходить для процедурного або функціонального коду без ієрархії класів. ### Швидкий приклад ```java // Погано: Controller знає забагато про бізнес-логіку Order class OrderController { void process(Order order) { if (order.getTotal() > 100) order.applyDiscount(0.1); // Не той власник } } // Добре: Information Expert - Order відповідає за свою знижку class Order { private double total; void applyDiscountIfEligible() { if (total > 100) total *= 0.9; // Order має дані, тому він відповідає за правило } } ``` Контролер лише координує. Правило знижки живе там, де живуть дані. ### Що таке GRASP насправді GRASP не є фреймворком або бібліотекою. Жоден компілятор не перевіряє його. Це словник проектування: набір іменованих принципів, які застосовують на дошці, в UML-діаграмах або при ескізуванні структури класів до написання коду. Користь з'являється пізніше: менше змін по ланцюжку, коли вимоги оновлюються. Craig Larman описав GRASP у книзі "Applying UML and Patterns", щоб зробити розподіл відповідальностей явним. До цього розробники приймали такі рішення інтуїтивно, без спільної мови для обґрунтування. ### GRASP vs SOLID vs GoF Три речі, які найчастіше плутають на співбесідах. GRASP відповідає: який клас повинен відповідати за цю задачу? SOLID відповідає: як повинні бути структуровані окремі класи? GoF відповідає: яким патерном вирішити цю типову структурну проблему? GRASP допомагає обрати або придумати патерн. GoF передбачає, що ти вже знаєш, коли його застосовувати. | | GRASP | SOLID | GoF | |---|---|---|---| | Фокус | Розподіл відповідальностей | Правила структури класів | Готові рішення | | Етап | Початкове моделювання | Постійне проектування | Вибір патерну | | Результат | "Цей клас відповідає за те" | Краща структура класів | Конкретний патерн | | Автор | Craig Larman | Кілька (Martin та ін.) | Gang of Four | ### Дев'ять принципів **Information Expert.** Призначай відповідальність класу, який має дані для її виконання. `Order` перевіряє товари, бо він їх містить. Не `Controller`, не утилітний клас. **Creator.** Клас B повинен створювати екземпляри класу A, якщо B містить, агрегує, записує або тісно використовує A. `PaymentGateway` створює об'єкти `Charge`, бо управляє платіжним контекстом. **Controller.** Зовнішні події (кліки UI, API-запити) проходять через спеціальний клас контролера. Контролер делегує доменним об'єктам і не виконує доменну логіку самостійно. **Low Coupling.** Мінімізуй залежності між класами. Чим менше класів ламається при зміні одного, тим краще. Передавай лише те, що метод реально використовує. **High Cohesion.** Кожен клас робить одне або споріднений набір речей. Клас, який відповідає за авторизацію, логування і розрахунок знижок одночасно, є проблемою згуртованості. **Polymorphism.** Варіативну поведінку залежно від типу реалізовуй через поліморфізм, а не if/else. Коли поведінка змінюється за типом у runtime, інтерфейси та підкласи впораються краще. **Pure Fabrication.** Іноді жоден доменний клас не підходить для певної відповідальності. Тоді створюється штучний клас, який не представляє доменну концепцію, але існує для підтримки Low Coupling і High Cohesion. Класичний приклад - `AuditLogger`. **Indirection.** Додай посередника між двома класами, які не повинні залежати один від одного напряму. Message brokers, фасади, адаптери - все це Indirection на практиці. **Protected Variations.** Обгорни все, що може змінюватись, за стабільним інтерфейсом. Код, який викликає інтерфейс, залишається незмінним навіть коли реалізація за ним змінюється. ### Коли застосовувати GRASP - Новий доменний модуль з нуля: застосуй усі дев'ять, щоб розподілити ролі класів до написання коду. - Рефакторинг "бога класу" (god class): High Cohesion і Low Coupling покажуть, де ділити. - Командне проектування: назвати принцип означає аргументувати рішення конкретно, а не суб'єктивно. - Межі мікросервісів: Information Expert підказує, який сервіс відповідає за які дані. Не підходить для процедурних скриптів або функціональних пайплайнів без ієрархії класів. ### Типові помилки **1. Controller як звалище бізнес-логіки.** GRASP-контролер є тонким координатором. Коли він починає накопичувати доменні правила, це порушення Information Expert. Більшість кодових баз починають саме так, бо контролер є очевидною точкою входу. Рішення: делегуй якомога раніше. ```java // Погано: контролер виконує доменну логіку class OrderController { void process(Order order) { // 80 рядків логіки знижок, податків і доставки } } // Добре: контролер делегує class OrderController { void process(Order order) { orderService.applyEligibleDiscounts(order); shippingService.calculateCost(order); } } ``` **2. Передавати цілий об'єкт заради одного поля.** Якщо методу потрібне лише одне поле, передача всього об'єкта створює зайву залежність. Додаєш поле до `Order` - і всі методи, що приймають його, стають потенційними жертвами змін. ```java // Погано: приймає весь Order, використовує тільки total void printReceipt(Order order) { System.out.println(order.getTotal()); } // Краще: передай те, що потрібно void printReceipt(double total) { System.out.println(total); } ``` **3. Поліморфізм для простих статичних випадків.** Поліморфізм виправданий, коли поведінка змінюється у runtime. Для двох фіксованих випадків він зайвий. Створювати підкласи `Discount10Percent` і `Discount20Percent` замість if/else - передчасна абстракція. Два-три випадки: if/else. Варіація у runtime: поліморфізм. **4. Розкидані виклики `new` замість Creator.** Коли створення об'єктів розкидане по різних класах, відстежити залежності і тестувати ізольовано стає болісно. Creator дає чітке правило: клас, який контекстуально "управляє" об'єктом, повинен його створювати. ### Де зустрічається - Spring Framework: `@Service`-класи є GRASP-контролерами; `@Entity`-класи є Information Experts. - React/Redux: редьюсери дотримуються High Cohesion (тільки чиста логіка стану); action creators дотримуються Creator. - Java EE: `EntityManager` є Creator для JPA-сутностей. - Stripe API: `PaymentGateway` створює об'єкти `Charge`, а не клієнтський код. - Мікросервіси: Information Expert підказує, який сервіс відповідає за який набір даних. ### Питання на співбесіді **Q:** Поясни Information Expert на поганому і хорошому прикладі. **A:** Погано: UI-компонент розраховує суму замовлення, бо має її відобразити. Він тепер залежить від кожного поля в розрахунку. Добре: `Order.calculateTotal()` відповідає за логіку, бо має дані. UI просто викликає метод. **Q:** Як Low Coupling відрізняється від Dependency Inversion? **A:** Low Coupling є метою: мінімізувати залежності загалом. Dependency Inversion є технікою: залежати від абстракцій, а не від конкретних класів. DIP є одним зі способів досягти Low Coupling, але не єдиним. **Q:** Коли використовувати Pure Fabrication замість методу в доменному класі? **A:** Коли відповідальність не належить жодній доменній концепції. Логування, аудит, форматування для експорту - це не доменна поведінка. Додавати їх у доменний клас означає ламати його згуртованість. **Q:** Спроектуй аукціонну систему з трьома принципами GRASP (рівень senior). **A:** `Bid` як Information Expert (знає свою валідність за мінімальною ціною і лімітами ставок). `Auctioneer` як Controller (отримує події ставок, делегує `BidValidator`). Low Coupling через інтерфейс `BidValidator`, щоб `Auctioneer` не залежав від конкретної логіки валідації. **Q:** High Cohesion vs Single Responsibility Principle: це одне й те саме? **A:** Пов'язані, але різні. High Cohesion групує споріднені поведінки в одному класі. SRP каже, що клас повинен мати лише одну причину для змін. Клас може бути згуртованим, але порушувати SRP: відповідати і за авторизацію, і за відображення профілю. Обидва пов'язані з "користувачем", але змінюються з різних причин. **Q:** Як принципи GRASP відображаються на функціональне програмування? **A:** Information Expert відображається на чисті функції, що отримують потрібні дані як аргументи. Low Coupling відображається на композицію функцій з мінімальним спільним станом. Назви різні, ідеї перетинаються. ## Приклади ### Information Expert: Order перевіряє себе сам ```java // Погано: Client дублює внутрішню логіку Order class Client { void validateOrder(Order order) { if (order.getItems().isEmpty()) throw new IllegalStateException("Порожнє замовлення"); for (Item item : order.getItems()) { if (!item.isInStock()) throw new IllegalStateException("Немає в наявності"); } } } // Добре: Order має список товарів, тому перевіряє себе сам class Order { private List<Item> items; boolean isValid() { return !items.isEmpty() && items.stream().allMatch(Item::isInStock); } } ``` `Client` змінюється рідше. Логіка перевірки наявності живе в одному місці. Коли правило валідації змінюється, торкаєшся одного класу. ### Creator: PaymentGateway створює об'єкти Charge ```javascript // Натхнено моделлю ресурсів Stripe class PaymentGateway { createCharge(amount, customer) { // Gateway управляє Charge-об'єктами - він природний Creator return new Charge(amount, customer.id); } } class Charge { constructor(amount, customerId) { this.amount = amount; this.customerId = customerId; } } const gateway = new PaymentGateway(); const charge = gateway.createCharge(100, { id: 'cus_123' }); // Виводить: Charge { amount: 100, customerId: 'cus_123' } ``` Викликаючий код ніколи не пише `new Charge(...)`. Все створення проходить через об'єкт, який контекстуально управляє зарядами. Замінити реалізацію пізніше стає простіше. ### Pure Fabrication: AuditLogger тримає компоненти чистими ```jsx // Погано: side effect логування в UI-компоненті function UserProfile({ user }) { console.log('Audit:', user.id); // UI-компонент не повинен про це знати return <div>{user.name}</div>; } // Добре: Pure Fabrication - AuditLogger не є доменною концепцією, // він існує, щоб кожен клас залишався сфокусованим class AuditLogger { static logUserView(userId) { console.log('Audit:', userId); } } function UserProfile({ user }) { AuditLogger.logUserView(user.id); return <div>{user.name}</div>; } // Виводить: 'Audit: 123' ``` `AuditLogger` не представляє нічого в бізнес-домені. Він існує, бо відповідальність за логування мала десь жити - не в UI-компоненті і не в доменному класі.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.