Каков наилучший способ работы со многими интерфейсами? - PullRequest
10 голосов
/ 20 февраля 2012

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

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

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

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

I сейчасесть две опции:

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

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

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

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

Вторая использует своего рода «специальную» типизацию.И Oracle 1045 * иногда натыкается на них, в то время как Eclipse делает это правильно.

Последнее решение использует приведение типов.Nuff сказал.

Вопросы:

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

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

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

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

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

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

Когда я нахожусь в коде для добавления потомка в менеджере дерева ревизий, я знаю, что каждый экземпляр должен реализовывать 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, но кажется компактным и полезным.Любые другие варианты / комментарии?

Ответы [ 5 ]

3 голосов
/ 20 февраля 2012

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

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

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

3 голосов
/ 20 февраля 2012

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

void doSomething(IA & ID & IE thing);

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

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

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

2 голосов
/ 20 февраля 2012

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

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

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

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

Отредактировано

ОК.Так как ваша дополнительная информация была добавлена, это начинает иметь смысл.Наилучшим подходом здесь является использование обратного вызова: вместо передачи родительского объекта, передавайте интерфейс, который принимает «потомок».

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

Ваш код будет выглядетьчто-то вроде этого (предостережение: может не скомпилироваться; я просто набрал его):

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);
    }
}

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

0 голосов
/ 20 февраля 2012

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

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

doSomethingWithFoo(WhatGoesHere foo);

вы делаете:

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

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

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

0 голосов
/ 20 февраля 2012

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...