J'ai regardé Pycon de Raymond Hettinger parler de "Super Considéré Super" et j'ai appris un peu sur le MRO de Python (Ordre de résolution de méthode) qui linéarise les classes "parent" d'une manière déterministe. Nous pouvons l'utiliser à notre avantage, comme dans le code ci-dessous, pour effectuer une injection de dépendance. Alors maintenant, naturellement, je veux l'utiliser super
pour tout!
Dans l'exemple ci-dessous, la User
classe déclare ses dépendances en héritant des deux LoggingService
et UserService
. Ce n'est pas particulièrement spécial. La partie intéressante est que nous pouvons utiliser l'Ordre de résolution de méthode pour simuler les dépendances lors des tests unitaires. Le code ci-dessous crée un MockUserService
qui hérite de UserService
et fournit une implémentation des méthodes que nous voulons simuler. Dans l'exemple ci-dessous, nous fournissons une implémentation de validate_credentials
. Afin de MockUserService
gérer tous les appels à, validate_credentials
nous devons le positionner auparavant UserService
dans le MRO. Cela se fait en créant une classe wrapper autour de User
appelée MockUser
et en la faisant hériter de User
et MockUserService
.
Maintenant, quand nous le faisons MockUser.authenticate
et, à son tour, les appels à super().validate_credentials()
MockUserService
est avant UserService
dans l'ordre de résolution de méthode et, puisqu'il offre une implémentation concrète de validate_credentials
cette implémentation, ils seront utilisés. Oui - nous avons réussi à nous moquer UserService
de nos tests unitaires. Considérez que cela UserService
pourrait faire des appels réseau ou de base de données coûteux - nous venons de supprimer le facteur de latence de cela. Il n'y a pas non plus de risque de UserService
toucher aux données live / 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())
Cela semble assez intelligent, mais est-ce une bonne et valide utilisation de l'héritage multiple et de l'ordre de résolution de méthode de Python? Quand je pense à l'héritage dans la façon dont j'ai appris la POO avec Java, cela semble complètement faux parce que nous ne pouvons pas dire User
est UserService
ou User
est un LoggingService
. Penser de cette façon, utiliser l'héritage de la façon dont le code ci-dessus l'utilise n'a pas beaucoup de sens. Ou est-ce? Si nous utilisons l'héritage uniquement pour fournir une réutilisation du code, et sans penser en termes de relations parents-> enfants, cela ne semble pas si mal.
Suis-je en train de mal faire?