我观看了Raymond Hettinger在Pycon上发表的“超级被认为是超级”的演讲,并了解了一些Python的MRO(方法解析顺序),该方法以确定性方式线性化了“父”类。像下面的代码一样,我们可以利用它来发挥优势,进行依赖注入。因此,自然而然地,我现在想使用super
所有功能!
在下面的示例中,User
该类通过从LoggingService
和继承来声明其依赖项UserService
。这不是特别特殊。有趣的是,我们可以使用“方法解析顺序”来模拟单元测试期间的依赖关系。下面的代码创建一个MockUserService
从继承UserService
并提供我们要模拟的方法的实现。在下面的示例中,我们提供的实现validate_credentials
。为了MockUserService
处理任何呼叫,validate_credentials
我们需要先将其UserService
放置在MRO中。这通过创建一个包装类各地进行User
所谓的MockUser
和有它继承User
和MockUserService
。
现在,当我们执行MockUser.authenticate
该操作时,它依次调用方法解析顺序中的super().validate_credentials()
MockUserService
before UserService
,因为它提供了validate_credentials
该实现的具体实现。是的-我们已经UserService
在单元测试中成功模拟了。考虑到这样UserService
做可能会导致一些昂贵的网络或数据库调用-我们刚刚删除了此延迟因素。也没有UserService
接触实时/生产数据的风险。
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())
感觉很聪明,但这是对Python的多重继承和方法解析顺序的一种有效的使用吗?当我以用Java学习OOP的方式来考虑继承时,这感觉是完全错误的,因为我们不能说User
a UserService
或User
is a LoggingService
。这样想,以上述代码使用继承的方式使用继承没有多大意义。还是?如果我们仅使用继承来提供代码重用,而不考虑父子关系,那么这似乎并不糟糕。
我做错了吗?