Перейти к основному содержимому

Low Coupling & High Cohesion

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

coupling-cohesion-themed

В рамках методологии это достигается через:

Композиция компонентов (UI level)#

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

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

Пример#

Рассмотрим такую композицию на примере списка с хедером:

Закладываем расширяемость#

Компонент списка не будет сам определять вид и структуру компонентов хедера и элементов списка, вместо этого будет принимать их в качестве параметров

interface ListProps {    Header: Component;    Items: Component;}
const List: Component<ListProps> = ({ Header, Items }) => (    <div class="wrapper">        {Header}        <ul class="...">            {Items}        </ul>    </div>)

Используем композицию#

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

<List Header={<FancyHeader />} Items={<ToDoItems />} />
<List Items={<CartItems />} />
<List Header={<FancyHeaderV2 color="red" />} Items={<FancyItems />} />

Композиция слоев (APP level)#

Методология предлагает разделять ценную для пользователя функциональность на отдельные модули - фичи (features), а логику, относящуюся к бизнес сущностям - в сущности (entities). И фичи, и сущности должны проектироваться как высоко-связные модули, т.е. направленные на решение одной конкретной задачи или сконцентрированные вокруг одной конкретной сущности.

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

Пример#

На примере приложения-чата с такими возможностями

  • можно открыть список контактов и выбрать друга
  • можно открыть переписку с выбранным другом

В рамках методологии, это может быть представлено примерно так:

Entities

  • Пользователь (содержит состояние пользователя)
  • Контакт (состояние списка контактов, инструменты для работы с отдельным контактом)
  • Переписка (состояние текущей переписки и работа с ней)

Features

  • Форма отправки сообщения
  • Меню выбора переписки

Свяжем все это вместе#

В приложении, для начала, будет одна страница, интерфейс будет основан на слегка модифицированном компоненте из первого примера

page/main/ui.tsx
<List    Header={<ConversationSwitch />}    Items={<Messages />}    Footer={<MessageInput />}/>

Модель данных#

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

Однако, реализация в виде фабрики необязательна - фича может зависеть от нижележащих слоев и напрямую

pages/main/model.ts
import { userStore } from "entitites/user"import { conversationStore } from "entities/conversation"import { contactStore } from "entities/contact"
import { createMessageInput } from "features/message-input"import { createConversationSwitch } from "features/conversation-switch"
import { beautifiy } from "shared/lib/beautify-text"
export const { allConversations, setConversation } = createConversationSwitch({    contacts: contactStore.allContacts,    setConversation: conversationStore.setConversation,    currentConversation: conversationStore.conversation,    currentUser: userStore.currentUser})
export const { sendMessage, attachFile } = createMessageInput({    author: userStore.currentUser    send: conversationStore.sendMessage,    formatMessage: beautify})

Итого#

  1. Модули должны обладать сильной связностью (иметь одну ответственность, решать одну конкретную задачу) и предоставлять публичный интерфейс доступа
  2. Слабая зацепленность достигается через композицию элементов - компонентов UI, фич и сущностей
  3. Также, для снижения зацепленности, модули должны зависеть друг от друга только через публичные интерфейсы - так достигается независимость модулей от внутренней реализации друг друга

См. также#