Frage

I am playing with using generics to be able to restrict sub-types in a composite hierarchy, so that I could force different types at different layers of a hierarchy.

For example having a (Business:Division:Department:Group:Person) hierarchy, where the composite nodes at each level would only accept child nodes of the proper type (the next lower level in the hierarchy). So I would construct the levels in the composite using generics at each level type instantiated to accept only nodes from the next lower level.

But I get an error with the generics, and am not sure if it indicates a design error, or just something that java Generics can't do for me. The idea seems valid, I just want to restrict the types accepted by the add() methods to only accept a sub-type from the specific next level in the hierarchy to force the levels of structure. Since I am just lowering the upper-bound on the type at each stage, all messages sent to the list would still be valid.

Top level node in the typical GOF-Composite pattern:

   public abstract class Business { ... }

Composite node hierarchy top level:

  public abstract class Group extends Business {

    protected List<Business> subs;      // composite links
    public Group add(Business comp) {
        subs.add(comp);
        return this;
    }

Subsequent levels in the composite hierarchy:

 public class Area<T extends Business> extends Group {

    protected List<Business> subs;      // composite links

    public Area(String title){
        super(title);
        subs = new ArrayList<Business>();
    }

    @Override
    public Group add(T comp) {    // ** Error Here **
        super.add(comp);
        return this;
    }

The Error is:

 Multiple markers at this line
    - The method add(T) of type Area<T> must override a superclass method

- Name clash: The method add(T) of type Area has the same erasure as add(Business) of type Group but does not override it

I tried a variation where I gave a similar type to the Group::add() method so they would have the same type signatures,

public Group add(T comp) { ... }

But that fails similarly:

 Multiple markers at this line

- The method add(T) of type Area must override a superclass method - overrides labs.composite.company.Group.add - Name clash: The method add(T) of type Area has the same erasure as add(T) of type Group but does not override it

Am I missing something here??? TIA

PS: Actually I think that this use of generics does not do exactly what I want anyway (even if it did work!), since i want to not only change the upper-bound at each level, but require a specific single level in the type hierarchy, not any covariant argument type. I really would want something like, "for any type T which is a sub-type of Composite, accept only that type of object in the add() method", I think that instead the generics says "accept any object as an argument which is a subtype of Composite". I suppose that is impossible since arguments in Java are covariant, and LSP will always allow sub-types to be used.

War es hilfreich?

Lösung

If your question is why the error, the reason is simple: T in Area can be an infinitude of Business subclasses, while Business in Group is a very specific class. Both methods are not the same. The compiler works mainly with full types while performing type checking (even if it also considers erasures for certain kind of warnings and special cases). That's the whole point of generics. Otherwise, it's better to revert to pre-generics code style (which is still supported).

If not, then I don't understand the question. But let me add a few more comments:

1) Inheritance describes a "is a kind of" relationship, not a "has" or "is composed of" relationship. In the code, you are saying that a Group is a kind of Business, and that an Area is a kind of Group. To me, it's much more natural to think that a Group belongs to a Business (i.e. a Business has Groups). Same thing with Groups and Areas. None of these two relationships are of inheritance, but of composition.

2) When in class Group<T extends Business> you define method add(T comp) and in class Area<T extends Business> you define method add(T comp), you say Group::add and Area:add have the same signature. That's not correct. The generic parameter T in Group is completely independent of the T in Area. Why? Assume that B1 and B2 are subclasses of Bussiness, but not of each other. Nobody can say that Area<B1>:add() and Group<B2>:add() have the same signature. In fact, the only case when this equivalence holds is when the generic argument is the same (i.e. Area<B1> and Group<B1>). Java can not consider the signatures equivalent when that equivalency holds only in a few particular cases (that are not otherwise described by the code).

3) GOF's Composite design pattern does not apply to this case because it does not represent hierarchical composition, but what we could call "unrestricted" composition. According to this pattern, aComposite can contain differentComponents, but these Components can be any kind of Composites, regardless of how high or low in a class hierarchy. What you want is differentComponents to be of the immediately lower class. No more, and no less.

I don't remember to have seen this case as a design pattern. Probably because it's too simple. See the last variant in next point 4).

4) Your code should probably take the following form:

public class Grouping<C,T> {
    C curr;
    List<T> subs;
    public C add(T comp) {
        this.subs.add(comp);
        return this.curr ;
    }
}

public class Business extends Grouping<Business,Group> {
    // Grouping::add does not need to be overriden
}
public class Group extends Grouping<Group,Area> {
    // Grouping::add does not need to be overriden
}

And so on. If you allow method add to return void instead of C, you can eliminate a generic parameter in all classes:

public class Grouping<T> {
    List<T> subs;
    public void add(T comp) {
        this.subs.add(comp);
    }
}

public class Business extends Grouping<Group> {
    // Grouping::add does not need to be overriden
}
public class Group extends Grouping<Area> {
    // Grouping::add does not need to be overriden
}

And if you want to make it really simple (but perhaps not as powerful):

public class Business {
    List<Group> subs;
    public Business add(Group comp) {
        this.subs.add(comp);
        return this ;
    }
}
public class Group {
    List<Area> subs;
    public Group add(Area comp) {
        this.subs.add(comp);
        return this ;
    }
}

This creates code duplication (method add in every class) that under more realistic scenarios could be much higher (you could also want methods count, list, retrieve, etc).

Andere Tipps

Overriding is when you have the same signature, your signature is different so it is not a valid override. Your method signature should be.

public Group add(Business comp)

  1. Since the T has to extend business anyway I can't see why this is a problem, if it is just get rid of the override annotation.

  2. It's possible you may be confusing overriding with overloading, which is when the signatures differ but the name is the same.

  3. technically the @Override annotation has no functionality, it is only there to catch programmer error, so if it doesn't compile just get rid of it

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top