Tôi đã xem Pycon của Raymond Hettinger nói "Siêu được coi là siêu" và tìm hiểu một chút về MRO (Thứ tự giải quyết phương pháp) của Python để tuyến tính hóa các lớp "cha mẹ" theo cách xác định. Chúng ta có thể sử dụng điều này cho lợi thế của mình, như trong đoạn mã dưới đây, để thực hiện tiêm phụ thuộc. Vì vậy, bây giờ, tự nhiên, tôi muốn sử dụng super
cho tất cả mọi thứ!
Trong ví dụ dưới đây, User
lớp khai báo các phụ thuộc của nó bằng cách kế thừa từ cả hai LoggingService
và UserService
. Điều này không đặc biệt. Phần thú vị là chúng ta có thể sử dụng Lệnh giải quyết phương pháp cũng chế nhạo các phụ thuộc trong quá trình kiểm tra đơn vị. Mã dưới đây tạo ra một MockUserService
kế thừa từ đó UserService
và cung cấp một triển khai các phương thức mà chúng ta muốn giả định. Trong ví dụ dưới đây, chúng tôi cung cấp một triển khai validate_credentials
. Để MockUserService
xử lý bất kỳ cuộc gọi nào, validate_credentials
chúng tôi cần định vị nó trước UserService
trong MRO. Điều này được thực hiện bằng cách tạo một lớp bao bọc xung quanh User
được gọi MockUser
và có nó kế thừa từ User
và MockUserService
.
Bây giờ, khi chúng ta thực hiện MockUser.authenticate
và lần lượt, các cuộc gọi đến super().validate_credentials()
MockUserService
trước UserService
trong Lệnh giải quyết phương pháp và, vì nó cung cấp một triển khai cụ thể của validate_credentials
việc triển khai này sẽ được sử dụng. Yay - chúng tôi đã chế giễu thành công UserService
trong các bài kiểm tra đơn vị của chúng tôi. Hãy xem xét điều đó UserService
có thể thực hiện một số cuộc gọi mạng hoặc cơ sở dữ liệu đắt tiền - chúng tôi vừa loại bỏ yếu tố độ trễ của việc này. Cũng không có nguy cơ UserService
chạm vào dữ liệu trực tiếp / 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())
Điều này cảm thấy khá thông minh, nhưng đây có phải là cách sử dụng tốt và hợp lệ của nhiều kế thừa và Thứ tự giải quyết phương thức của Python không? Khi tôi nghĩ về sự kế thừa theo cách mà tôi đã học OOP với Java, điều này cảm thấy hoàn toàn sai lầm bởi vì chúng ta không thể nói User
là một UserService
hoặc User
là một LoggingService
. Nghĩ theo cách đó, sử dụng tính kế thừa theo cách mà đoạn mã trên sử dụng nó không có ý nghĩa gì nhiều. Hoặc là nó? Nếu chúng ta sử dụng tính kế thừa hoàn toàn chỉ để cung cấp việc sử dụng lại mã và không suy nghĩ về mối quan hệ cha mẹ-> con cái, thì điều này có vẻ không tệ lắm.
Tôi đang làm sai à?