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

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. Также, для снижения зацепленности, модули должны зависеть друг от друга только через публичные интерфейсы - так достигается независимость модулей от внутренней реализации друг друга

См. также