Чи багатократне успадкування порушує принцип єдиної відповідальності?


18

Якщо у вас є клас, який успадковує два різних класи, чи це не означає, що ваш підклас автоматично робить (принаймні) 2 речі, по одному з кожного надкласу?

Я вважаю, що немає різниці, якщо у вас є спадковість декількох інтерфейсів.

Редагування: Щоб зрозуміти, я вважаю, що якщо підкласифікація декількох класів порушує SRP, то реалізація декількох інтерфейсів (без маркера або базового інтерфейсу (наприклад, Порівнюваний)) порушує і SRP.


Щоб було зрозуміло, ви також вважаєте, що впровадження декількох інтерфейсів порушує SRP?

Я вважаю, що якщо підкласифікація декількох класів порушує SRP, то реалізація декількох (без маркерних) інтерфейсів порушує і SRP.
m3th0dman

Я б підтримав це питання, але ви вклали свою думку у питання, з чим я не згоден.
Ніколь

1
@NickC: я не сказав своєї думки (ви повинні прочитати вищевказаний коментар); Я відредагую питання, щоб зробити його більш зрозумілим.
m3th0dman

1
@NickC У мене немає думки, тому я запитав. Я лише сказав, що два пов'язані між собою (успадкування інтерфейсу та успадкування класів); якщо він порушує успадкування класу, то він порушує і успадкування інтерфейсу, якщо він не для одного, то і для іншого не порушує. І мені не байдуже голосування вгору ...
m3th0dman

Відповіді:


17

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

Ви можете розділити свої класи та інтерфейси на дві основні групи - ті, що стосуються суттєвої складності вашої системи, і ті, що стосуються її випадкової складності. Якщо успадковувати з декількох класів «суттєвої складності», це погано; якщо ви успадковуєте один з "основних" і один або більше "випадкових" класів, це нормально.

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

class BillingCycleInvoice : public BillingCycle, public Invoice {
};

це погано: ваша BillingCycleInvoiceвідповідальність є неоднозначною, оскільки це стосується суттєвої складності системи.

З іншого боку, якщо ти наслідуєш так

class PersistentInvoice : public Invoice, public PersistentObject {
};

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


3
Ваш другий приклад дуже схожий на те, що викликало б наскрізну проблему в аспектно-орієнтованому програмуванні. Це говорить про те, що вашим об'єктам іноді потрібно робити такі речі (як ведення журналів, тести контролю доступу чи постійні), які не є їх основною діяльністю, і що ви можете робити це з багаторазовим успадкуванням. Це правильне використання інструменту.

Найпростіше, якщо впровадження нового інтерфейсу вимагає для завершення функціональності класу, то його немає. Але якщо його додавання нової відповідальності інтерфейс впровадження з яким-небудь іншим класом, то ін'єкція як залежність ніколи не порушить SRP
Ramankingdom

9

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

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


4

Дещо. Давайте зупинимось на головному принципі SRP:

Клас повинен мати лише одну причину зміни.

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

Місця, де багатократне успадковування не порушує SRP, - це коли всі, крім одного з базових типів, не те, що клас реалізує, а виступають як риса, яка має місце в класі. Розглянемо IDisposableв C #. Одноразові речі не несуть двох обов'язків, інтерфейс просто виконує роль класів, які мають спільні риси, але несуть рішуче різні обов'язки.


2

Не по-моєму. Для мене одна відповідальність - це "модель користувача моєї програми". Можливо, мені доведеться доставити ці дані через веб-сервіс і зберегти їх у реляційній базі даних. Я міг би забезпечити цю функціональність, успадкувавши пару класів змішання.

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


2

Зовсім ні. У Java ви можете мати інтерфейс, який представляє, наприклад, якийсь об’єкт домену - Foo, наприклад. Можливо, його потрібно буде порівнювати з іншими об'єктами Foo, і тому він реалізує Порівняльне (з логікою порівняння, зовнішньою у класі порівняння); можливо, цей об’єкт також повинен бути серіалізаційним, щоб його можна було перенести через мережу; і може знадобитися клонувати. Таким чином, клас реалізує три інтерфейси, але не має ніякої логіки для того, щоб підтримувати їх, крім того, який підтримує Foo.

Однак це дурно сприймати СРП до крайнощів. Якщо клас визначає більше одного методу, чи це порушення SRP? Усі об'єкти Java мають метод toString () та метод рівний (Object), що означає, КОЖЕН об’єкт у Java відповідає за рівність та саморепрезентацію. Це погано?


1

Я думаю, це залежить від того, як ви визначаєте відповідальність. У класичному прикладі iostream він не порушує SRP. IOstream відповідає за управління одним двонаправленим потоком.

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

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