Шаблоны GRASP: Low Coupling (низкая связанность) и High Cohesion (высокое зацепление)

от автора

И снова зравстуйте. Меня зовут Владислав Родин. В настоящее время я являюсь руководителем курса «Архитектор высоких нагрузок» в OTUS, а также преподаю на курсах, посвященных архитектуре ПО.

Специально к старту нового набора на курс «Архитектура и шаблоны проектирования» я продолжаю серию своих публикаций про шаблоны GRASP.


Введение

Описанные в книге Craig’а Larman’а «Applying UML and patterns, 3rd edition», GRASP’овские паттерны являются обобщением GoF’овских паттернов, а также непосредственным следствием принципов ООП. Они дополняют недостающую ступеньку в логической лестнице, которая позволяет получить GoF’овские паттерны из принципов ООП. Шаблоны GRASP являются скорее не паттернами проектирования (как GoF’овские), а фундаментальными принципами распределения ответственности между классами. Они, как показывает практика, не обладают особой популярностью, однако анализ спроектированных классов с использованием полного набора GRASP’овских паттернов является необходимым условием написания хорошего кода.

Полный список шаблонов GRASP состоит из 9 элементов:

  • Information Expert
  • Creator
  • Controller
  • Low Coupling
  • High Cohesion
  • Polymorphism
  • Pure Fabrication
  • Indirection
  • Protected Variations

В прошлый раз мы обсудили принцип Creator. Сейчас я предлагаю рассмотреть два принципа GRASP, которые имеет смысл рассматривать только в паре, потому что рассмотрение их по отдельности в пределе приводит к явно плохому коду. Эти принципы могут рассматриваться не только в контексте микропроектирования, но и при проектировании, например, микросервисов. Ниже поговорим о Low Coupling и High Cohesion.

Low Coupling

Формулировка

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

Пример нарушения

Самым ярким примером нарушения этого принципа, с моей точки зрения, является циклическая зависимость (да-да, то, за что обычно отрывают руки на code review):

public class A {     private int a;     private B b;         public A(int a) {         this.a = a;         this.b = new B(this);     } }  public class B {     private A a;          public B(A a) {         this.a = a;     } } 

На UML — диаграмме классов для такой системы можно будет увидеть как зависимость класса A на класс B, так и зависимость класса B на класс A. Почему это плохо? Дело в том, что мы не можем отдать класс A без класса B, также как и класс B без класса A: их нельзя переиспользовать по — отдельности, только вместе. Чем меньше связей между классами — тем лучше, вот о чем говорит нам принцип Low Coupling. Если вспомнить предыдущие разобранные нами принципы: Information Expert и Creator, то можно вспомнить, что соблюдение этих принципов приводит к уменьшению количества ненужных связей между классами.

High Cohesion

Формулировка

Если возвести Low Coupling в абсолют, то достаточно быстро можно прийти к тому, чтобы разместить всю функциональность в одном единственном классе. В таком случае связей не будет вообще, но все при этом понимают, что что-то тут явно не так, ведь в этот класс попадет совершенно несвязанная между собой бизнес — логика. Принцип High Cohesion говорит нам следующее: классы должны содержать связанную бизнес — логику.

Пример нарушения

Давайте рассмотрим класс, представляющий из себя данные с какого — либо счетчика:

@AllArgsConstructor public class Data {     private int temperature;     private int time;        private int calculateTimeDifference(int time) {       return this.time - time;    }    private int calculateTemperatureDifference(int temperature) {       return this.temperature - temperature;    } } 

Представленный класс содержит бизнес — логику по работе как с температурой, так и со временем. Они не имеют ничего общего, за исключением того, что собираются с одного датчика. Если мы захотим переиспользовать бизнес — логику, связанную по работе только с давлением, то осуществить это легко не получится. Если проводить параллели с принципами SOLID, то класс нарушает SRP: через него проходят две оси изменения.

Кстати говоря, наличие префиксов в названиях часто говорит о том, что принцип High Cohesion нарушается: программист, пишущий этот код, сам понимал, что он работает с двумя классами, с двумя разными контекстами. Чтобы не запутаться, было принято решение о добавлении префиксов.

Пример соблюдения

Имеет смысл создать 2 класса: один для температуры, другой для времени:

@AllArgsConstructor public class Data {     private TemperatureData temperatureData;     private TimeData timeData;        public Data(int time, int temperature) {        this.temperatureData = new TemperatureData(temperature);        this.timeData = new TimeData(time);    }     // тут логика по работе как со временем, так и с температурой  }  @AllArgsConstructor public class TimeData {     private int time;      private int calculateTimeDifference(int time) {       return this.time - time;    } }  @AllArgsConstructor public class TemperatureData {     private int temperature;      private int calculateTemperatureDifference(int temperature) {       return this.temperature - temperature;    } } 

Таким образом, бизнес — логика в каждом из классов является «сильно зацепленной», эти классы легко переиспользовать, образуя любые комбинации.

Вывод

Low Coupling и High Cohesion представляют из себя два связанных между собой паттерна, рассматривать которые имеет смысл только вместе. Их суть можно объединить следующим образом: система должна состоять и слабо связанных классов, которые содержать связанную бизнес — логику. Соблюдение этих принципов позволяет удобно переиспользовать созданные классы, не теряя понимания об их зоне ответственности.


Фабричный метод и абстрактная фабрика


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/505852/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *