Чи є спадщина Пітона "спадковим" стилем успадкування чи композиційним стилем?


10

З огляду на те, що Python дозволяє отримати багаторазове успадкування, як виглядає ідіоматичне успадкування в Python?

У мовах з одинарним успадкуванням, як-от Java, успадкування буде використовуватися, коли можна сказати, що один об'єкт "є-а" іншого об'єкта, і ви хочете поділити код між об'єктами (від батьківського об'єкта до дочірнього об'єкта). Наприклад, ви можете сказати, що 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

Який би був пітонічний спосіб реалізації вищевказаного коду?

Відповіді:


9

Чи є спадщина Пітона "спадковим" стилем успадкування чи композиційним стилем?

Python підтримує обидва стилі. Ви демонструєте склад-співвідношення композиції, коли Користувач має функцію реєстрації з одного джерела та перевірку облікових даних з іншого джерела. LoggingServiceІ UserServiceпідставами є Домішки: вони забезпечують функціональні можливості і не призначені , щоб бути створений самі по собі.

Складаючи комбінації типів, у вас є користувач, який може входити в систему, але він повинен додати свою функціональну інстанцію.

Це не означає, що не можна дотримуватися єдиного успадкування. Python також підтримує це. Якщо ваша здатність до розвитку заважає складності багаторазового успадкування, ви можете уникнути цього, поки не почуватимете себе комфортніше або не прийдете до дизайнерської точки, де ви вважаєте, що воно варте компромісу.

Чи створює це об'єкт Бога?

Ведення журналів здається дещо дотичним - Python має власний модуль реєстрації з об'єктом реєстратора, і умовою є те, що на модуль є один реєстратор.

Але відкладіть модуль реєстрації. Можливо, це порушує єдину відповідальність, але, можливо, у вашому конкретному контексті це вирішальне значення для визначення Користувача. Декретизація відповідальності може бути суперечливою. Але більш широкий принцип полягає в тому, що Python дозволяє користувачеві приймати рішення.

Супер менш зрозуміло?

superнеобхідний лише тоді, коли вам потрібно делегувати батькові в Порядку розв'язання методів (MRO) зсередини функції однойменної функції. Використання цього замість жорсткого кодування виклику батьківського методу - найкраща практика. Але якщо ви не збиралися жорстко кодувати батьків, вам це не потрібно super.

У вашому прикладі тут потрібно лише зробити self.validate_credentials. selfне більш зрозуміло, з вашого погляду. Вони обидва слідують за МРО. Я просто використовую кожен, де це доречно.

Якби ви зателефонували authenticate, validate_credentialsзамість цього вам знадобилося б використовувати super(або жорсткий код батьків), щоб уникнути помилки рекурсії.

Пропозиція альтернативного коду

Отже, якщо припустити, що семантика нормальна (як ведення журналу), що я б робив, це у класі User:

    def validate_credentials(self): # changed from "authenticate" to 
                                    # demonstrate need for super
        if not super().validate_credentials(self.username, self.password):
            # just use self on next call, no need for super:
            self.log_error('Invalid credentials supplied') 
            return False
        return True

1
Я не погоджуюсь. Спадкування завжди створює копію публічного інтерфейсу класу у його підкласах. Це не стосунки "є". Це субтипізація, звичайна та проста, а тому невідповідна описаному застосуванню.
Жуль

@Jules З чим ти не згоден? Я сказала багато речей, які демонструються, і зробила висновки, які логічно випливають. Ви перебуваєте неправильно , коли ви говорите, «Спадкування завжди створює копію відкритого інтерфейсу класу в його підкласів.» У Python копії немає - методи дивіться динамічно відповідно до порядку роздільної здатності методу алгоритму C3 (MRO).
Аарон Холл

1
Суть не в конкретних деталях того, як працює реалізація, а в тому, як виглядає публічний інтерфейс класу. У випадку прикладу Userоб’єкти мають у своїх інтерфейсах не тільки члени, визначені у Userкласі, але й ті, що визначені у UserServiceта LoggingService. Це не стосунки "has-a", оскільки публічний інтерфейс копіюється (хоча і не шляхом прямого копіювання, а скоріше шляхом непрямого пошуку на інтерфейси суперклассів).
Жуль

Має-засіб Склад. Міксини - це форма Склад. Клас користувача є НЕ UserService або LoggingService, але має таку функціональність. Я думаю, що спадщина Python сильніше відрізняється від Java, ніж ви розумієте.
Аарон Холл

@AaronHall Ви надто спрощуєте (це, як правило, суперечить вам іншій відповіді , яку я знайшов випадково). З точки зору взаємозв'язку підтипу, Користувач є і UserService, і LoggingService. Тепер дух полягає у складанні функціональних можливостей, щоб Користувач мав такі і такі функціональні можливості. Міксини взагалі не потребують реалізації з багатократним успадкуванням. Однак це звичайний спосіб робити це в Python.
coredump

-1

Крім того, що він дозволяє кілька суперклассів, успадкування Python істотно не відрізняється від Java, тобто члени підкласу також є членами кожного з їх супертипів [1]. Факт, що Python використовує типізацію качок, також не має значення: у вашому підкласі є всі члени його суперклассів, тому він може бути використаний будь-яким кодом, який міг би використовувати ці підкласи. Те, що багатократне успадкування ефективно реалізується за допомогою складу - це червона оселедець: проблема автоматизованого копіювання властивостей одного класу в інший, і не має значення, чи він використовує склад, чи просто магічно здогадується, як належать члени працювати: неправильно мати їх

Так, це порушує єдину відповідальність, тому що ви надаєте своїм об'єктам можливість виконувати дії, які логічно не входять до того, що вони розроблені. Так, він створює об'єкти "бога", що є по суті іншим способом сказати те саме.

При проектуванні об'єктно-орієнтованих систем у Python застосовується та сама максима, яку проповідують книги дизайну Java: віддайте перевагу композиції перед успадкуванням. Те саме стосується (більшості [2]) інших систем з багаторазовим успадкуванням.

[1]: ви можете назвати, що відносини "є-а", хоча мені цей термін особисто не подобається, тому що він пропонує ідею моделювання реального світу, а об'єктно-орієнтоване моделювання не те саме, що реальне.

[2]: Я не дуже впевнений у C ++. C ++ підтримує "приватне успадкування", яке по суті є складом без необхідності вказувати ім'я поля, коли потрібно використовувати публічні члени спадкового класу. Це взагалі не впливає на публічний інтерфейс класу. Мені не подобається його використовувати, але я не бачу жодної вагомої причини цього не робити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.