Сплоченность В Корпоративных Приложениях



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

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

Например, если мы используем DAO (Data Access Object) для доступа к базе данных, то наряду с созданием новой структуры в базе данных нам потребуется создать новую DAO или расширить существующую, но никак не писать SQL, скажем, на уровне презентации.

Чтобы было еще понятнее, добавлю, что речь пойдет о том же, о чем писал «классик» — Паттерны архитектуры корпоративных приложений М.

Фаулера.

В книге вообще говорится о том, как разделить функциональность, т. е.

какой метод какому классу должен принадлежать.



Проблема

Начнем с примера.

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

Система должна позволять создавать запрос, состоящий из контактной информации и информации о месте работы; отправить заявку на оформление в визовый центр; принять решение о выдаче или отказе в выдаче визы; сформировать некий отчет о человеке, подающем документы; доставьте свой паспорт на летающих обезьянах.

Кроме того, запрос может быть оформлен на несколько человек.

В упрощенном виде структура запроса может выглядеть так:

  
  
  
  
  
  
  
   

class VisaRequest{ Collection<Applicant> applicants } class Applicant{ Contact contact WokrInfo workInfo } …

Не забывайте, что язык программирования тоже волшебный (т. е.

он не может компилироваться без магии), хотя и похож на groovy. Сам функционал в этом случае может выглядеть примерно так:

class VisaRequestService { def create(def visaRequest){.

} def update(def visaRequest){.

} def get(def requestId){} def submit(def requestId){.

} def getDecision(def requestId, def applicantName){.

} def generateReport(def requestId, def applicantName){.

} }

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



Сплоченность в корпоративных приложениях

Что по этому поводу пишет Мартин Фаулер? Я не буду пересказывать книгу, скажу лишь, что, наряду со всем остальным, он обращает внимание на то, что с добавлением функциональности такой класс, как VisaRequestService, будет разрастаться, и повторно использовать написанные методы будет все сложнее, так как каждый метод на самом деле является сценарием, специфичным для конкретного сценария.

Не будем с ним спорить, а обратим внимание на понятие из названия статьи - сплоченность.

Сплоченность — это свойство объекта/класса, которое определяет, насколько объект/класс занят своей работой.

Если связность низкая, то у класса слишком много обязанностей, класс выполняет слишком много разных операций и становится большим, а большой класс трудно читать, трудно расширять и т. д. В нашем тривиальном случае мы фактически создали класс Бога, который за всё отвечает. Конечно, это не единственное решение, и мы могли бы создать отдельный DecisionService:

class DecisionService{ def getDecision(def requestId, def applicantName){.

} }

или отдельный SubmitService:

class SubmitService{ def submit(def requestId){.

} }

или отдельный ApplicantService:

class ApplicantService{ def getDecision(def requestId, def applicantName){.

} def generateReport(def requestId, def applicantName){.

} }

или что-то еще (все-таки страна Ос).

Во всех этих примерах сплоченность новых классов кажется достаточно хорошей, но не во всех из них мы улучшаем сплоченность существующих.

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

Кроме того, остаются неясными причины, по которым мы разделили класс таким образом.



Решение



Классический

Фаулер решает эту проблему, полностью возложив ее на плечи ООП.

Он вводит концепцию Rich Data Model, которая представляет собой не что иное, как честное сочетание данных и методов в одном классе, что является одной из основ ООП.

Богатая модель данных для нашей системы будет выглядеть так:

class VisaRequest{ Collection<Applicant> applicants def create(){.

} def update(){.

} def get(){} def submit(){.

} } class Applicant{ Contact contact WokrInfo workInfo def getDecision(){.

} def generateReport(){.

} def create(){.

} def update(){.

} def get(){} }

Разделение функциональности следует очень простому правилу: класс содержит только методы, относящиеся к той сущности, которую этот класс отражает. Конечно, это определение не является строгим, как и само определение сплоченности.

Rich Data Model удобна для ряда систем, например в случае сложных вычислений, но небольшого количества операций на сущность и слабого взаимодействия с внешней средой, но не всегда.

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

Первая из них — это модель с полным состоянием, т. е.

