Low Coupling & High Cohesion
Application modules should be designed according to high cohesion (should solve one specific task) and low coupling (independent of other modules) principles.
Within the methodology, this is achieved through:
- Splitting the application into layers and slices that implement specific functionality
- Providing a public access interface for each module
- Setting up restrictions for modules interactions - each module can depend only on the modules below it, but not on modules from the same or higher layer
Components composition (UI level)β
The majority of modern UI frameworks and libraries provide a component model in which each component can have its own properties, state, child components, and even slots.
This model allows you to design an interface as a composition of various components that are not directly related to each other and, thereby, achieve low coupling of the interface components.
Exampleβ
Let's consider such a composition using the example of a list with a header:
Laying the extensibilityβ
List component will not itself define the look and structure of the header components and list elements, instead it will accept them as parameters
interface ListProps {
Header: React.ReactNode;
Items: React.ReactNode;
}
const List: Component<ListProps> = ({ Header, Items }) => (
<div class="wrapper">
{Header}
<ul class="...">
{Items}
</ul>
</div>
)
Using the compositionβ
This allows you to reuse and independently change components with different Header and list Items. Header and Items components can have both their own local state and their binding to the general state of the application - the List component will not know anything about it, and therefore will not depend on it
<List Header={<FancyHeader />} Items={<ToDoItems />} />
<List Items={<CartItems />} />
<List Header={<FancyHeaderV2 color="red" />} Items={<FancyItems />} />
Layer composition (APP level)β
The methodology suggests putting the functionality that is valuable for the user into features slice, and the logic related to business entities - into entities. Both features and entities should be designed as modules with high cohesion, i.e. aimed at solving one specific task or related to one specific entity.
All interactions between such modules, similar to the UI components from the example above, should be coordinated via a modules composition.
Exampleβ
Let's use an example of a chat application with the following features:
- user can open a contact list and select a friend
- user can open a conversation with a selected friend
According to methodology principles, it can be represented as:
Entities
- User (contains user's state)
- Contact (state of the contact list, utilities for working with an individual contact)
- Chat (the state of the current chat and utilies for it)
Features
- Form for sending a message
- Chat selection menu
Let's tie it all togetherβ
The application, to begin with, will have one page, and the interface will be slightly modified from the first example
<List
Header={<ConversationSwitch />}
Items={<Messages />}
Footer={<MessageInput />}
/>
Data modelβ
The page data model will be organized as a composition of features and entities. In this example, the features will be implemented as factories and they will access the interface of entities through the parameters of these factories.
However, the implementation using factory is optional - the feature may directly depend on the lower layers.
import { userModel } from "entitites/user"
import { conversationModel } from "entities/conversation"
import { contactModel } 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: contactModel.allContacts,
setConversation: conversationModel.setConversation,
currentConversation: conversationModel.conversation,
currentUser: userModel.currentUser
})
export const { sendMessage, attachFile } = createMessageInput({
author: userModel.currentUser
send: conversationModel.sendMessage,
formatMessage: beautify
})
Summaryβ
- Modules must have high cohesion (have one responsibility, solve one specific task) and provide a public interface access
- Low coupling is achieved through the composition of elements - UI components, features and entities
- To reduce entanglement, modules should interact with each other only through a public interfaces - this makes modules independent of each other's internal implementation
See alsoβ
- (Article) Low Coupling and High Cohesion in details
- The diagram at the beginning is inspired by this article
- (Article) Low Coupling and High Cohesion. The Law of Demeter
- (Presentation) On design principles (including Low Coupling & High Cohesion)