Ich habe gesehen, wie Raymond Hettingers Pycon "Super Considered Super" sprach, und ein wenig über Pythons MRO (Method Resolution Order) gelernt, das eine Klasse "Eltern" -Klassen auf deterministische Weise linearisiert. Wir können dies zu unserem Vorteil nutzen, wie im folgenden Code, um die Abhängigkeitsinjektion durchzuführen. Also jetzt möchte ich natürlich super
für alles verwenden!
Im folgenden Beispiel User
deklariert die Klasse ihre Abhängigkeiten, indem sie von LoggingService
und erbt UserService
. Das ist nicht besonders speziell. Der interessante Teil ist, dass wir mit der Method Resolution Order auch Abhängigkeiten während des Unit-Tests verspotten können. Der folgende Code erstellt ein, MockUserService
das UserService
von den Methoden erbt und diese implementiert, die wir verspotten möchten. Im folgenden Beispiel stellen wir eine Implementierung von bereit validate_credentials
. Um MockUserService
Anrufe zu bearbeiten, müssen validate_credentials
wir sie vorher UserService
im MRO positionieren . Dies geschieht, indem eine Wrapper-Klasse um User
aufgerufen erstellt MockUser
und von User
und geerbt wird MockUserService
.
Wenn wir dies jetzt tun MockUser.authenticate
und es wiederum Aufrufe an super().validate_credentials()
MockUserService
ist , ist dies UserService
in der Reihenfolge der Methodenauflösung vorher und wird, da es eine konkrete Implementierung validate_credentials
dieser Implementierung bietet , verwendet. Ja - wir haben uns UserService
in unseren Unit-Tests erfolgreich verspottet . Bedenken Sie, dass UserService
dies einige teure Netzwerk- oder Datenbankaufrufe verursachen könnte - wir haben gerade den Latenzfaktor entfernt. Es besteht auch kein Risiko UserService
, Live- / Produktdaten zu berühren.
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())
Das fühlt sich ziemlich klug an, aber ist dies eine gute und gültige Verwendung von Pythons Mehrfachvererbung und Methodenauflösungsreihenfolge? Wenn ich über Vererbung nachdenke, wie ich OOP mit Java gelernt habe, fühlt sich das völlig falsch an, weil wir nicht sagen können, ob User
es ein UserService
oder ein User
ist LoggingService
. So zu denken und die Vererbung so zu verwenden, wie es der obige Code verwendet, macht wenig Sinn. Oder ist es? Wenn wir die Vererbung nur verwenden, um die Wiederverwendung von Code bereitzustellen, und nicht in Eltern-Kind-Beziehungen denken, dann scheint dies nicht so schlimm zu sein.
Mache ich es falsch
Antworten:
Nein. Dies ist eine theoretisch beabsichtigte Verwendung des C3-Linearisierungsalgorithmus. Dies widerspricht Ihren vertrauten Beziehungen, aber einige betrachten die Komposition als der Vererbung vorzuziehen. In diesem Fall haben Sie einige Has-A-Beziehungen erstellt. Es scheint, dass Sie auf dem richtigen Weg sind (obwohl Python über ein Protokollierungsmodul verfügt, ist die Semantik etwas fragwürdig, aber als akademische Übung ist es vollkommen in Ordnung).
Ich denke nicht, dass Spott oder Affen-Patching eine schlechte Sache ist, aber wenn Sie sie mit dieser Methode vermeiden können, ist das gut für Sie - mit zugegebenermaßen mehr Komplexität haben Sie es vermieden, die Definitionen der Produktionsklassen zu ändern.
Es sieht gut aus. Sie haben eine möglicherweise teure Methode außer Kraft gesetzt, ohne Affen zu patchen oder einen Schein-Patch zu verwenden, was wiederum bedeutet, dass Sie die Definitionen der Produktionsklassen nicht einmal direkt geändert haben.
Wenn Sie die Funktionalität ausüben möchten, ohne tatsächlich Anmeldeinformationen im Test zu haben, sollten Sie wahrscheinlich Folgendes tun:
anstatt Ihre echten Anmeldeinformationen zu verwenden und zu überprüfen, ob die Parameter korrekt empfangen wurden, möglicherweise mit Zusicherungen (da dies schließlich Testcode ist):
Ansonsten sieht es so aus, als hätten Sie es herausgefunden. Sie können den MRO folgendermaßen überprüfen:
Und Sie können überprüfen, ob das
MockUserService
Vorrang vor dem hatUserService
.quelle