Как спроектировать инфраструктуру расширяемого типа с зависимостями друг от друга

Мое приложение представляет собой редактор для подключения «модулей» (через порты модулей). Порты имеют типы портов. Каждый тип порта имеет свой компаратор. Два типа совместимы, если их атрибуты удовлетворяют правилам, реализованным в компараторе.

Я хочу, чтобы пользователи расширяли приложение, внедряя новые типы портов и правила их подключения (через инфраструктуру точек расширения eclipse).

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

Я реализовал эти два интерфейса:

public interface IPortType {
    [many attributes]
}
public interface IComparator {
    public boolean isCompatible(IPortType source, IPortType target);

    // more interaction methods!
}

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

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

Конкретный пример компаратора:

public class ATypeComparator implements IComparator {

    public boolean isCompatible(IPortType source, IPortType target) {
        if (!(source instanceof AType))
            return false;
        if (target instanceof BType)
            return isCompatible(source, (BType) target);
        if (target instanceof CType)
            return isCompatible(source, (CType) target);
    }

    public boolean isCompatible(AType source, BType target) {...}
    public boolean isCompatible(AType source, CType target) {...}
}

Как бы вы решили эту проблему?

Спасибо за любые предложения!


person DEAD10CC    schedule 09.01.2010    source источник


Ответы (2)


Я не думаю, что реализация IPortType должна решать, совместима ли она с другими реализациями IPortType. Это просто не входит в его обязанности.

Простым решением было бы создать один общедоступный статический метод, например, в классе PortTypeManager, который знает, совместимы ли две реализации IPortType. Таким образом, вы всегда можете добавить новый тип, и вам нужно изменить логику только в одном месте, чтобы приспособить этот новый тип.

Однако в итоге и этого будет недостаточно, потому что количество случаев, которые должен охватывать метод, растет как n^2. Вам необходимо снабдить каждую реализацию IPortType методом, подобным getVersion() или getSignature(), который возвращает часть данных, которую вы можете сравнить с аналогичной частью данных, чтобы решить, совместимы ли две реализации.

person Confusion    schedule 09.01.2010
comment
Я согласен, что проверка совместимости не входит в обязанности типа. Это также приводит ко многим местам для внесения изменений при появлении новых типов. Правила совместимости зависят от подмножества атрибутов, которые может иметь тип. Есть комбинаторный и арифметический тест, чтобы решить. Общая структура данных, которая обрабатывает эти операции, может привести к реализации другого языка программирования. Но ваша идея с подписью весьма интересна... - person DEAD10CC; 09.01.2010

Казалось бы, вашу реализацию можно было бы исправить, если бы вы позволили полиморфизму справиться со сложностями.

Не проще ли было бы иметь метод .compatibleTo() на IPortType? Если бы вы могли сделать это, каждая реализация могла бы изначально знать, какие конечные точки она может поддерживать?

Что-то вроде:

IPortType port1 = ...
IPortType port2 = ...

if (port.compatibleTo(port2)) {
    // do whatever
}
person gpampara    schedule 09.01.2010
comment
Я согласен, что это решение менее сложное, но оно приводит к изменениям во всех типах, совместимых друг с другом. И нет возможности изменить код существующих типов портов, поскольку философия eclipse заключается в расширении, а не в изменении. Также правила совместимости должны быть отделены от типа порта, так как он не обязан знать, что с ним можно делать. - person DEAD10CC; 09.01.2010
comment
Является ли совместимость рефлексивной? Если port1 совместимо с port2, то должно ли быть так, что port2 совместимо с port1? - person seh; 09.01.2010
comment
Он должен быть рефлексивным. В любом случае, это, кажется, вопрос перспективы. Если добавление новой конкретной реализации интерфейса приводит к большему объему работы, чем можно было бы ожидать, обычно можно с уверенностью предположить, что в коде присутствует запах, и некоторый рефакторинг, возможно, будет хорошей идеей. - person gpampara; 09.01.2010
comment
Да, я думаю, что совместимость здесь рефлексивна. Но в худшем случае все еще есть правила O (n ^ 2) для n типов. - person DEAD10CC; 10.01.2010