Saya menonton pembicaraan Pycon karya Raymond Hettinger, "Super Considered Super" dan belajar sedikit tentang Python MRO (Method Resolution Order) yang membuat linearkan kelas "induk" kelas dengan cara deterministik. Kita dapat menggunakan ini untuk keuntungan kita, seperti dalam kode di bawah ini, untuk melakukan injeksi ketergantungan. Jadi sekarang, tentu saja, saya ingin menggunakannya super
untuk semuanya!
Dalam contoh di bawah ini, User
kelas menyatakan dependensinya dengan mewarisi dari keduanya LoggingService
dan UserService
. Ini tidak terlalu istimewa. Bagian yang menarik adalah bahwa kita dapat menggunakan Metode Resolution Order juga mengejek dependensi selama pengujian unit. Kode di bawah ini membuat MockUserService
yang diturunkan dari UserService
dan menyediakan implementasi metode yang ingin kita tiru. Dalam contoh di bawah ini, kami menyediakan implementasi validate_credentials
. Agar dapat MockUserService
menangani panggilan apa pun, validate_credentials
kita perlu UserService
mengaturnya sebelum di MRO. Ini dilakukan dengan membuat kelas pembungkus sekitar User
dipanggil MockUser
dan memilikinya mewarisi dari User
dan MockUserService
.
Sekarang, ketika kita melakukannya MockUser.authenticate
dan, pada gilirannya, panggilan ke super().validate_credentials()
MockUserService
sebelumnya UserService
dalam Metode Resolusi Urutan dan, karena menawarkan implementasi konkret dari validate_credentials
implementasi ini akan digunakan. Yay - kami berhasil mengejek UserService
unit test kami. Pertimbangkan yang UserService
mungkin melakukan panggilan jaringan atau database yang mahal - kami baru saja menghapus faktor latensi ini. Juga tidak ada risiko UserService
menyentuh data langsung / 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())
Ini terasa cukup pintar, tetapi apakah ini penggunaan yang baik dan valid dari pewarisan berganda Python dan Metode Resolution Order? Ketika saya berpikir tentang pewarisan cara saya belajar OOP dengan Java, ini terasa sangat salah karena kita tidak bisa mengatakan User
apakah a UserService
atau User
a LoggingService
. Berpikir seperti itu, menggunakan warisan cara kode di atas menggunakannya tidak masuk akal. Atau itu? Jika kita menggunakan warisan semata-mata hanya untuk menyediakan penggunaan kembali kode, dan tidak memikirkan hubungan orangtua-anak, maka ini sepertinya tidak terlalu buruk.
Apakah saya salah?