Разделить большие интерфейсы

9

Я использую большой интерфейс с около 50 методов для доступа к базе данных. Интерфейс был написан моим коллегой. Мы обсуждали это:

Я: 50 методов это слишком много. Это кодовый запах.
Коллега: что мне с этим делать? Вы хотите доступ к БД - у вас есть.
Я: Да, но это неясно и вряд ли будет исправимо в будущем.
Коллега: хорошо, вы правы, это не приятно. Как должен выглядеть интерфейс?
Я: Как насчет 5 методов, которые возвращают объекты, которые имеют, например, 10 методов каждый?

Ммм, но разве это не будет так же? Это действительно приводит к большей ясности? Стоит ли усилий?

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


Обновление (в ответ на комментарий Сюаня):

«Методы»: это интерфейс для извлечения данных из базы данных. Все методы имеют вид (псевдокод)

List<Typename> createTablenameList()

Методы и таблицы не находятся точно в отношении 1-1, акцент делается больше на том факте, что вы всегда получаете какой-то список, который поступает из базы данных.

TobiMcNamobi
источник
12
Отсутствует соответствующая информация (какие у вас есть методы). В любом случае, я думаю: если вы делите только на число, то ваш коллега прав, вы ничего не улучшаете. Один возможный критерий деления был бы возвращен «сущностью» (почти эквивалентом таблицы) (так, a UserDaoи a CustomerDaoи a ProductDao)
SJuan76
Действительно, некоторые таблицы семантически близки к другим таблицам, образующим «клики». Как и методы.
TobiMcNamobi
Можно ли увидеть код? Я знаю, что это 4 года, и вы, вероятно, уже исправили это: D Но я хотел бы подумать об этой проблеме. Я решил что-то подобное раньше.
clankill3r
@ clankill3r Действительно, у меня больше нет доступа к конкретному интерфейсу, который заставил меня опубликовать вопрос выше. Проблема более общая, хотя. Представьте себе БД с примерно 50 таблицами и для каждой таблицы такой метод, какList<WeatherDataRecord> createWeatherDataTable() {db.open(); return db.select("*", "tbl_weatherData");}
TobiMcNamobi

Ответы:

16

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

Не существует единого шаблона для его исправления, но принцип, который описывает желаемое состояние, - это принцип разделения интерфейса («I» в SOLID), который гласит, что ни одного клиента не следует заставлять зависеть от методов, которые он не использует. ,

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

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

Карл Билефельдт
источник
Это и ответ утнапистим действительно велики.
TobiMcNamobi
13

Коллега: хорошо, вы правы, это не приятно. Как должен выглядеть интерфейс?

Я: Как насчет 5 методов, которые возвращают объекты, которые имеют, например, 10 методов каждый?

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

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

Ммм, но разве это не будет так же? Это действительно приводит к большей ясности? Стоит ли усилий?

Если вы выбираете правильные критерии, безусловно. Если нет, то определенно нет :).

Несколько примеров:

  • посмотрите на объекты ADODB для упрощенного примера примитивов OO (ваш DB API, вероятно, уже предлагает это)

  • посмотрите на модель данных Django ( https://docs.djangoproject.com/en/dev/topics/db/models/ ) для идеи модели данных с высоким уровнем абстракции (в C ++ вам, вероятно, понадобится немного больше котла код, но это хорошая идея). Эта реализация разработана с учетом роли «модели» в схеме проектирования MVC.

  • посмотрите на API sqlite, чтобы получить представление об плоском API ( http://www.sqlite.org/c3ref/funclist.html ), состоящем только из функциональных примитивов (C API).

utnapistim
источник
3

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

Это анти-шаблон дизайна, называемый монолитным классом . Наличие 50 методов в классе или интерфейсе является вероятным нарушением SRP . Монолитный класс возникает потому, что он пытается быть всем для всех.

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

Как насчет 5 методов, которые возвращают объекты, которые имеют около 10 методов каждый?

Это предполагает создание всех ролей, когда создается сам объект. Но зачем создавать роли, которые вам могут не понадобиться? Вместо этого создайте экземпляр роли в контексте, в котором она вам действительно нужна.

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

РЕДАКТИРОВАТЬ: мое мышление по этому вопросу изменилось. Я дал альтернативный ответ.

Марио Т. Ланца
источник
1

Мне кажется, каждый ответ не имеет смысла. Дело в том, что в идеале интерфейс должен определять элементарный фрагмент поведения. Это Я в ТВЕРДОМ.

У класса должна быть одна ответственность, но она может включать в себя несколько вариантов поведения. Чтобы придерживаться типичного объекта клиента базы данных, это может предложить полную функциональность CRUD. Это было бы четыре поведения: создавать, читать, обновлять и удалять. В чистом мире SOLID клиент базы данных будет реализовывать не IDatabaseClient, а неустановившиеся ICreator, IReader, IUpdater и IDeleter.

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

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

Так что да, 50 методов - это запах, но это зависит от намерения и цели, плохо это или нет. Это, конечно, не идеально.

Мартин Маат
источник
0

Уровни доступа к данным, как правило, имеют множество методов, привязанных к одному классу. Если вы когда-либо работали с Entity Framework или другими инструментами ORM, вы увидите, что они генерируют сотни методов. Я полагаю, что вы и ваш коллега выполняете это вручную. Нет необходимости пахнуть кодом, но на это не приятно смотреть. Не зная вашего домена, сложно сказать.

Роман Мик
источник
Методы или свойства?
Джеффо
0

Я использую протоколы (назовите их интерфейсами, если хотите) почти универсально для всех API с FP и OOP. (Помните Матрицу? Конкреций нет!) Конечно, существуют конкретные типы, но в рамках программы каждый тип рассматривается как нечто, играющее роль в некотором контексте.

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

Я предполагаю, что вполне возможно, что сложный объект может выдерживать множество различных протоколов, но вам все равно будет сложно получить API с 50 методами. Большинство протоколов имеют 1 или 2 метода, возможно 3, но никогда не 50! Любая сущность, имеющая 50 методов, должна быть объединена в группу более мелких компонентов, каждый из которых имеет свои обязанности. Сущность в целом представила бы более простой интерфейс, который абстрагирует общую сумму apis в нем.

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

Марио Т. Ланца
источник