Каков наилучший способ работы со многими интерфейсами?

У меня есть ситуация, когда у меня есть много классов моделей (~ 1000), которые реализуют любое количество 5 интерфейсов. Поэтому у меня есть классы, которые реализуют один и другие, которые реализуют четыре или пять.

Это означает, что я могу иметь любую перестановку этих пяти интерфейсов. В классической модели мне пришлось бы реализовать 32-5 = 27 "meta interfaces", которые "соединяются" с интерфейсами в комплекте. Часто это не проблема, потому что IB обычно расширяет IA и т.д., Но в моем случае пять интерфейсов ортогональны/независимы.

В моем коде кода у меня есть методы, которым нужны экземпляры, в которых реализовано любое количество этих интерфейсов. Поэтому давайте предположим, что у нас есть класс X и интерфейсы IA, IB, IC, ID и IE. X реализует IA, ID и IE.

Ситуация ухудшается, потому что некоторые из этих интерфейсов имеют формальные параметры типа.

Теперь у меня есть два варианта:

  • Я мог бы определить интерфейс IADE (или, скорее, IPersistable_MasterSlaveCapable_XmlIdentifierProvider; подчеркивает только ваше удовольствие от чтения)

  • Я мог бы определить общий тип как <T extends IPersistable & IMasterSlaveCapable & IXmlIdentifierProvider>, который дал бы мне удобный способ смешивания и сопоставления интерфейсов по мере необходимости.

  • Я мог бы использовать такой код: IA a = ...; ID d = (ID)a; IE e = (IE)e, а затем использовать локальную переменную с правильным типом для вызова методов, даже если все три работают в одном экземпляре. Или используйте бросок в каждом вызове второго метода.

Первое решение означает, что я получаю много пустых интерфейсов с очень нечитаемыми именами.

Второй использует своего рода "ad-hoc" типизацию. И Oracle javac иногда натыкается на них, а Eclipse получает это право.

Последнее решение использует броски. Сказал Нафф.

Вопросы:

  • Есть ли лучшее решение для смешивания любого количества интерфейсов?

  • Есть ли причины, чтобы избежать временных типов, которые предлагает решение №2 (за исключением недостатков в Oracle javac)?

Примечание. Я знаю, что писать код, который не компилируется с Oracle javac, является риском. Мы знаем, что мы можем справиться с этим риском.

[Редактировать] Кажется, что я пытаюсь попытаться здесь смутить. Мои экземпляры модели могут иметь одну из следующих черт:

  • Они могут быть "мастер-ведомыми" (думаю, клонирование)
  • Они могут иметь XML-идентификатор
  • Они могут поддерживать операции дерева (parent/child)
  • Они могут поддерживать версии
  • и т.д.. (да, модель еще сложнее)

Теперь у меня есть код поддержки, который работает на деревьях. Расширения деревьев - деревья с ревизиями. Но у меня также есть ревизии без деревьев.

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

Но в реализации мне нужно вызвать методы на узлах дерева:

public void addChild( T parent, T child ) {
    T newRev = parent.createNewRevision();
    newRev.addChild( foo );
    ... possibly more method calls to other interfaces ...
}

Если createNewRevision находится в интерфейсе IRevisionable и addChild находится в интерфейсе ITree, каковы мои параметры для определения T?

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

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

public
<T extends IRevisionable & ITree>
void addChild( T parent, T child ) { ... }

Это не всегда работает с Oracle javac, но кажется компактным и полезным. Любые другие варианты/комментарии?

Ответ 1

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

IA ia = x.lookupCapability(IA.class);
if (ia != null) {
    ia.a();
}

Он подходит здесь, как и для многих интерфейсов, желание разделить уровни, и вы можете более легко комбинировать случаи взаимозависимых интерфейсов (if (ia != null && ib != null) ...).

Ответ 2

Если у вас есть метод (полукод)

void doSomething(IA & ID & IE thing);

тогда моя главная проблема: не может ли doSomething быть лучше адаптирован? Может быть, лучше разделить функциональность? Или сами интерфейсы плохо адаптированы?

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

Поскольку вы сформулировали свой вопрос очень абстрактно (т.е. без разумного примера), я не могу сказать, насколько это целесообразно и в вашем случае.

Ответ 3

Я бы избегал всех "искусственных" интерфейсов/типов, которые пытаются представить комбинации. Это просто плохой дизайн... что произойдет, если вы добавите еще 5 интерфейсов? Число комбинаций взрывается.

Кажется, вы хотите знать, реализует ли какой-либо экземпляр какой-либо интерфейс (-ы). Возможные варианты:

  • use instanceof - нет стыда
  • использовать отражение, чтобы обнаружить интерфейсы через object.getClass().getInterfaces() - вы можете написать некоторый общий код для обработки файлов
  • используйте отражение, чтобы обнаружить методы с помощью object.getClass().getMethods() и просто вызовите те, которые соответствуют известному списку методов ваших интерфейсов (этот подход означает, что вам не нужно заботиться о том, что он реализует), звучит просто и, следовательно, звучит как хороший идея)

Вы не указали нам, почему именно вы хотите знать, поэтому трудно сказать, что такое "лучший" подход.

Edited

OK. Поскольку добавлена ​​дополнительная информация, это начинает иметь смысл. Лучшим подходом здесь является использование обратного вызова: вместо передачи в родительском объекте передайте интерфейс, который принимает "дочерний".

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

Ваш код будет выглядеть примерно так (caveat: May не компилируется, я просто набрал его):

public interface Parent<T> {
    void accept(T child);
}

// Central code - I assume the parent is passed in somewhere earlier
public void process(Parent<T> parent) {
    // some logic that decides to add a child
    addChild(parent, child);
}

public void addChild(Parent<T> parent, T child ) {
    parent.accept(child);
}

// Calling code
final IRevisionable revisionable = ...;
someServer.process(new Parent<T> {
    void accept(T child) {
        T newRev = revisionable.createNewRevision();
        newRev.addChild(child);
    }
}

Возможно, вам придется манипулировать вещами, но я надеюсь, вы понимаете, что я пытаюсь сказать.

Ответ 4

На самом деле решение 1 является хорошим решением, но вы должны найти лучшее именование.

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

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

Ответ 5

Моя ситуация противоположная: я знаю, что в определенный момент кода, foo должен реализовать IA, ID и IE (в противном случае он не смог бы получить это далеко). Теперь мне нужно вызвать методы во всех трех интерфейсах. Какой тип должен ли foo получить?

Можете ли вы полностью обойти проблему, передав (например) три объекта? Поэтому вместо:

doSomethingWithFoo(WhatGoesHere foo);

:

doSomethingWithFoo(IA foo, ID foo, IE foo);

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

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