2016-05-06 5 views
6

Я должен разработать интерфейс для иерархической сущности:Проектирование интерфейса для иерархической сущности

interface HierarchicalEntity<T extends HierarchicalEntity<T>> { 
    T getParent(); 
    Stream<T> getAncestors(); 
} 

Это довольно легко реализовать по умолчанию методgetAncestors() с точки зрения getParent() таким образом, что бывший вернется Stream из все предки.

Пример реализации:

default Stream<T> getAncestors() { 
    Stream.Builder<T> parentsBuilder = Stream.builder(); 
    T parent = getParent(); 
    while (parent != null) { 
     parentsBuilder.add(parent); 
     parent = parent.getParent(); 
    } 
    return parentsBuilder.build(); 
} 

Но мне нужно также включать this в поток, и здесь возникает проблема. Следующая строка не является правильным, потому что this имеет тип HierarchicalEntity, не T:

parentsBuilder.add(this); // type mismatch! 

Как я могу переделать интерфейс для того, чтобы сделать getAncestors() включать this в результате?

+5

К сожалению, это никогда не будет полностью безопасным для типа. Java не имеет синтаксиса типа self-referential. Я могу создать «класс Fake реализует HierarchicalEntity », и ваш 'getAncestors', по-видимому, с ошибкой ClassCastException. –

+0

Понял. Возможно, какой-то редизайн можно применить ко всему интерфейсу. – Aliaxander

+0

Просто введите 'this' в' T' и добавьте его в построитель потока –

ответ

1

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

Конечно, вы можете выполнить непроверенный листинг this до T, если вы уверены, что все подтипы будут выполнять это ограничение. Но вы должны выполнить этот непроверенный бросок, когда вам нужна ссылка this как T.

Лучшее решение заключается в добавлении абстрактного метода как

/** 
    All subtypes should implement this as: 

    public T myself() { 
     return this; 
    } 
*/ 
public abstract T myself(); 

Затем вы можете использовать myself() вместо this всякий раз, когда вам нужно самореференцию как T.

default Stream<T> getAncestors() { 
    Stream.Builder<T> parentsBuilder = Stream.builder(); 
    for(T node = myself(); node != null; node = node.getParent()) { 
     parentsBuilder.add(parent); 
    } 
    return parentsBuilder.build(); 
} 

Конечно, вы не можете применять, что подклассы правильно реализовать myself() как return this;, но, по крайней мере, вы можете легко проверить, делают ли они во время выполнения:

assert this == myself(); 

Эта ссылка сравнение очень дешевая работа и, если myself() правильно реализована, так как неизменно возвращается this, HotSpot может заранее доказать, что это сравнение всегда будет true и полностью выполнить проверку.

Недостатком является то, что каждая специализация должна иметь эту избыточную реализацию myself() { return this; }, но, с другой стороны, она полностью свободна от непроверенных типов. Альтернативой является наличие объявления abstractmyself() в базовом классе как @SuppressWarnings("unchecked") T myself() { return (T)this; }, чтобы ограничить непроверенную операцию одним местом для иерархии типов. Но тогда вы не можете проверить, действительно ли this типа T ...

1

Добавление this не работает, потому что HierarchicalEntity<T> необязательно является T; это может быть неизвестный подтип. Тем не менее, T всегда HierarchicalEntity<T>, как вы это заявили.

Изменить тип возвращаемого getAncestors и в Stream.Builder от T к HierarchicalEntity<T>, что позволит вам добавить this.

default Stream<HierarchicalEntity<T>> getAncestors() { 
    Stream.Builder<HierarchicalEntity<T>> parentsBuilder = Stream.builder(); 
    T parent = getParent(); 
    while (parent != null) { 
     parentsBuilder.add(parent); 
     parent = parent.getParent(); 
    } 
    parentsBuilder.add(this); 
    return parentsBuilder.build(); 
} 

Вы можете объявить getParent вернуть HierarchicalEntity<T> также для обеспечения согласованности.

+0

Но OP хочет поток 'T'. – shmosel

+0

@shmosel Это текущий дизайн интерфейса, но нет такого явного выражения OP. – rgettman

+0

Фактический тип @rgettman для 'T' должен иметь некоторые методы, которые не определены в' HierarchicalEntity', поэтому код типа 'getAncestors(). Map (ActualType :: foo)' становится недействительным. – Aliaxander

2

Как сказал @SotiriosDelimanolis, нет возможности полностью обеспечить соблюдение этого. Но если вы готовы взять на себя интерфейс используется как задумано, то можно предположить, что this является экземпляром T и просто брось:

parentsBuilder.add((T)this); 

Если вы хотите, чтобы избежать литья, вы можете добавить метод должны быть переопределены в подклассе:

interface HierarchicalEntity<T extends HierarchicalEntity<T>> { 
    T getParent(); 
    T getThis(); 
    default Stream<T> getAncestors() { 
     // ... 
     parentsBuilder.add(getThis()); 
     // ... 
    } 
} 

class Foo extends HierarchicalEntity<Foo> { 
    // ... 
    @Override 
    public Foo getThis() { 
     return this; 
    } 
} 

Теперь мы можем получить в типизированном образе this, но нет никакой гарантии, что getThis() правильно реализован. Он может вернуть любой экземпляр Foo. Итак, выберите яд, я думаю.

+0

Путь с 'getThis()' кажется адекватным, но все еще неудобным ... – Aliaxander

+0

@Aliaxander Если это вариант использования общего абстрактного класса, вы можете создать конструктор 'AbstractEntity (T thisObj)' и иметь подклассы call 'super (это) ', поэтому вам не нужно реализовывать' getThis() 'для каждого подкласса. Все еще не изящно, но я не знаю, есть ли элегантное решение. – shmosel

+1

Вы не можете использовать 'this' в явном вызове конструктора, см. Http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.7.1 – Alex

Смежные вопросы