Учитывая, что Python допускает множественное наследование, как выглядит идиоматическое наследование в Python?
В языках с единичным наследованием, таких как Java, наследование будет использоваться, когда вы можете сказать, что один объект «a-a» другого объекта и вы хотите разделить код между объектами (от родительского объекта до дочернего объекта). Например, вы можете сказать, что Dog
это Animal
:
public class Animal {...}
public class Dog extends Animal {...}
Но поскольку Python поддерживает множественное наследование, мы можем создать объект, составив множество других объектов вместе. Рассмотрим пример ниже:
class UserService(object):
def validate_credentials(self, username, password):
# validate the user credentials are correct
pass
class LoggingService(object):
def log_error(self, error):
# log an error
pass
class User(UserService, LoggingService):
def __init__(self, username, password):
self.username = username
self.password = password
def authenticate(self):
if not super().validate_credentials(self.username, self.password):
super().log_error('Invalid credentials supplied')
return False
return True
Это приемлемое или хорошее использование множественного наследования в Python? Вместо того чтобы говорить, что наследование - это когда один объект «является-а» другого объекта, мы создаем User
модель, состоящую из UserService
и LoggingService
.
Вся логика для баз данных или сетевых операций может храниться отдельно от User
модели, помещая их в UserService
объект и сохраняя всю логику для входа в систему LoggingService
.
Я вижу некоторые проблемы с этим подходом:
- Создает ли это объект Бога? Поскольку
User
наследует или состоит из,UserService
иLoggingService
действительно ли он следует принципу единой ответственности? - Для того, чтобы получить доступ к методам родительского / следующего в строке объекта (например,
UserService.validate_credentials
мы должны использоватьsuper
. Это немного затрудняет просмотр объекта, который будет обрабатывать этот метод, и не так ясно, как, скажем, , создаваяUserService
и делая что-то вродеself.user_service.validate_credentials
Что бы Pythonic способ реализовать вышеупомянутый код?
User
объекты имеют в своих интерфейсах не только элементы, определенные вUser
классе, но также и элементы , определенные вUserService
иLoggingService
. Это не отношение «есть», потому что общедоступный интерфейс копируется (хотя не путем прямого копирования, а косвенным поиском интерфейсов суперклассов).Помимо факта, что он допускает множественные суперклассы, наследование Python существенно не отличается от Java, то есть члены подкласса также являются членами каждого из своих супертипов [1]. Тот факт, что Python использует duck-typing, также не имеет значения: ваш подкласс имеет всех членов своих суперклассов, поэтому может использоваться любым кодом, который может использовать эти подклассы. Тот факт, что множественное наследование эффективно реализуется с помощью композиции, представляет собой «красную сельдь»: проблема заключается в автоматическом копировании свойств одного класса в другой, и не имеет значения, использует ли он композицию или просто волшебным образом догадывается, как предполагаются члены. работать: иметь их неправильно.
Да, это нарушает единственную ответственность, потому что вы даете своим объектам возможность выполнять действия, которые логически не являются частью того, для чего они предназначены. Да, он создает объекты "бога", что по сути является еще одним способом сказать то же самое.
При проектировании объектно-ориентированных систем в Python применяется тот же принцип, который проповедуется в книгах по Java-дизайну: предпочтение композиции наследованию. То же самое верно для (большинства [2]) других систем с множественным наследованием.
[1]: вы могли бы назвать это отношением «есть», хотя лично мне не нравится этот термин, потому что он предполагает идею моделирования реального мира, а объектно-ориентированное моделирование отличается от реального мира.
[2]: я не уверен в C ++. C ++ поддерживает «частное наследование», которое по сути является композицией, без необходимости указывать имя поля, когда вы хотите использовать открытые члены унаследованного класса. Это никак не влияет на публичный интерфейс класса. Я не люблю его использовать, но не вижу веских причин не делать этого.
источник