Я спостерігав, як Реймонд Хеттінгер говорив про "Супер вважається Супер" і дізнався трохи про MRO Python (Order Resolution Order), який детерміновано лінеаризує "батьківські" класи. Ми можемо використовувати це для наших переваг, як у наведеному нижче коді, щоб зробити ін'єкцію залежності. Тому зараз, природно, я хочу використовувати super
для всього!
У наведеному нижче прикладі User
клас заявляє про свої залежності, успадковуючи і від, LoggingService
і від UserService
. Це не особливо. Цікава частина полягає в тому, що ми можемо використовувати наказ про дозвіл методів також висміювати залежності під час тестування одиниць. Нижче наведений код створює функцію, MockUserService
яка успадковується UserService
та забезпечує реалізацію методів, з яких ми хочемо знущатися. У наведеному нижче прикладі ми пропонуємо реалізацію validate_credentials
. Для того, щоб MockUserService
обробляти будь-які дзвінки, validate_credentials
нам потрібно позиціонувати його раніше UserService
в МРО. Це робиться шляхом створення класу обгортки навколо User
виклику MockUser
та спадкування від User
та MockUserService
.
Тепер, коли ми це зробимо, MockUser.authenticate
і це, в свою чергу, вимагає, щоб super().validate_credentials()
MockUserService
це було раніше UserService
у Порядку розв'язання методів, і оскільки він пропонує конкретну реалізацію validate_credentials
цієї реалізації, буде використано. Так - ми успішно висміяли 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
. Думаючи про це, використовувати успадкування так, як використовується вищевказаний код, не має особливого сенсу. Або це? Якщо ми використовуємо спадщину виключно для того, щоб забезпечити повторне використання коду, а не думати про стосунки батьків-> дітей, то це не здається поганим.
Чи я це роблю неправильно?