Сколько слишком много интерфейсов в классе? [закрыто]


28

Вероятно, я бы посчитал запахом кода или даже анти-паттерном иметь класс, реализующий 23 интерфейса. Если это действительно анти-паттерн, как бы вы это назвали? Или просто не следует принципу Единой ответственности?


3
Ну, это сомнительно в любом случае. Если у класса есть 23 метода, которые все выполняют одну ответственность (не всем классам нужно так много методов, но это не невозможно - особенно если большинство из них представляют собой удобные обертки с тремя строками вокруг других методов), у вас просто слишком детализированные интерфейсы; )

Рассматриваемый класс является своего рода сущностью, а интерфейсы в основном связаны со свойствами. Есть 113 из них и только четыре метода.
Йонас Эльфстрем

6
Я думал, что интерфейсы в основном используются для типизации утилит в статически компилируемых языках В чем проблема? :)
ashes999

2
просто вопрос: все 113 свойств заполнены для каждого экземпляра этой сущности? Или это какая-то «общая сущность», используемая в разных контекстах с заполненными только некоторыми свойствами?
Дэвид

1
Давайте вернемся к концептуальному пониманию того, что представляет собой «класс». Класс - это отдельная вещь с четко определенной ответственностью и ролью. Можете ли вы определить свой 23-интерфейсный объект таким образом? Можете ли вы опубликовать одно (не Joycean) предложение, которое адекватно суммирует ваш класс? Если так, то 23 интерфейса в порядке. Если нет, то он слишком большой, слишком сложный.
Стивен Гросс

Ответы:



23

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


2
Как насчет швейцарского армейского ножа?
FrustratedWithFormsDesigner

Кажется, что этот термин уже используется для интерфейсов со слишком многими методами :-) Хотя это также имеет смысл и здесь.
Джалайн

1
@FrustratedWithFormsDesigner У швейцарского армейского ножа никогда не было бы плохого вкуса иметь интерфейс с именем IValue, который содержит 30 свойств, из которых 20 предшествует ключевое слово нового модификатора.
Йонас Эльфстрем

3
+1 за слишком много шляп ... чтобы правильно спрятать многоголового божества
ньютопист

1
Швейцарский армейский нож - это образец повторного использования. т. е. один «клинок» может выполнять любые методы, так что это, вероятно, плохая аналогия!
Джеймс Андерсон

17

Если бы мне пришлось дать ему имя, я бы назвал его Гидрой :

гидра

В древнегреческой мифологии Лернейская гидра (греч. Λερναία Ὕδρα) была древним безымянным змеиным хтоническим водным зверем с чертами рептилий (как следует из его названия), у которых было много голов - поэты упоминали больше голов, чем художники-вазы. краски, и на каждую отрубленную голову росло еще два - и ядовитое дыхание было настолько яростным, что даже ее следы были смертельными.

Особое значение имеет тот факт, что у него не только много голов, но и растет их все больше и из-за этого невозможно убить. Это был мой опыт с этими видами конструкций; разработчики просто вбивают в него все больше и больше интерфейсов, пока их не будет слишком много, чтобы поместиться на экране, и к тому времени она настолько глубоко укоренится в дизайне программы и предположениях, что ее разделение - безнадежная перспектива (если вы попытаетесь, вы на самом деле часто нужно больше интерфейсов для преодоления разрыва).

Одним из ранних признаков надвигающейся гибели из-за «Гидры» является частое приведение одного интерфейса к другому, без особой проверки работоспособности, и часто к цели, которая не имеет смысла, как в:

public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
    var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
    return ((IAssembler)locator).BuildWidget(partsNeeded);
}

Очевидно, когда вырвано из контекста, в этом коде есть что-то подозрительное, но вероятность того, что это произойдет, возрастает с увеличением числа «голов» объекта, потому что разработчики интуитивно знают, что они всегда имеют дело с одним и тем же существом.

Успехи когда - либо делать какое - либо техническое обслуживание на объекте , который реализует 23 интерфейсов. Вероятность того, что вы не нанесете побочный ущерб в процессе, невелика.


16