объект имеет некоторое состояние.

До этого состояние имели только структуры данных, это не представляло никакой проблемы, но теперь состояние имеют и объекты, несущие функциональность системы.

На практике это приводит к усложнению процесса создания таких объектов.

Фактически, это делает довольно проблематичным использование популярных контейнеров DI (Dependency Injection), таких как Spring. Кроме того, если говорить о J2EE или WEB, никто не отменял необходимость создания фасадов (действий в весне), что приводит к появлению нового слоя функциональности, непонятно как структурировать.

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



Не классический

Попробуем решить проблему немного по-другому, используя те же сервисы.

Почему они? Хотя бы потому, что сервисы очень популярны среди программистов.

Возникает вопрос, как правильно ими пользоваться, избегая описанных выше острых углов и препятствий.

На мой взгляд, удобнее всего разбить его по двум координатам — структуре (элементу) данных и функционалу, который должен быть отражен в названии сервиса.



Сплоченность в корпоративных приложениях

Следуя этому правилу, мы получим для нашей системы следующие сервисы:

class VisaRequestCRUDService { def create(def visaRequest){.

} def update(def visaRequest){.

} def get(def requestId){.

} def getDecision(def requestId, def applicantName){.

} } class VisaRequestActionService { def submit(def requestId){.

} } class ApplicantCRUDService{ def create(def Applicant){.

} def update(def Applicant){.

} def get(def applicantId){.

} def create(def Applicant){.

} } class ApplicantInfoService{ def getDecision(def requestId, def applicantName){.

} def generateReport(def requestId, def applicantName){.

} }

Диаграмма классов будет выглядеть примерно так:

Сплоченность в корпоративных приложениях

Преимущества такого подхода вполне очевидны.

Во-первых, у нас есть почти все преимущества, которые мы имеем при использовании Rich Data Model, только вместо одного класса у нас будет два, что снижает связность, что хорошо.

Во-вторых, мы явно указываем механизм (правило) для дальнейшего улучшения связности, что минимизирует загрязнение системы.

Есть еще один, не совсем очевидный плюс.

Модель данных не всегда совпадает с моделью предметной области, но операции всегда принадлежат модели предметной области.

Приведенное выше решение позволяет отразить различия между этими двумя моделями в функциональной координате.

Например, давайте подумаем о летающих обезьянах.

Судя по требованиям, они не имеют никаких характеристик, т.е.

нам не нужно хранить о них какую-либо информацию и, соответственно, им нечего делать в модели данных.

С другой стороны, они находятся в модели предметной области, которую мы можем отразить в сервисе, зависящем от класса VisaRequest:

class FlyApesRequestDeliveryService{ def deliver(def Request){.

} }

Конечно, описанный подход имеет и недостатки.

Та же гибкость разбиения по функциональной координате порождает неочевидность этого разбиения.

Разработчик должен иметь четкое представление о типе системы и наборе выполняемых операций (хотя бы на уровне бизнес-требований), чтобы грамотно использовать данную конструкцию.



Вместо заключения

Думаю, с сплоченностью все более-менее понятно, а как насчет предпринимательства? Корпоративные приложения интересны тем, что над ними, как правило, работают несколько команд, возможно, разделенных географически.

Много команд – много людей, много людей – много вопросов.

Где и как реализовать новый функционал? Есть ли что-то подобное в системе? Новый разработчик – новый подход. Все это порождает множество проблем, однако самой сложной с технической точки зрения является грязный код (а плохая структура классов — один из самых ярких примеров грязного кода).

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

Я уверен, что не все со мной согласятся, но я бы сказал, что сплоченность является поэтому одним из главных показателей здоровья системы.

Возможно, нам следует закончить здесь.

Надеюсь, статья будет кому-то полезна, но если описанные выше проблемы вам не знакомы, вы знаете, что такое эволюционный дизайн и он работает в вашей команде – забудьте все, что здесь написано, вы уже в стране Ос.

.

Теги: #java #архитектура #архитектура приложений #дизайн #сплоченность #шаблоны #шаблоны #enterprice #программирование #java #проектирование и рефакторинг

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.