Нарушает ли множественное наследование принцип единой ответственности?

18

Если у вас есть класс, который наследует от двух разных классов, не означает ли это, что ваш подкласс автоматически делает (как минимум) 2 вещи, по одной от каждого суперкласса?

Я считаю, что нет разницы, если у вас есть несколько интерфейсов наследования.

Изменить: Чтобы было ясно, я считаю, что если подклассы нескольких классов нарушают SRP, то реализация нескольких интерфейсов (не маркерный или базовый интерфейс (например, Comparable)) также нарушает SRP.

m3th0dman
источник
Просто чтобы быть ясно, вы также считаете, что реализация нескольких интерфейсов нарушает SRP?
Я считаю, что если наследование нескольких классов нарушает SRP, то реализация нескольких (не маркерных) интерфейсов также нарушает SRP.
m3th0dman
Я бы поддержал этот вопрос, но вы включили в него свое мнение, с которым я не согласен.
Николь
1
@NickC: я не сказал свое мнение (вы должны были прочитать вышеупомянутый комментарий); Я отредактирую вопрос, чтобы сделать его более понятным.
m3th0dman
1
@NickC У меня нет мнения, поэтому я и спросил. Я только сказал, что оба они связаны (наследование интерфейса и наследование классов); если он нарушает наследование класса, то он нарушает и наследование интерфейса; если это не относится к одному, то и к другому не нарушается. И мне плевать на голосование против ...
m3th0dman

Ответы:

17

В очень узком смысле ответ «Да»: если предположить, что ваши базовые классы или интерфейсы предназначены для одной цели, наследование их обоих создает класс с множеством обязанностей. Однако, является ли это "плохой вещью", зависит от природы классов или интерфейсов, которые вы наследуете.

Вы можете разделить ваши классы и интерфейсы на две основные группы: те, которые касаются существенной сложности вашей системы, и те, которые касаются ее случайной сложности. Если наследовать от нескольких классов "существенной сложности", это плохо; если вы наследуете один «обязательный» и один или несколько «случайных» классов, это нормально.

Например, в биллинговой системе у вас могут быть классы для представления счетов и циклов выставления счетов (они касаются существенной сложности) и классы для сохраняющихся объектов (они обращаются к случайной сложности). Если вы наследуете так

class BillingCycleInvoice : public BillingCycle, public Invoice {
};

это плохо: на вас BillingCycleInvoiceлежит смешанная ответственность, поскольку она связана с существенной сложностью системы.

С другой стороны, если вы наследуете так

class PersistentInvoice : public Invoice, public PersistentObject {
};

с вашим классом все в порядке: технически он обслуживает две проблемы одновременно, но, поскольку только одна из них важна, вы можете списать наследование случайной как «затраты на ведение бизнеса».

dasblinkenlight
источник
3
Ваш второй пример очень похож на то, что было бы сквозной проблемой в аспектно-ориентированном программировании. Это говорит о том, что ваши объекты иногда должны выполнять такие действия (как ведение журнала, тестирование контроля доступа или сохранение), которые не являются их основной задачей, и что вы можете делать это с множественным наследованием. Это правильное использование инструмента.
Самое простое: если реализация нового интерфейса требует завершения функциональности класса, то это не так. Но если он добавляет новый интерфейс
реализует
9

SRP - это руководство, позволяющее избегать божьих занятий, которые являются плохими, но его также можно воспринимать слишком буквально, и проект запускает воздушный шар с кучей классов, которые действительно ничего не могут сделать без объединения нескольких вещей. Множественное наследование / множественные интерфейсы могут быть признаком того, что класс становится слишком большим, но это также может быть признаком того, что ваш фокус слишком гранулирован для выполняемой задачи и ваше решение перегружено, или оно может быть просто идеальным действительный вариант использования для множественного наследования и ваши опасения необоснованны.

Разработка программного обеспечения - это столько же искусство, сколько наука, это уравновешивание многих конкурирующих интересов. У нас есть правила, помогающие избежать того, что, как мы знаем, далеко не в одном направлении, что вызывает проблемы, но эти правила могут так же легко привести нас в такое же плохое или плохое место, как мы и пытались избежать в первую очередь.

Ryathal
источник
4

В некотором роде. Давайте сосредоточимся на основном принципе для SRP:

У класса должна быть только одна причина измениться.

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

Места, где множественное наследование не нарушает SRP, - это когда все, кроме одного из базовых типов, не являются чем-то, что реализует класс, а действуют как черта, которой обладает класс. Рассмотрим IDisposableв C #. Вещи, которые являются одноразовыми, не имеют двух обязанностей: интерфейс просто выступает в качестве представления для классов, которые разделяют эту черту, но имеют совершенно разные обязанности.

Telastyn
источник
2

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

Это не значит, что у класса три обязанности. Это означает, что часть ответственности за моделирование пользователя требует поддержки сериализации и постоянства.

Кевин Клайн
источник
2

Не за что. В Java у вас может быть интерфейс, представляющий некоторый вид предметного объекта - например, Foo. Возможно, его нужно будет сравнивать с другими объектами Foo, и поэтому он реализует Comparable (с логикой сравнения, экстернализованной в классе Comparator); может случиться так, что этот объект также должен быть Сериализуемым, чтобы его можно было транспортировать по сети; и это, возможно, должно быть Клонируемым. Таким образом, класс реализует три интерфейса, но не имеет никакой логики для их поддержки, кроме той, которая поддерживается Foo.

Тем не менее, глупо доводить SRP до крайности. Если класс определяет более одного метода, нарушает ли он SRP? Все объекты Java имеют метод toString () и метод equals (Object), что означает, что КАЖДЫЙ объект в Java отвечает как за равенство, так и за самопредставление. Это плохо?

Мэтью Флинн
источник
1

Я думаю, это зависит от того, как вы определяете ответственность. В классическом примере iostream это не нарушает SRP. IOstream отвечает за управление одним двунаправленным потоком.

После того, как вы исчерпали примитивные обязанности, вы переходите к более общим обязанностям высокого уровня, в конце концов, как вы определяете ответственность своей основной точки входа? Его обязанность должна заключаться в том, чтобы быть «главной точкой входа», а не «всем, что делает мое приложение», иначе невозможно не нарушить SRP и создать приложение.

stonemetal
источник