Бог Объект приходит на ум; один объект, который знает, как делать все. Это происходит из-за низкой приверженности требованиям «сплоченности» двух основных методологий проектирования; если у вас есть объект с 23 интерфейсами, у вас есть объект, который знает, как быть 23 разными вещами для своих пользователей, и эти 23 разные вещи, вероятно, не соответствуют линии одной задачи или области системы.

В SOLID, хотя создатель этого объекта, очевидно, пытался следовать принципу разделения интерфейса, он нарушил предыдущее правило; Принцип единой ответственности. Есть причина, по которой это «ТВЕРДЫЙ»; S всегда стоит на первом месте, думая о дизайне, и все остальные правила следуют.

В GRASP создатель такого класса пренебрег правилом «Высокой сплоченности»; GRASP, в отличие от SOLID, учит, что объект не ДОЛЖЕН иметь единственную ответственность, но в большинстве случаев он должен иметь две или три очень тесно связанные обязанности.


Я не уверен, что это анти-паттерн «Объект Бога», поскольку Бог не ограничен никакими интерфейсами. Ограниченность таким количеством интерфейсов может уменьшить всемогущество Бога. ;) Может быть, это Химерный Объект?
FrustratedWithFormsDesigner

У Бога много лиц, и он может быть многим для многих людей. Если функциональность объединенных интерфейсов делает объект «всезнающим», то это на самом деле не ограничивает Бога, не так ли?
KeithS

1
Если только то, что создало интерфейсы, не изменяет их. Тогда Бог, возможно, придется переопределить, чтобы соответствовать изменениям интерфейса.
FrustratedWithFormsDesigner

3
Больно, что ты думаешь, что это я создал класс.
Йонас Эльфстрем

Употребление «вы» означает больше вас во множественном числе, имея в виду вашу команду разработчиков. Кто-то в этой команде, в прошлом или настоящем, создал этот класс. Я буду редактировать, хотя.
KeithS

8

23 - это просто число! В наиболее вероятном сценарии это достаточно высоко, чтобы встревожить. Однако, если мы спросим, ​​каково наибольшее количество методов / интерфейсов, прежде чем он сможет получить тег для вызова в качестве «анти-паттерна»? Это 5 или 10 или 25? Вы понимаете, что число на самом деле не является ответом, потому что если 10 хорошо, 11 также может быть - и затем любое целое число после этого.

Настоящий вопрос о сложности. И мы должны отметить, что длинный код, количество методов или большой размер класса по любым показателям на самом деле НЕ являются определением сложности. Да, все больший и больший код (большее количество методов) затрудняет чтение и восприятие нового новичка. Он также обрабатывает потенциально различную функциональность, большое количество исключений и довольно развитые алгоритмы для различных сценариев. Это не значит, что это сложно - трудно только переварить.

С другой стороны, код сравнительно небольшого размера, который можно надеяться прочитать через несколько часов, может быть сложным. Вот когда я думаю, что код (излишне) сложен.

