Я наблюдал за речью Рэймонда Хеттингера о Pycon «Супер рассмотренный супер» и немного узнал о MRO (порядок разрешения методов) Python, который линеаризует классы «родительские» классы детерминистическим способом. Мы можем использовать это в наших интересах, как в приведенном ниже коде, для внедрения зависимостей. Так что теперь, естественно, я хочу использовать super
для всего!
В приведенном ниже примере User
класс объявляет свои зависимости, наследуя от обоих LoggingService
и UserService
. Это не особенно особенное. Интересно то, что мы можем использовать Порядок разрешения методов и макетировать зависимости во время модульного тестирования. Код ниже создает объект, MockUserService
который наследует UserService
и обеспечивает реализацию методов, которые мы хотим смоделировать. В приведенном ниже примере мы предоставляем реализацию validate_credentials
. Для того, чтобы MockUserService
обрабатывать любые вызовы, validate_credentials
нам нужно разместить его раньше UserService
в MRO. Это делается путем создания класса-обертки для User
вызываемого MockUser
и наследования от User
и MockUserService
.
Теперь, когда мы это сделаем, MockUser.authenticate
а это, в свою очередь, вызовы super().validate_credentials()
MockUserService
- это раньше, UserService
в Порядке разрешения методов и, поскольку он предлагает конкретную реализацию validate_credentials
этой реализации, будет использоваться. Yay - мы успешно смоделированы UserService
в наших модульных тестах. Учтите, что это UserService
может сделать дорогостоящие вызовы сети или базы данных - мы только что убрали фактор задержки. Также нет риска UserService
прикосновения к данным в реальном времени / продукту.
class LoggingService(object):
"""
Just a contrived logging class for demonstration purposes
"""
def log_error(self, error):
pass
class UserService(object):
"""
Provide a method to authenticate the user by performing some expensive DB or network operation.
"""
def validate_credentials(self, username, password):
print('> UserService::validate_credentials')
return username == 'iainjames88' and password == 'secret'
class User(LoggingService, UserService):
"""
A User model class for demonstration purposes. In production, this code authenticates user credentials by calling
super().validate_credentials and having the MRO resolve which class should handle this call.
"""
def __init__(self, username, password):
self.username = username
self.password = password
def authenticate(self):
if super().validate_credentials(self.username, self.password):
return True
super().log_error('Incorrect username/password combination')
return False
class MockUserService(UserService):
"""
Provide an implementation for validate_credentials() method. Now, calls from super() stop here when part of MRO.
"""
def validate_credentials(self, username, password):
print('> MockUserService::validate_credentials')
return True
class MockUser(User, MockUserService):
"""
A wrapper class around User to change it's MRO so that MockUserService is injected before UserService.
"""
pass
if __name__ == '__main__':
# Normal useage of the User class which uses UserService to resolve super().validate_credentials() calls.
user = User('iainjames88', 'secret')
print(user.authenticate())
# Use the wrapper class MockUser which positions the MockUserService before UserService in the MRO. Since the class
# MockUserService provides an implementation for validate_credentials() calls to super().validate_credentials() from
# MockUser class will be resolved by MockUserService and not passed to the next in line.
mock_user = MockUser('iainjames88', 'secret')
print(mock_user.authenticate())
Это кажется довольно умным, но действительно ли это хорошее и правильное использование множественного наследования Python и порядка разрешения методов? Когда я думаю о наследовании способом, которым я изучил ООП с Java, это кажется совершенно неправильным, потому что мы не можем сказать, User
является UserService
или User
есть LoggingService
. Думать таким образом, используя наследование так, как это делает вышеприведенный код, не имеет особого смысла. Или это? Если мы используем наследование исключительно для обеспечения повторного использования кода и не думаем с точки зрения отношений между родителями и детьми, то это не так уж плохо.
Я делаю это неправильно?