← Назад к вопросам

Что такое GRASP?

2.0 Middle🔥 41 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 — это не о конкретных решениях, а о принципах назначения ответственности. Когда ты проектируешь код, спросись:

  • Какой класс имеет информацию?
  • Как минимизировать связанность?
  • Как сохранить возможность изменений?

Эти вопросы помогут создать архитектуру, которая легко тестируется, поддерживается и расширяется.