Coupling и Cohesion: два понятия, которые стоит понимать каждому разработчику

Последнее время я довольно активно изучаю паттерны проектирования и архитектуру приложений. И чем больше в это погружаюсь, тем чаще встречаю два понятия, которые лежат где-то в основе практически всего — coupling и cohesion.

На первый взгляд они звучат довольно абстрактно, но на самом деле описывают очень практичные вещи: насколько части системы зависят друг от друга и насколько хорошо они подходят друг другу внутри одного модуля или класса.

Coupling — связанность

Coupling показывает, насколько сильно один компонент зависит от другого.

Например, если класс напрямую создаёт и использует конкретную реализацию:

class OrderService
{
    public function pay(Order $order): void
    {
        $gateway = new StripePaymentGateway();
        $gateway->charge($order->total());
    }
}

OrderService жёстко связан со StripePaymentGateway.

Если завтра понадобится PayPal, тестовый mock или другой способ оплаты, придётся менять сам OrderService.

Можно уменьшить связанность, например, используя интерфейс и dependency injection:

class OrderService
{
    public function __construct(
        private PaymentGateway $gateway
    ) {}
 
    public function pay(Order $order): void
    {
        $this->gateway->charge($order->total());
    }
}

Теперь OrderService не знает, какой именно платёжный шлюз используется.

Это уже напрямую связано с SOLID, особенно с Dependency Inversion Principle.

Cohesion — связность внутри компонента

Если coupling отвечает на вопрос:

«Насколько сильно этот компонент зависит от других?»

то cohesion скорее отвечает:

«Насколько хорошо всё внутри этого компонента относится к одной задаче?»

Представим класс:

class UserService
{
    public function register(): void {}
    public function sendEmail(): void {}
    public function generatePdf(): void {}
    public function resizeImage(): void {}
}

Формально всё это может работать. Но у класса очень низкая cohesion — в нём собраны совершенно разные обязанности.

Гораздо лучше разделить их:

UserService
EmailService
PdfGenerator
ImageResizer

Каждый компонент занимается своей задачей.

И здесь мы снова приходим к Single Responsibility Principle из SOLID.

Как это связано с паттернами?

На самом деле многие паттерны проектирования можно рассматривать через призму coupling и cohesion.

Dependency Injection помогает уменьшить coupling.

Strategy позволяет заменить конкретную реализацию и тем самым уменьшить зависимость между компонентами.

Facade скрывает сложность подсистемы и уменьшает количество зависимостей клиента.

Mediator уменьшает количество прямых связей между объектами.

А такие идеи, как Single Responsibility, помогают создавать компоненты с высокой cohesion.

Получается довольно простая картина:

High Cohesion
      +
Low Coupling
      ↓
более гибкая архитектура

Конечно, это не означает, что нужно стремиться вообще избавиться от всех зависимостей. Зависимости между компонентами неизбежны.

Вопрос в другом — насколько они сильные и насколько оправданные.

Зачем вообще это понимать?

Понимание coupling и cohesion помогает смотреть на код немного шире, чем просто:

«Работает ли этот метод?»

Можно начать задавать другие вопросы:

Почему изменение одного класса требует изменений ещё в пяти местах?
Почему этот класс знает слишком много о других компонентах?
Почему в одном классе собрано столько разных задач?
Насколько легко заменить конкретную реализацию?
Насколько просто протестировать этот компонент отдельно?

Именно такие вопросы постепенно переводят мышление от написания отдельных классов к проектированию системы.

Для меня это одна из причин, почему при изучении паттернов и архитектуры важно понимать не только сами паттерны, но и фундаментальные принципы, которые за ними стоят.

Low coupling + high cohesion — пожалуй, одна из самых полезных концепций, которую стоит держать в голове при проектировании PHP-приложений.

Добавить комментарий

CAPTCHA
Защита от спама
Target Image