Любая мудрость объектно-ориентированного дизайна может быть заложена здесь, чтобы определить «сложный», но я бы ограничился здесь, чтобы показать, когда «так много методов» является признаком сложности.

  1. Взаимное Знание. (иначе говоря) Во многих случаях, когда вещи пишутся как классы, мы все думаем, что это «хороший» объектно-ориентированный код. Но предположение о другом классе существенно нарушает действительно необходимую инкапсуляцию. Когда у вас есть методы, которые «пропускают» глубокие подробности о внутреннем состоянии алгоритмов, - и приложение строится с ключевым предположением о внутреннем состоянии класса обслуживания.

  2. Слишком много повторений (взлом) Когда методы, которые имеют схожие имена, но выполняют противоречивую работу - или противоречат именам со схожей функциональностью. Много раз коды развиваются, чтобы поддерживать немного разные интерфейсы для разных приложений.

  3. Слишком много ролей Когда класс продолжает добавлять побочные функции и продолжает расширяться по мере того, как людям это нравится, нужно только знать, что класс действительно теперь является двумя классами. Удивительно, но все это начинается с подлинноготребования и никакой другой класс не существует для этого. Учтите это, есть класс Transaction, который сообщает подробности транзакции. Пока все выглядит хорошо, теперь кому-то требуется преобразование формата в «время транзакции» (между UTC и т. Д.), Позже люди добавляют правила, чтобы проверить, была ли определенная вещь в определенные даты, чтобы проверить недействительные транзакции. - Я не буду писать всю историю, но в конце концов класс транзакций будет включать в себя весь календарь, и тогда люди начнут использовать его только из одного календаря! Это очень сложно (представить), почему я буду создавать экземпляр "класса транзакции", чтобы иметь функциональность, которую мне предоставил бы "календарь"!

  4. (В) согласованность API Когда я делаю book_a_ticket () - я бронирую билет! Это очень просто, независимо от того, сколько проверок и процессов идет, чтобы это произошло. Теперь это становится сложным, когда поток времени начинает влиять на это. Как правило, можно было бы разрешить «поиск» и возможный доступный / недоступный статус, затем, чтобы уменьшить количество возвратов, вы начинаете сохранять некоторые контекстные указатели внутри заявки, а затем ссылаетесь на нее , чтобы забронировать билет. Поиск - не единственная функциональность - после многих таких «побочных функций» дела ухудшаются. В процессе значение book_a_ticket () подразумевает book_that_ticket ()! и это может быть невообразимо сложным.

Вероятно, есть много таких ситуаций, которые вы бы увидели в очень развитом коде, и я уверен, что многие люди могут добавлять сценарии, где «так много методов» просто не имеет смысла или не делает то, что вы, очевидно, думаете. Это анти-паттерн.

Мой личный опыт показывает, что, когда проекты, которые начинаются разумно снизу вверх, многие вещи, для которых должны были существовать подлинные классы сами по себе - остаются похороненными или, что еще хуже, остаются разделенными между различными классами, и увеличивается сцепление. Чаще всего то, что заслуживало бы 10 классов, но имеет только 4, вполне вероятно, что многие из них имеют многоцелевые запутанные и большое количество методов. Назовите это БОГОМ и ДРАКОНОМ, это то, что ПЛОХО.

НО вы встречаете ОЧЕНЬ БОЛЬШИЕ классы, которые аккуратно согласованы, у них есть 30 методов - и все еще очень ЧИСТЫЕ. Они могут быть хорошими.


Класс, о котором идет речь, имеет 23 интерфейса, и, в общем, содержит 113 открытых свойств и четыре метода. Только некоторые из них доступны только для чтения. C # имеет автоматически реализуемые свойства, поэтому я различаю методы и свойства msdn.microsoft.com/en-us/library/bb384054.aspx
Йонас Эльфстрем

7

Классы должны иметь точно правильное количество интерфейсов; Ни больше ни меньше.

Сказать «слишком много» было бы невозможно без рассмотрения того, служат ли все эти интерфейсы полезной цели в этом классе. Объявление о том, что объект реализует интерфейс, означает, что вы должны реализовать его методы, если ожидаете, что класс скомпилируется. (Я предполагаю, что класс, на который вы смотрите, это делает.) Если все в интерфейсе реализовано, и эти реализации делают что-то относительно внутренностей класса, было бы трудно сказать, что реализации не должно быть. Единственный случай, когда я могу вспомнить, где интерфейс, отвечающий этим критериям, не будет там, - это внешний: когда его никто не использует.

Некоторые языки позволяют «подклассить» интерфейсы с помощью механизма, подобного extendsключевому слову Java , и тот, кто его написал, может не знать об этом. Возможно также, что все 23 достаточно обширны, и их объединение не имело смысла.


Я полагал, что этот вопрос был довольно независимым от языка. Фактический код находится в C #.
Йонас Эльфстрем

Я не делал C #, но, похоже, он поддерживает аналогичную форму наследования интерфейса.
Blrfl

Да, интерфейс может «наследовать» другие интерфейсы и так далее.
Йонас Эльфстрем

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

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

1

