Существует ли принцип интерфейса «спросите только то, что вам нужно»?

9

Я перешел на использование принципа проектирования и использования интерфейсов, который гласит: «просите только то, что вам нужно».

Например, если у меня есть куча типов, которые можно удалить, я сделаю Deletableинтерфейс:

interface Deletable {
   void delete();
}

Тогда я могу написать общий класс:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

В другом месте кода я всегда буду требовать минимальной ответственности за выполнение требований клиентского кода. Так что, если мне нужно только удалить File, я все равно буду просить Deletable, а не File.

Является ли этот принцип общеизвестным и уже принятым? Является ли это спорно? Это обсуждается в учебниках?

glenviewjeff
источник
1
Слабая связь может быть? Или узкие интерфейсы?
tdammers

Ответы:

16

Я действительно считаю, что это относится к тому, что Роберт Мартин называет принципом разделения интерфейсов . Интерфейсы разделены на маленькие и сжатые, так что потребители (клиенты) должны будут знать только о методах, которые им интересны. Вы можете проверить больше на SOLID .

Вадим
источник
4

Для того, чтобы расширить свое присутствие на очень хороший ответ Вадима, я ответить на «это спорный» вопрос с «нет, на самом деле не».

В целом, сегрегация интерфейса - это хорошая вещь, поскольку сокращается общее количество «причин для изменения» различных задействованных объектов. Основной принцип заключается в том, что когда интерфейс с несколькими методами должен быть изменен, скажем, для добавления параметра к одному из методов интерфейса, тогда все потребители интерфейса должны по крайней мере перекомпилироваться, даже если они не использовали измененный метод, «Но это просто перекомпиляция!», Слышу, вы говорите; это может быть правдой, но имейте в виду, что, как правило, все, что вы перекомпилируете, должно быть удалено как часть программного патча, независимо от того, насколько значительным является изменение в двоичном файле. Эти правила были изначально концептуализированы еще в начале 90-х годов, когда средняя настольная рабочая станция была менее мощной, чем телефон в вашем кармане, 14,4 Кбод было просто невероятно, а 3,5 "1,44 МБ" дискеты "были основными сменными носителями. Даже в нынешнюю эпоху 3G / 4G пользователи беспроводного Интернета часто имеют тарифные планы с ограничениями, поэтому при выпуске обновления меньше загружаемых двоичных файлов, тем лучше.

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

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

Разделение интерфейса должно основываться на классах, которые зависят от интерфейса:

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

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

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

Keiths
источник
Стоит также отметить, что сегрегация интерфейса может быть хорошей и изящной, если использовать язык / систему ООП, которая позволяет коду указывать точную комбинацию интерфейсов, но, по крайней мере, в .NET они могут вызывать некоторые серьезные головные боли, так как не существует достойных способ указания коллекции «вещей, которые реализуют IFoo и IBar, но в противном случае могут не иметь ничего общего».
суперкат
Параметры общего типа могут быть определены с помощью критериев, включая реализацию нескольких интерфейсов, но вы правы в том, что выражения, требующие статического типа, обычно не могут поддерживать указание более одного. Если есть необходимость, чтобы статический тип реализовывал и IFoo, и IBar, и вы управляете обоими этими интерфейсами, это может быть хорошей идеей для реализации IBaz : IFoo, IBarи требовать этого вместо этого.
KeithS
Если клиентскому коду может понадобиться что-то, что можно использовать как IFooи IBar, определение составного IFooBarможет быть хорошей идеей, но если интерфейсы тонко разделены, то в конечном итоге потребуется десятки различных типов интерфейсов. Рассмотрим следующие коллекции функций, которые могут иметь: перечислить, подсчитать число, прочитать n-й элемент, записать n-й элемент, вставить перед n-ным элементом, удалить n-й элемент, новый элемент (увеличить коллекцию и вернуть индекс нового пространства) и добавить. Девять методов: ECRWIDNA. Я мог бы описать десятки типов, которые, естественно, поддерживали бы множество различных комбинаций.
суперкат
Например, массивы будут поддерживать ECRW. Arraylist будет поддерживать ECRWIDNA. Потоково-безопасный список может поддерживать ECRWNA [хотя A обычно полезен только для предварительного заполнения списка]. Оболочка массива только для чтения может поддерживать ECR. Интерфейс ковариантного списка может поддерживать ECRD. Неуниверсальный интерфейс может обеспечить безопасную поддержку типов C или CD. Если бы был вариант Swap, некоторые типы могли бы поддерживать CS, но не D (например, массивы), в то время как другие поддерживали бы CDS. Попытка определить различные типы интерфейса для каждой необходимой комбинации способностей была бы кошмаром.
суперкат
Теперь представьте, что вам нужна возможность обернуть коллекцию объектом, который может делать все, что может делать коллекция, но который регистрирует каждую транзакцию. Сколько оберток понадобится? Если бы все коллекции унаследовали от общего интерфейса, который включал свойства для определения их возможностей, одной оболочки было бы достаточно. Однако, если все интерфейсы различны, потребуются десятки.
суперкат