
Click the above blue text to follow C Language Chinese Community
Source: CSDN
1. Principles
Low coupling refers to making modules as independent as possible. While it is impossible for modules to have no connections, the interfaces between modules should be minimized and simplified. Thus, high cohesion from the perspective of the internal characteristics of each module in the program, and low coupling from the perspective of the relationships between various modules in the program, impose requirements on our design.
Many technologies and design principles that have emerged during the development of program design and software engineering can be interpreted from the perspective of cohesion and coupling. As beginners in C language programming, we will explore how to achieve high cohesion and low coupling based on our current understanding of functions.
Regarding low coupling, the lowest level of coupling is non-direct coupling, which means that the connection between two functions is entirely realized through the control and invocation of a common calling function, resulting in the weakest coupling and the strongest independence of functions. However, it is unrealistic for a group of functions to have no data transfer; thus, we pursue data coupling, where the calling function and the called function only pass simple data parameters, such as functions using value passing.
Some functions, when called, use the method of passing addresses through formal parameters, allowing the function body to modify storage units outside its scope via pointers, which constitutes a stronger coupling known as characteristic coupling. Here, the connection between functions is established through address identifiers. Additionally, if two functions open the same file for operations, this also constitutes a form of characteristic coupling.
A stronger coupling is external coupling, where a group of modules accesses the same global variable without passing information about that global variable through parameter tables. When an abnormal program execution result is found, it is difficult to locate which function has the error. Many beginners find parameter passing troublesome and define the data to be processed as global variables as much as possible, simplifying the interfaces between functions but forming a structure with strong coupling.
In C language, static local variables can also share data between two calls of the same program, which can also be seen as a form of external coupling. However, the scope of static local variables is limited to the function, and their impact is only within the function, making the coupling degree much weaker than using global variables. Thus, we can understand the principle of “use when appropriate, do not misuse” when using global variables and static local variables.
Regarding high cohesion, the highest level of cohesion is functional cohesion, where all components within a module exist to complete the same function, collectively achieving a single function, making the module indivisible. Such functions have very clear and defined functionalities and generally appear at lower levels in the program structure diagram.
The next level is sequential cohesion, where the various processing elements in a function are closely related to the same function, usually with the output of the previous processing element being the input of the next processing element. For such functions, if high coupling is not produced, they can be separated into two functions.
In some functions, different processing functionalities are associated merely because they access a common data, which is called communication cohesion and information cohesion, with the level of cohesion further decreasing. The lowest levels of cohesion will not be listed one by one; the worst is coincidental cohesion, where there is no connection between the processing elements within a function, merely being coincidentally grouped together.
One can imagine such a module as a disorganized group, corresponding to low quality. In summary, when solving problems and dividing functions, one should adhere to the principle of “one function, one feature” and strive to achieve functional cohesion in modules.
To achieve high cohesion and low coupling, the key is to spend some time on design before writing code. In the following example, we will discuss how to consider the above factors in conjunction with specific problems.
2. Example
This example is inspired by Teacher Qiu Zongyan’s “From Problem to Program – Introduction to Program Design and C Language”.
Task: Output perfect squares within 200 (a number is called a perfect square if it is the square of another integer, also known as a square number), requiring a newline after every 5 data.
Solution and Comments: For this simple task, we completed it in a main function. The program is as follows in Solution 1:
// Solution 1: High cohesion single-module implementation
#include <stdio.h>
int main()
{
int m, num=0;
for (m = 1; m * m <= 200; m++)
{
printf("%d ", m * m);
num++;
if (num%5==0)
printf("\n");
}
return 0;
}
Since the task itself is simple, implementing it in a single main function results in a cohesion level close to functional cohesion, which is already quite high. For the task itself, no further decomposition is needed. To help readers gain a deeper understanding of the technical aspects of module quality, we will attempt to further improve the cohesion of the program and then examine various solutions with different coupling levels.
To improve the cohesion of the above solution (only the main function), we consider the functional aspects of “finding perfect squares and outputting” – “finding perfect squares” and “outputting” are two functions in themselves, and when detailing the output, there is also the requirement of “5 data per line”. These functional implementation details are all within one function, indicating that there is room to further improve the level of cohesion.
In practical applications, almost all processing can be decomposed into the “input-computation-output” pattern. Excellent solutions often require at least these three modules to be independent. For the “computation” module, it should not include input and output, but rather accept input data and return results after computation. Of course, for complex problems, further decomposition may be needed at various stages.
Next, we will explore a solution that separates the implementation of “finding perfect squares and outputting” from “new line after every 5 data”. Such decomposition helps improve cohesion, while the coupling level between the two decomposed modules becomes the focus of our attention.
Now, we will keep the functionality of “finding perfect squares and outputting” in the main function (it can also be made into a separate function, but it is unnecessary), while designing a function named format for the functionality of “new line after every 5 data”, which outputs a space as a separator between two perfect squares each time it is called, and outputs a newline every 5th call.
Between these two modules, there is a need to pass information about “how many times it has been called”, which cannot use the loosest coupling of non-direct coupling. We consider data coupling, using simple formal parameters to pass values, resulting in Solution 2.
// Solution 2: A low coupling solution that cannot meet functional requirements
#include <stdio.h>
void format(int);
int main()
{
int m, num=0;
for (m = 1; m * m <= 200; m++)
{
printf("%d", m * m);
format(num);
}
return 0;
}
void format(int n)
{
n++;
if (n%5==0)
printf("\n");
else
printf(" ");
return;
}
In this program structure, the coupling level between format and main functions is data coupling. In main, the local variable num is initialized to 0, which is reasonable since it has not been output yet. When calling format, num is passed as the formal parameter n, indicating the number of outputs (the nth perfect square), and n is incremented by 1 to control the output of space or newline.
However, analysis and execution of the program reveal that the functionality of “outputting a newline after every 5 data” has not been achieved. This is because the change of the formal parameter n in the format function corresponds to a different memory space than the actual parameter num, and the modification of n++ does not affect num, leading to the loss of important information about “which output is being made” in the next call.
A remedy is to have format return the changed n value back to the main function, resulting in the program of Solution 3:
// Solution 3: A solution that increases coupling but achieves functionality using return values
#include <stdio.h>
int format(int);
int main()
{
int m, num=0;
for (m = 1; m * m <= 200; m++)
{
printf("%d", m * m);
num = format(num);
}
return 0;
}
int format(int n)
{
n++;
if (n%5==0)
printf("\n");
else
printf(" ");
return n;
}
Maintaining the original function’s return value as void, we modify the parameter to pass by address, resulting in the following program of Solution 4. This solution has a higher coupling level but still achieves functionality.
// Solution 4: A solution that implements functionality by passing addresses, with higher coupling
#include <stdio.h>
void format(int*);
int main()
{
int m, num=0;
for (m = 1; m * m <= 200; m++)
{
printf("%d", m * m);
format(&num);
}
return 0;
}
void format(int *p)
{
(*p)++;
if ((*p)%5==0)
printf("\n");
else
printf(" ");
return;
}
Some may have thought of using global variables as a solution. This way, num can be defined as a global variable, and its lifespan is no longer dependent on function calls, allowing its value to remain unchanged between function calls (as long as it is not assigned a new value in between), thus completing the task of passing information. In this case, format can be designed as a no-parameter function, resulting in the following program of Solution 5:
// Solution 5: The highest coupling solution using global variables
#include <stdio.h>
void format();
int num=0;
int main()
{
int m;
for (m = 1; m * m <= 200; m++)
{
printf("%d", m * m);
format();
}
return 0;
}
void format()
{
num++;
if (num%5==0)
printf("\n");
else
printf(" ");
return;
}
This is the solution with the highest coupling level for this problem. Defining num as an external variable means that if there are other functions, num can be modified by any function, making it difficult to locate errors when format counts incorrectly, and modifications may lead to errors elsewhere. In such a short program, this solution may still be acceptable, but as the scale of the program increases, the issues it may bring must be taken seriously. Therefore, in practical applications, it is emphasized that global variables should be used cautiously (not avoided).
Considering that num is private data used in format – only format cares about which data it is, while main does not need to care. Thus, we can consider defining num as a local static variable within format, resulting in the program of Solution 6:
// Solution 6: Using static local variables, with higher coupling but the best encapsulation
#include <stdio.h>
void format();
int main()
{
int m;
for (m = 1; m * m <= 200; m++)
{
printf("%d", m * m);
format();
}
return 0;
}
void format()
{
static int num=0;
num++;
if (num%5==0)
printf("\n");
else
printf(" ");
return;
}
Here, the scope of the static local variable num is local, defined within the function body, providing the best encapsulation among all solutions, thus ensuring information concealment and avoiding unintentional access by other functions. However, the lifespan of num is global, allowing it to pass information between different calls of the function, resulting in a higher coupling degree (self-coupling), but achieving the ideal coupling between the main function and the format function, ensuring both functional correctness and the safety of local data, demonstrating the advantages of static local variables. In summary, there are many solutions to a problem. Solution 1 is acceptable, but improvements are desired to enhance cohesion; Solution 2 achieves low coupling through simple parameter passing but unfortunately cannot meet functionality; among other solutions, the preferred order for this problem is:
❝
Solution 6, Solution 3 > Solution 4 > Solution 5
❞
Readers are encouraged to review the previous content and consider the reasons for this ordering. Throughout the discussion of various solutions, we should realize that in the process of improving programming skills, it is essential to continuously learn new technologies and understand new evaluation criteria, which is a process of broadening one’s perspective. In subsequent exercises, it is advisable to think of more solutions and be able to evaluate the pros and cons of solutions from a professional perspective, ultimately achieving a level of expertise where the best solution is instinctively chosen.
Source:https://helijian.blog.csdn.net/article/details/79401703
【Disclaimer】: Articles reproduced from other platforms or media will be credited to the source and author, but no express or implied guarantee is provided regarding the accuracy, reliability, or completeness of the content contained herein, and it is for reference only. This public account is only used for learning and appreciation, not for any commercial profit. If there is any infringement, please contact this platform and provide relevant proof of the page, and this platform will correct the source and author or delete the article based on the opinion of the copyright owner, and will not bear any other responsibility.
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧
Follow my WeChat public account and reply "C Language" to receive 300G of programming materials for free.