Designing interface for hierarchical entity

*爱你&永不变心* 提交于 2019-12-19 06:24:48

问题


I have to design an interface for hierarchical entity:

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

It's quite easy to implement default getAncestors() method in terms of getParent() in such a way that the former would return Stream of all the ancestors.

Implementation example:

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

But I need to also include this into the stream, and here a problem appears. The following line is not correct because this is of type HierarchicalEntity, not T:

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

How can I redesign the interface in order to make getAncestors() include this into the result?


回答1:


It’s a recurring problem when creating self-referential types. In the base type (or interface), you can’t enforce that this will be assignment compatible with T.

Of course, you can perform an unchecked cast of this to T if you are confident that all subtypes will fulfill that constraint. But you have to perform this unchecked cast whenever you need a this reference as T.

The better solution is to add an abstract method like

/**
    All subtypes should implement this as:

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

Then, you can use myself() instead of this whenever you need a self-reference as 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();
}

Of course, you can’t enforce that subclasses correctly implement myself() as return this;, but at least, you can easily verify whether they do at runtime:

assert this == myself();

This reference comparison is a very cheap operation and, if myself() is correctly implemented as invariably returning this, HotSpot can prove in advance that this comparison will always be true and elide the check completely.

The drawback is that each specialization will have to have this redundant implementation of myself() { return this; }, but on the other hand, it’s completely free of unchecked type casts. The alternative is to have an non-abstract declaration of myself() in the base class as @SuppressWarnings("unchecked") T myself() { return (T)this; } to limit the unchecked operation to a single place for the type hierarchy. But then, you can’t verify whether this really is of type T




回答2:


As @SotiriosDelimanolis said, there's no way fully enforce this. But if you're willing to assume the interface is being used as designed, you can assume that this is an instance of T and simply cast it:

parentsBuilder.add((T)this);

If you want to avoid casting, you can add a method to be overridden in the subclass:

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

Now we can get this in a typesafe manner, but there's no guarantee that getThis() has been implemented correctly. It's possible for it to return any instance of Foo. So, pick your poison I guess.




回答3:


Adding this fails because a HierarchicalEntity<T> is not necessarily a T; it could be an unknown subtype. However, a T is always a HierarchicalEntity<T> as you've declared it that way.

Change the return type of getAncestors and of the Stream.Builder from T to HierarchicalEntity<T>, which will allow you to add 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();
}

You may wish to declare getParent to return a HierarchicalEntity<T> also for consistency.



来源:https://stackoverflow.com/questions/37078814/designing-interface-for-hierarchical-entity

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!