Что такое GRASP и как применять эти принципы в Node.js разработке?

Ответ

GRASP (General Responsibility Assignment Software Patterns) — это набор принципов для распределения ответственностей в объектно-ориентированном дизайне. В контексте Node.js с его модульной архитектурой, эти принципы помогают создавать поддерживаемый код даже без классических классов.

Применение GRASP в Node.js проектах:

  1. Информационный эксперт:

    // Сервисный класс, владеющий логикой работы с заказами
    class OrderService {
    constructor(orderRepository) {
    this.orderRepository = orderRepository;
    }
    
    // Этот класс знает как вычислять общую стоимость
    async calculateTotal(orderId) {
    const order = await this.orderRepository.findById(orderId);
    return order.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    }
    }
  2. Создатель:

    // Фабрика создает сложные объекты
    class LoggerFactory {
    static create(config) {
    const transports = [
      new transports.File({ filename: config.filePath }),
      new transports.Console()
    ];
    return new winston.createLogger({ transports });
    }
    }
  3. Контроллер:

    // Express контроллер обрабатывает HTTP запросы
    class UserController {
    async createUser(req, res) {
    try {
      const userData = req.body;
      const user = await this.userService.create(userData);
      res.status(201).json(user);
    } catch (error) {
      res.status(400).json({ error: error.message });
    }
    }
    }
  4. Низкая связанность: Достигаю через Dependency Injection:

    
    // Вместо жесткой зависимости
    const paymentService = new PaymentService();

// Использую DI class OrderProcessor { constructor(paymentService, notificationService) { this.paymentService = paymentService; this.notificationService = notificationService; } }


5. **Высокая связность:** Группирую связанные функции в модулях:
```javascript
// auth-module.js
const validateToken = (token) => { /* ... */ };
const generateToken = (user) => { /* ... */ };
const hashPassword = (password) => { /* ... */ };

module.exports = { validateToken, generateToken, hashPassword };

В моих Node.js проектах эти принципы помогают создавать архитектуру, которую легко тестировать и расширять.

Ответ 18+ 🔞

А, GRASP, говоришь? Ну это, блядь, такая штука, чтобы код не превращался в пиздопроебибну, где всё завязано на всё, и ты потом сам от себя охуеешь, когда надо одну кнопку поменять, а у тебя полпроекта накрывается медным тазом.

Короче, это типа свод правил, кто за что в твоей программе должен отвечать. В Node.js, где классов в чистом виде может и не быть, они всё равно пригодятся, чтобы не наделать архитектурного говна.

Как это в Node.js применить:

  1. Информационный эксперт (тот, кто всё знает). Отдавай задачу тому модулю или сервису, у которого уже есть все нужные данные. Не тащи данные через всю вселенную, чтобы посчитать хуй с горы.

    // Сервис заказов — он и есть главный эксперт по заказам, ёпта.
    class OrderService {
      constructor(orderRepository) {
        this.orderRepository = orderRepository; // Он знает, где данные лежат
      }
    
      // Он же знает, как посчитать итог. Не заставляй контроллер этим заниматься!
      async calculateTotal(orderId) {
        const order = await this.orderRepository.findById(orderId);
        // Вот он, на месте всё делает. Красота.
        return order.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
      }
    }
  2. Создатель. Если объект сложный, как мартышлюшка с гранатой, не собирай его вручную в десяти местах. Сделай фабрику, которая этим будет заниматься. Доверия к ручному сбору — ноль ебать.

    class LoggerFactory {
      static create(config) {
        // Вот эта фабрика — она профессиональный создатель логгеров.
        const transports = [
          new transports.File({ filename: config.filePath }),
          new transports.Console()
        ];
        return new winston.createLogger({ transports }); // И только она знает, как его правильно склепать.
      }
    }
    // А потом везде просто LoggerFactory.create(config). И не еби мозг.
  3. Контроллер. Это как диспетчер на проходной. Его работа — принять входящий запрос (HTTP, сообщение из очереди), разобрать, позвать нужных экспертов (сервисы), и отправить ответ. Он не должен сам бизнес-логику выполнять, а то получится хитрая жопа, которая всё умеет и ничего не делает нормально.

    class UserController {
      async createUser(req, res) {
        try {
          const userData = req.body; // Принял данные
          const user = await this.userService.create(userData); // Передал эксперту (UserService)
          res.status(201).json(user); // Отправил ответ
        } catch (error) {
          res.status(400).json({ error: error.message }); // Обработал косяк
        }
      }
    }
    // Видишь? Он не лезет в базу и не хэширует пароли. Он координирует. И всё.
  4. Низкая связанность. Это святое, ёб твою мать. Твои модули должны знать друг о друге по минимуму. Достигается это через внедрение зависимостей (DI). Не пиши const payment = new PaymentService() внутри класса. Лучше попроси передать его снаружи.

    // Плохо: жёсткая привязка. Заменишь PaymentService — придёшь сюда.
    // const paymentService = new PaymentService();
    
    // Хорошо: зависимость принесли снаружи. Хоть манда с ушами, лишь бы интерфейс подходил.
    class OrderProcessor {
      constructor(paymentService, notificationService) {
        this.paymentService = paymentService; // Мне похуй, что там внутри, главное — плати.
        this.notificationService = notificationService; // И уведомляй.
      }
    }
    // Теперь OrderProcessor не привязан к конкретным реализациям. Гибко, тестируемо. Красота.
  5. Высокая связность. А это про то, что всё родственное должно лежать в одном месте. Не размазывай функции для работы с аутентификацией по двадцати файлам. Собери их в одном модуле, чтобы не было волнения ебать, когда ищешь, где же hashPassword спрятался.

    // auth-module.js — здесь живёт ВСЁ про аутентификацию.
    const validateToken = (token) => { /* ... */ };
    const generateToken = (user) => { /* ... */ };
    const hashPassword = (password) => { /* ... */ };
    
    module.exports = { validateToken, generateToken, hashPassword };
    // Идеально. Открыл один файл — и там всё, что нужно. Связность высокая, как у хорошего коллектива.

Если в своих проектах придерживаться этой простой, блядь, идеи — кто за что отвечает — то код живёт долго и счастливо. Его легче тестировать (подсовываешь заглушки), легче менять (меняешь один эксперт, а не двадцать мест) и легче понимать новым людям. А иначе — пиши пропало, через полгода будешь смотреть на свой же код и думать: «Какого хуя? Кто это писал?».