Звучит так, как будто у них есть пара «getter» / «setter» для каждого свойства, как обычно для типичного «bean» и продвигает все эти методы в интерфейс. Так как насчет того, чтобы называть это " бобом "? Или более рабелевская «метеоризм» после хорошо известного воздействия слишком большого количества бобов.


Он находится в C # и имеет автоматически реализованные свойства. Всего эти 23 интерфейса содержат 113 таких свойств.
Йонас Эльфстрем

1

Количество интерфейсов часто растет, когда универсальный объект используется в разных средах. В C # варианты IComparable, IEqualityComparer и IComparer допускают сортировку в разных установках, поэтому вы можете в конечном итоге реализовать все из них, некоторые из них могут быть несколько раз, поскольку вы можете реализовать универсальные строго типизированные версии, а также неуниверсальные версии. Кроме того, вы можете реализовать несколько обобщений.

Давайте рассмотрим пример сценария, скажем, интернет-магазин, в котором вы можете купить кредиты, с помощью которых вы можете купить что-то другое (стоковые фото-сайты часто используют эту схему). У вас может быть класс "Valuta" и класс "Credits", которые наследуют одну и ту же базу. Valuta имеет несколько изящных перегрузок операторов и процедур сравнения, которые позволяют выполнять вычисления, не беспокоясь о свойстве «Валюта» (например, добавление фунтов к долларам). Кредиты намного проще, но имеют другое отличное поведение. Желая иметь возможность сравнивать их друг с другом, вы можете в конечном итоге реализовать IComparable, а также IComparable и другие варианты интерфейсов сравнения на обоих (хотя они используют общую реализацию, будь то в базовом классе или где-то еще).

При реализации сериализации реализуются ISerializable, IDeserializationCallback. Затем реализуем стеки отмены и повторения: добавлен IClonable. Функциональность IsDirty: IObservable, INotifyPropertyChanged. Разрешение пользователям редактировать значения, используя строки: IConvertable ... Список можно продолжать и продолжать ...

В современных языках мы видим другую тенденцию, которая помогает разделить эти аспекты и поместить их в свои собственные классы, вне основного класса. Внешний класс или аспект затем связывается с целевым классом с помощью аннотации (атрибутов). Часто можно сделать классы внешних аспектов более или менее общими.

Использование атрибутов (аннотации) подлежит рефлексии. Один недостаток - незначительная (начальная) потеря производительности. Недостаток (часто эмоциональный) заключается в том, что такие принципы, как инкапсуляция, должны быть смягчены.

Всегда есть другие решения, но для каждого изящного решения есть компромисс или подвох. Например, использование решения ORM может потребовать объявления всех свойств виртуальными. Решения сериализации могут потребовать конструкторов по умолчанию на ваших классах. Если вы используете внедрение зависимостей, вы можете реализовать 23 интерфейса в одном классе.

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

Всякий раз, когда вы переключаете работу или вынуждены строить на существующей архитектуре. Мой совет - сначала полностью познакомиться, не пытайтесь все слишком быстро реорганизовать. Послушайте оригинального разработчика (если он все еще там) и постарайтесь выяснить мысли и идеи, стоящие за тем, что вы видите. Задавая вопросы, делайте это не ради того, чтобы разбить его, а чтобы узнать ... Да, у каждого есть свои золотые молотки, но чем больше молотков вы сможете собрать, тем легче вам будет ладить с коллегами.


0

«Слишком много» субъективно: стиль программирования? спектакль? соответствие стандартам? Прецеденты? просто чувство комфорта / уверенности?

Пока ваш код ведет себя правильно и не вызывает проблем с обслуживаемостью, 23 может даже стать новой нормой. Однажды я мог бы сказать: «Вы можете сделать отличную работу с 23 интерфейсами, см. Jonas Elfström».


0

Я бы сказал, что предел должен быть меньше, чем 23 - больше как 5 или 7,
однако это не включает в себя любое количество интерфейсов, которые наследуют эти интерфейсы, или любое количество интерфейсов, реализованных базовыми классами.

(Таким образом, всего N + любое количество унаследованных интерфейсов, где N <= 7.)

Если ваш класс реализует слишком много интерфейсов, это, вероятно, божественный класс .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.