Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
GRASP: General Responsibility Assignment Software Patterns
GRASP — это набор из 9 паттернов проектирования для назначения ответственности классам и объектам при объектно-ориентированном программировании. Это не конкретные паттерны вроде Singleton или Factory, а скорее принципы, которые помогают принимать решения о дизайне архитектуры.
Автор GRASP — Craig Larman. Паттерны помогают ответить на вопрос: "Какому классу дать ответственность за выполнение этой задачи?"
9 GRASP паттернов
1. Information Expert (Эксперт по информации)
Идея: Отдай ответственность классу, который имеет наибольше информации для выполнения задачи.
// ❌ Плохо: User имеет мало информации о заказах
class OrderProcessor {
calculateTotal(user: User): number {
return user.orders.reduce((sum, order) => sum + order.price, 0);
}
}
// ✅ Хорошо: User "знает" свои заказы
class User {
private orders: Order[];
getTotalOrderAmount(): number {
return this.orders.reduce((sum, order) => sum + order.price, 0);
}
}
2. Creator (Создатель)
Идея: Назначь создание объекта классу, который:
- Содержит или составляет создаваемый объект
- Инициализирует его параметры
- Имеет статическую связь с ним
// ✅ Правильно: Order создаёт LineItem
class Order {
private items: LineItem[] = [];
addItem(product: Product, quantity: number): void {
const item = new LineItem(product, quantity);
this.items.push(item);
}
}
// ❌ Неправильно: неизвестный класс создаёт LineItem
class RandomClass {
createLineItem(): LineItem {
return new LineItem(...);
}
}
3. Controller (Контроллер)
Идея: Назначь обработку системного события классу, который представляет:
- Систему в целом
- Основной use case или сценарий
// ❌ Плохо: компонент React обрабатывает всю логику
function CheckoutButton() {
return (
<button
onClick={() => {
// 50 строк логики валидации, платежа, уведомлений...
}}
>
Оплатить
</button>
);
}
// ✅ Хорошо: отдельный сервис обрабатывает
class OrderService {
async checkout(order: Order): Promise<PaymentResult> {
// логика обработки заказа
}
}
function CheckoutButton({ orderService }: Props) {
const handleClick = async () => {
await orderService.checkout(order);
};
return <button onClick={handleClick}>Оплатить</button>;
}
4. Low Coupling (Слабая связанность)
Идея: Назначь ответственность так, чтобы зависимости между классами были минимальны.
// ❌ Высокая связанность: PaymentService жестко зависит от SpecificBank
class PaymentService {
private bank = new SpecificBank();
pay(amount: number): void {
this.bank.processPayment(amount);
}
}
// ✅ Низкая связанность: через интерфейс
interface PaymentProvider {
processPayment(amount: number): Promise<void>;
}
class PaymentService {
constructor(private provider: PaymentProvider) {}
async pay(amount: number): void {
await this.provider.processPayment(amount);
}
}
// Можешь использовать любого провайдера
const service = new PaymentService(new SpecificBank());
const service2 = new PaymentService(new AnotherBank());
5. High Cohesion (Высокая связность)
Идея: Группируй ответственность так, чтобы методы класса были логически связаны.
// ❌ Низкая связность: User отвечает за слишком много
class User {
name: string;
email: string;
sendEmail(): void { /* ... */ }
generateReport(): void { /* ... */ }
calculateTaxes(): void { /* ... */ }
uploadToCloud(): void { /* ... */ }
}
// ✅ Высокая связность: разделени на классы
class User {
name: string;
email: string;
}
class EmailService {
sendEmail(user: User): void { /* ... */ }
}
class ReportGenerator {
generateReport(user: User): void { /* ... */ }
}
class TaxCalculator {
calculateTaxes(user: User): void { /* ... */ }
}
6. Polymorphism (Полиморфизм)
Идея: Когда поведение зависит от типа, используй полиморфизм вместо условных операторов.
// ❌ С условными операторами
function calculateDiscount(customerType: string, amount: number): number {
if (customerType === "premium") return amount * 0.2;
if (customerType === "regular") return amount * 0.1;
return 0;
}
// ✅ С полиморфизмом
interface Customer {
calculateDiscount(amount: number): number;
}
class PremiumCustomer implements Customer {
calculateDiscount(amount: number): number {
return amount * 0.2;
}
}
class RegularCustomer implements Customer {
calculateDiscount(amount: number): number {
return amount * 0.1;
}
}
class GuestCustomer implements Customer {
calculateDiscount(amount: number): number {
return 0;
}
}
7. Pure Fabrication (Чистая выдумка)
Идея: Создай класс, который не представляет объект из предметной области, но улучшает дизайн, снижая связанность.
// Классы User и Order не должны сами сохраняться в БД
// ✅ Вспомогательный класс Repository (чистая выдумка)
class UserRepository {
save(user: User): Promise<void> {
// логика сохранения в БД
}
findById(id: string): Promise<User> {
// логика поиска в БД
}
}
// Теперь User не связан напрямую с БД
class User {
name: string;
email: string;
}
8. Indirection (Косвенность)
Идея: Вставь промежуточный класс между другими классами, чтобы избежать прямой связанности.
// ❌ Прямая зависимость
class UI {
constructor(private database: Database) {}
loadUser(id: string) {
return this.database.query("SELECT * FROM users WHERE id = ?", id);
}
}
// ✅ Через промежуточный сервис (косвенность)
class UserService {
constructor(private db: Database) {}
getUser(id: string): Promise<User> {
return this.db.query("SELECT * FROM users WHERE id = ?", id);
}
}
class UI {
constructor(private userService: UserService) {}
loadUser(id: string) {
return this.userService.getUser(id);
}
}
9. Protected Variations (Защита от изменений)
Идея: Определи точки возможного изменения и создай стабильный интерфейс вокруг них.
// ❌ Компонент зависит от конкретного API
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch("/api/users") // прямой вызов
.then(r => r.json())
.then(setUsers);
}, []);
}
// ✅ Через абстрактный сервис
const useUsers = (userService: UserService) => {
const [users, setUsers] = useState([]);
useEffect(() => {
userService.getAll().then(setUsers);
}, [userService]);
return users;
};
function UserList({ userService }: Props) {
const users = useUsers(userService);
// Теперь если API изменится, меняется только UserService
}
Как GRASP применяется во фронтенде
В React:
- Information Expert — компонент отвечает за свои состояния
- Controller — кастомные хуки или сервисы обрабатывают логику
- Low Coupling — разделение на контейнеры и presentational компоненты
- High Cohesion — каждый компонент делает одно
- Protected Variations — API сервисы абстрагируют изменения
Практический пример:
// api.ts (Protected Variations)
class UserAPI {
async getUsers(): Promise<User[]> {
const response = await fetch("/api/users");
return response.json();
}
}
// useUsers.ts (Controller)
function useUsers(api: UserAPI) {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
api.getUsers().then(setUsers);
}, [api]);
return users; // Information Expert
}
// UserList.tsx (Low Coupling, High Cohesion)
function UserList({ api }: Props) {
const users = useUsers(api); // Зависит только от API
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Основной вывод
GRASP — это не о конкретных решениях, а о принципах назначения ответственности. Когда ты проектируешь код, спросись:
- Какой класс имеет информацию?
- Как минимизировать связанность?
- Как сохранить возможность изменений?
Эти вопросы помогут создать архитектуру, которая легко тестируется, поддерживается и расширяется.