Hierarchical decomposition
You should never start writing classes for your application right away. First it needs to be designed. The design should end with a thoughtful architecture. And to get this architecture, you need to consistently decompose the system.
Decomposition must be carried out hierarchically - first, the system is divided into large functional modules / subsystems that describe its operation in the most general way. Then the resulting modules are analyzed in more detail and divided into submodules or objects.
Before selecting objects, divide the system into basic semantic blocks, at least mentally. In small applications, this is usually very easy to do: a couple of levels of hierarchy are quite enough, since the system is first divided into subsystems / packages, and packages are divided into classes.

This idea is not as trivial as it seems. For example, what is the essence of such a common “architectural pattern” as Model-View-Controller (MVC)?
It's all about separating the presentation from the business logic . First, any user application is divided into two modules - one is responsible for implementing the business logic itself (Model), and the second is responsible for interacting with the user (User Interface or View).
Then it turns out that the modules must somehow interact, for this they add a Controller, whose task is to manage the interaction of the modules. Also in the mobile (classic) version of MVC, the Observer pattern is added to it so that the View can receive events from the model and change the displayed data in real time.
Typical top-level modules, obtained as a result of the first division of the system into the largest components, are precisely:
- Business logic;
- User interface;
- Database;
- Messaging system;
- Object container.
The first split usually splits the entire application into 2-7 (maximum 10 parts). If we break it down into more parts, then there will be a desire to group them, and we will again get 2-7 top-level modules.
Functional decomposition
The division into modules / subsystems is best done based on the tasks that the system solves . The main task is divided into its constituent subtasks, which can be solved/performed autonomously, independently of each other.
Each module should be responsible for solving some subtask and perform its corresponding function . In addition to the functional purpose, the module is also characterized by a set of data necessary for it to perform its function, that is:
Module = Function + Data needed to execute it.
If the decomposition into modules is done correctly, then the interaction with other modules (responsible for other functions) will be minimal. It may be, but its absence should not be critical for your module.
A module is not an arbitrary piece of code, but a separate functionally meaningful and complete program unit (subprogram) that provides a solution to a certain task and, ideally, can work independently or in another environment and be reused. The module should be a kind of "integrity capable of relative independence in behavior and development." (Christopher Alexander)
Thus, competent decomposition is based, first of all, on the analysis of the system functions and the data necessary to perform these functions. Functions in this case are not class functions and modules, because they are not objects. If you have only a couple of classes in a module, then you overdid it.
Strong and weak connectivity
It is very important not to overdo it with modularization. If you give a beginner a monolithic Spring application and ask him to break it into modules, then he will take out each Spring Bean into a separate module and consider that his work is finished. But it's not.
The main criterion for the quality of decomposition is how the modules are focused on solving their tasks and are independent.
This is usually formulated as follows: "The modules obtained as a result of decomposition should be maximally conjugated internally (high internal cohesion) and minimally interconnected with each other (low external coupling)."
High Cohesion, high cohesion or "cohesion" within the module, indicates that the module is focused on solving one narrow problem, and is not engaged in performing heterogeneous functions or unrelated responsibilities.
Cohesion characterizes the degree to which the tasks performed by the module are related to each other.
A consequence of High Cohesion is the Single Responsibility Principle - the first of the five SOLID principles , according to which any object / module should have only one responsibility and there should not be more than one reason for changing it.
Low Coupling , loose coupling, means that the modules into which the system is divided should be, if possible, independent or loosely coupled to each other. They should be able to interact, but at the same time know as little as possible about each other.
Each module does not need to know how the other module works, what language it is written in, and how it works. Often, to organize the interaction of such modules, a certain container is used, into which these modules are loaded.
With proper design, if you change one module, you will not have to edit others, or these changes will be minimal. The looser the coupling, the easier it is to write/understand/extend/repair the program.
It is believed that well-designed modules should have the following properties:
- Functional integrity and completeness - each module implements one function, but implements it well and completely, the module independently performs a full set of operations to implement its function.
- One input and one output - at the input, the program module receives a certain set of initial data, performs meaningful processing and returns one set of result data, that is, the standard IPO principle is implemented - input -\u003e process -\u003e output.
- Logical independence - the result of the work of the program module depends only on the initial data, but does not depend on the work of other modules.
- Weak information links with other modules - the exchange of information between modules should be minimized if possible.
It is very difficult for a beginner to understand how to reduce the connectivity of modules even more. Partly this knowledge comes with experience, partly - after reading smart books. But it is best to analyze the architectures of existing applications.
Composition instead of inheritance
Competent decomposition is a kind of art and a difficult task for most programmers. Simplicity is deceptive here, and mistakes are costly.

It happens that dedicated modules are strongly coupled with each other and cannot be developed independently. Or it is not clear what function each of them is responsible for. If you encounter a similar problem, then most likely the partitioning into modules was done incorrectly.
It should always be clear what role each module plays . The most reliable criterion that the decomposition is done correctly is if the modules are independent and valuable subroutines that can be used in isolation from the rest of the application (and therefore can be reused).
When decomposing a system, it is desirable to check its quality by asking yourself the questions: "What task does each module perform?", "How easy are the modules to test?", "Is it possible to use the modules on their own or in another environment?" affect others?"
You need to try to keep the modules as autonomous as possible . As mentioned before, this is a key parameter for proper decomposition . Therefore, it must be carried out in such a way that the modules are initially weakly dependent on each other. If you succeeded, then you are great.
If not, then all is not lost here either. There are a number of special techniques and patterns that allow you to further minimize and weaken the links between subsystems. For example, in the case of MVC, the Observer pattern was used for this purpose, but other solutions are possible.
It can be said that techniques for decoupling constitute the main "architect's toolkit". It is only necessary to understand that we are talking about all subsystems and it is necessary to weaken the connection at all levels of the hierarchy , that is, not only between classes, but also between modules at each hierarchical level.
GO TO FULL VERSION