I haven't touched on it much since I studied with basic information, and at that time I didn't try to write code, so I decided to study again.
It shows how strong the module-to-module relationship is. It is said that the independence of modules can be increased by reducing the degree of coupling between modules. Increased module independence has the following benefits:
For example, take a look at the code below. This function determines whether it is a leap year based on the current year.
from datetime import datetime
from calendar import isleap
#A function that determines if this year is a leap year and outputs the result
def A():
this_year = datetime.now().year
if isleap(this_year):
print('This year is a leap year')
else:
print('This year is not a leap year')
Now, let's say you want to incorporate a "mechanism to determine if 10 years ago was a leap year" in this module.
from datetime import datetime
from calendar import isleap
#A function that determines if this year is a leap year and outputs the result
def A():
this_year = datetime.now().year
if isleap(this_year):
print('This year is a leap year')
else:
print('This year is not a leap year')
#A function that determines if 10 years ago is a leap year and outputs the result
def B():
this_year = datetime.now().year - 10
if isleap(this_year):
print('10 years ago is a leap year')
else:
print('10 years ago is not a leap year')
Almost the same code is written, and it doesn't feel very good intuitively in the first place. In addition, there are problems when testing. Suppose you want to put the following test data into function A and test whether the leap year is judged correctly.
test data | Expected value |
---|---|
2000 | This year is a leap year |
2020 | This year is a leap year |
2100 | This year is not a leap year |
So how do you test it?
That's right. This cannot be tested. This is because both functions A and B have a mixture of "processing to find the current year" and "mechanism to determine leap year", and their functions are not independent.
Therefore, it is better to do this.
def A(year):
if isleap(year):
print('This year is a leap year')
else:
print('This year is not a leap year')
In this way, the year information should be input from the outside. Now, whether it's 10 years ago, 100 years ago, or 200 years later, you only need one function, whether you have multiple test data.
There are different types of inter-module coupling.
Evaluation criteria | Coupling between modules | Module independence |
---|---|---|
Inner join | high | Low |
Common join | ||
Outer join | ||
Control join | ||
Stamp combination | ||
Data join | Low | high |
So, from here, I will summarize it with a code. This time I will reproduce it with Python.
This is almost impossible nowadays, and it's difficult to reproduce ... Is it like this if you force it?
There is such a module. username, level, attack, defense, etc. are defined as global variables.
moduleA.py
username = 'hogehogekun'
level = 25
attack = 20
defence = 5
def show_user_status():
print('User name:' + username)
print('level:' + str(level))
print('Offensive power:' + str(attack))
print('Defense power:' + str(defence))
Suppose you have code that takes advantage of this.
main.py
import moduleA
#Raise the level by 1
moduleA.level += 1
#Display status using function of moduleA
moduleA.show_user_status()
result
Username: hogehogekun
Level: 26
Attack power: 20
Defense: 5
The initial value of the level was 25, but it has increased by 1. There is no problem with the behavior, but the behavior of moduleA depends deeply on the behavior of the main module.
Now, let's say you have grouped user information into one class and managed them in one list.
moduleA.py
class User:
def __init__(self, username, level, attack, defence):
self.username = username
self.level = level
self.attack = attack
self.defence = defence
def show_user_status(self):
print('User name:' + self.username)
print('level:' + str(self.level))
print('Offensive power:' + str(self.attack))
print('Defense power:' + str(self.defence))
#Manage the list of users in a list
user_list = [User('hogehogekun', 75, 90, 80), User('fugafugakun', 10, 5, 7)]
And suppose there are two functions in mainA that use this module.
mainA.py
import moduleA
def funcA():
del(moduleA.user_list[0])
def funcB():
print(moduleA.user_list[0].username)
#Execute in the order of funcA, funcB
funcA()
funcB()
result
fugafugakun
This time around, funcA has removed the first element of the global list. By the time funcB peeked in, hogehoge had already disappeared, leaving only fugafuga. By the way, if funcB refers to moduleA.user_list [1], IndexError will occur. In this way, with common joins, if you change or delete part of a common data structure, you will need to review all the modules that reference that common part.
It's very similar to a common join, but it's a perception that the shared information is a collection of single pieces of data rather than a data structure such as a list or object.
This time, suppose you have information about the cumulative number of users and service status.
moduleA.py
class User:
def __init__(self, username, level, attack, defence):
self.username = username
self.level = level
self.attack = attack
self.defence = defence
def show_user_status(self):
print('User name:' + self.username)
print('level:' + str(self.level))
print('Offensive power:' + str(self.attack))
print('Defense power:' + str(self.defence))
user_count = 123091 #Cumulative number of users
service_status = 200 #Service status
main.py
import moduleA
def funcA():
print(moduleA.user_count)
def funcB():
print(moduleA.service_status)
funcA()
funcB()
result
123091
200
This code has no behavioral issues. Both the number of users and the service status have been acquired correctly in funcA and funcB. However, if there is a specification change such as the service_status of moduleA.py becoming a character type instead of a numeric type, it is necessary to modify funcB that refers to the corresponding information.
Control join uses arguments to control the processing of the called function. In this code, different processing is done depending on whether 1 or 2 is passed to some_command.
moduleA.py
class User:
def __init__(self, username, level, attack, defence):
self.username = username
self.level = level
self.attack = attack
self.defence = defence
def some_command(self, command_id):
if command_id == 1: #Status display command
print('User name:' + self.username)
print('level:' + str(self.level))
print('Offensive power:' + str(self.attack))
print('Defense power:' + str(self.defence))
elif command_id == 2: #Level up command
print(self.username + 'Level has increased by 1!')
self.level += 1
main.py
from moduleA import User
user1 = User('hogehogekun', 40, 20, 20)
user1.some_command(1)
user1.some_command(2)
result
Username: hogehogekun
Level: 40
Attack power: 20
Defense: 20
The level of hogehogekun has increased by 1!
It looks good at first glance because the information called command is passed from the outside. Another module that calls some_command needs to know the internal structure of some_command. Therefore, the degree of coupling is relatively high.
User class as usual. This time, we will focus on the exchange between funcA and funcB in main.py. Calling funcB inside funcA and passing a list as an argument looks like some kind of code.
moduleA.py
class User:
def __init__(self, username, level, attack, defence):
self.username = username
self.level = level
self.attack = attack
self.defence = defence
def show_user_status(self):
print('User name:' + self.username)
print('level:' + str(self.level))
print('Offensive power:' + str(self.attack))
print('Defense power:' + str(self.defence))
Suppose another module behaves like this.
main.py
from moduleA import User
def funcA():
user_list = [User('hogehogekun', 20, 10, 10), User('fugafugakun', 99, 99, 99), User('piyopiyokun', 99, 99, 99)]
funcB(user_list)
def funcB(user_list):
print(user_list[2].username)
funcA()
result
piyopiyokun
There is nothing wrong with this at this point, but if the number of elements in funcA changes, for example, funcB will be affected.
main.py
def funcA():
user_list = [User('hogehogekun', 20, 10, 10), User('fugafugakun', 99, 99, 99)]
funcB(user_list)
def funcB(user_list):
print(user_list[2].username)
funcA()
result
IndexError: list index out of range
In stamp combination, the entire list is passed, but the calling module uses only some information. This time, funcB is only using the third element, even though it is passed a list with three elements. At this time, even if there is a change in the elements that are not used on the funcB side (the number in this case) in the stamp combination, funcB may be affected.
Minimizes the information passed between multiple modules. Following the example of stamp combination, it looks like this.
moduleA.py
class User:
def __init__(self, username, level, attack, defence):
self.username = username
self.level = level
self.attack = attack
self.defence = defence
def show_user_status(self):
print('User name:' + self.username)
print('level:' + str(self.level))
print('Offensive power:' + str(self.attack))
print('Defense power:' + str(self.defence))
main.py
def funcA():
user_list = [User('hogehogekun', 20, 10, 10), User('fugafugakun', 99, 99, 99), User('piyopiyokun', 99, 99, 99)]
funcB(user_list[2])
def funcB(target_user):
print(target_user.username)
funcA()
result
piyopiyokun
In funcB, only piyopiyokun is processed, so the data to be passed is only user_list [2] in the first place. If this reduces the number of user_lists, it should affect funcA but not funcB. In the first place, the decrease in the number of user_list means that funcA is being modified, so the range of influence may be considerably smaller.
In this way, you can weaken the coupling of modules by exchanging only the necessary information.
Addendum: When I was reading the book I had, it said, "The called module will be able to directly manipulate the called data with the arguments it receives." In other words, it seems that you should pass by reference. There seemed to be variations in the way of thinking around here depending on the developer and development method.
There are various advantages to setting the strength of the module to be high and the degree of coupling between the modules to be low.
And so on!
Recommended Posts