Widziałem, jak Raymond Hettinger Pycon mówi „Super uważany za super” i dowiedziałem się trochę o MRO Pythona (Order Resolution Order), który deterministycznie interpretuje klasy „nadrzędne”. Możemy to wykorzystać na naszą korzyść, tak jak w poniższym kodzie, aby wykonać wstrzyknięcie zależności. Więc teraz oczywiście chcę używać superdo wszystkiego!
W poniższym przykładzie Userklasa deklaruje swoje zależności, dziedzicząc zarówno z, jak LoggingServicei UserService. To nie jest specjalnie wyjątkowe. Interesującą częścią jest to, że możemy użyć metody Resolution Resolution Order, aby wykpić zależności podczas testów jednostkowych. Poniższy kod tworzy MockUserServicedziedziczenie UserServicei zapewnia implementację metod, które chcemy wyśmiewać. W poniższym przykładzie zapewniamy implementację validate_credentials. Aby MockUserServiceobsłużyć wszelkie połączenia validate_credentials, musimy wcześniej ustawić go UserServicew MRO. Odbywa się to poprzez utworzenie klasy otoki wokół i Userwywołanie MockUserjej dziedziczenia z Useri MockUserService.
Teraz, kiedy mamy zrobić MockUser.authenticatei go z kolei domaga się super().validate_credentials() MockUserServiceto zanim UserServicew Metodzie Uchwałą Zakonu i, ponieważ oferuje implementacja beton z validate_credentialstej implementacji zostaną wykorzystane. Tak - z powodzeniem wyśmiewaliśmy się UserServicew naszych testach jednostkowych. Pomyśl, że UserServicemoże to powodować kosztowne połączenia sieciowe lub bazy danych - właśnie usunęliśmy czynnik opóźniający. Nie ma również ryzyka UserServicedotknięcia danych na żywo / prod.
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())
To wydaje się dość sprytne, ale czy jest to dobre i prawidłowe wykorzystanie wielokrotnego dziedziczenia Pythona i kolejności rozwiązywania metod? Kiedy myślę o dziedziczeniu w sposobie, w jaki nauczyłem się OOP z Javą, wydaje się to całkowicie błędne, ponieważ nie możemy powiedzieć, że Userjest a UserServicelub Userjest LoggingService. Myślenie w ten sposób, używanie dziedziczenia tak, jak wykorzystuje powyższy kod, nie ma większego sensu. Albo to jest? Jeśli używamy dziedziczenia wyłącznie w celu zapewnienia ponownego użycia kodu, a nie myślenia w kategoriach relacji rodzic-> dzieci, nie wydaje się to takie złe.
Czy robię to źle?