И снова зравсвтуйте. Меня зовут Владислав Родин. В настоящее время я являюсь руководителем курса «Архитектор высоких нагрузок» в OTUS, а также преподаю на курсах, посвященных архитектуре ПО.
Специально к старту нового набора на курс «Архитектура и шаблоны проектирования» я продолжаю серию своих публикаций про шаблоны GRASP.
Описанные в книге Craig'а Larman'а «Applying UML and patterns, 3rd edition», GRASP'овские паттерны являются обобщением GoF'овских паттернов, а также непосредственным следствием принципов ООП. Они дополняют недостающую ступеньку в логической лестнице, которая позволяет получить GoF'овские паттерны из принципов ООП. Шаблоны GRASP являются скорее не паттернами проектирования (как GoF'овские), а фундаментальными принципами распределения ответственности между классами. Они, как показывает практика, не обладают особой популярностью, однако анализ спроектированных классов с использованием полного набора GRASP'овских паттернов является необходимым условием написания хорошего кода.
Полный список шаблонов GRASP состоит из 9 элементов:
В прошлый раз мы обсудили принцип Creator. Сейчас я предлагаю рассмотреть два принципа GRASP, которые имеет смысл рассматривать только в паре, потому что рассмотрение их по отдельности в пределе приводит к явно плохому коду. Эти принципы могут рассматриваться не только в контексте микропроектирования, но и при проектировании, например, микросервисов. Ниже поговорим о Low Coupling и High Cohesion.
Необходимо распределить ответственности между классами так, чтобы обеспечить минимальную связанность.
Самым ярким примером нарушения этого принципа, с моей точки зрения, является циклическая зависимость (да-да, то, за что обычно отрывают руки на code review):
На UML — диаграмме классов для такой системы можно будет увидеть как зависимость класса A на класс B, так и зависимость класса B на класс A. Почему это плохо? Дело в том, что мы не можем отдать класс A без класса B, также как и класс B без класса A: их нельзя переиспользовать по — отдельности, только вместе. Чем меньше связей между классами — тем лучше, вот о чем говорит нам принцип Low Coupling. Если вспомнить предыдущие разобранные нами принципы: Information Expert и Creator, то можно вспомнить, что соблюдение этих принципов приводит к уменьшению количества ненужных связей между классами.
Если возвести Low Coupling в абсолют, то достаточно быстро можно прийти к тому, чтобы разместить всю функциональность в одном единственном классе. В таком случае связей не будет вообще, но все при этом понимают, что что-то тут явно не так, ведь в этот класс попадет совершенно несвязанная между собой бизнес — логика. Принцип High Cohesion говорит нам следующее: классы должны содержать связанную бизнес — логику.
Давайте рассмотрим класс, представляющий из себя данные с какого — либо счетчика:
Представленный класс содержит бизнес — логику по работе как с температурой, так и со временем. Они не имеют ничего общего, за исключением того, что собираются с одного датчика. Если мы захотим переиспользовать бизнес — логику, связанную по работе только с температурой, то осуществить это легко не получится. Если проводить параллели с принципами SOLID, то класс нарушает SRP: через него проходят две оси изменения.
Кстати говоря, наличие префиксов в названиях часто говорит о том, что принцип High Cohesion нарушается: программист, пишущий этот код, сам понимал, что он работает с двумя классами, с двумя разными контекстами. Чтобы не запутаться, было принято решение о добавлении префиксов.
Имеет смысл создать 2 класса: один для температуры, другой для времени:
Таким образом, бизнес-логика в каждом из классов является «сильно зацепленной», эти классы легко переиспользовать, образуя любые комбинации.
Low Coupling и High Cohesion представляют из себя два связанных между собой паттерна, рассматривать которые имеет смысл только вместе. Их суть можно объединить следующим образом: система должна состоять и слабо связанных классов, которые содержать связанную бизнес — логику. Соблюдение этих принципов позволяет удобно переиспользовать созданные классы, не теряя понимания об их зоне ответственности.
Фабричный метод и абстрактная фабрика
Специально к старту нового набора на курс «Архитектура и шаблоны проектирования» я продолжаю серию своих публикаций про шаблоны 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 представляют из себя два связанных между собой паттерна, рассматривать которые имеет смысл только вместе. Их суть можно объединить следующим образом: система должна состоять и слабо связанных классов, которые содержать связанную бизнес — логику. Соблюдение этих принципов позволяет удобно переиспользовать созданные классы, не теряя понимания об их зоне ответственности.
Фабричный метод и абстрактная фабрика