Search code examples
javacomposition

Composition with different types


I have two Java classes B and C in which both extend from class A (I do not own class A).

public class B extends A {…}
public class C extends A {…}

I need a list of type „ListItem“ to hold instances of either B or C, but since both (B and C) are already extended, they cannot be further extended by using „ListItem“ as a superclass.

I think the only way to go is with composition („has-a“ relation…).

Since „ListItem“ should never have B AND C (but only one of them), I am planning to create ListItem with 2 constructors and set an appropriated inner type to reflect if either B or C is being hold.

Is there a better approach to accomplish this? Please see the pseudo code below.

public class ListItem {

    private enum elemType {
        TYPE_B, TYPE_C 
    } 

    private final B mBItem;
    private final C mCItem;
    private final elemType mType;

    // used to hold instance of B
    public ListItem(B b) {
        this.mBItem = b;
        this.mType = TYPE_B;
    } 

    // used to hold instance of C
    public ListItem(C c) {
        this.mCItem = c;
        this.mType = TYPE_C;
    }

    // sample method to demonstrate delegation
    private int getAge() {
        int age;

        if (containsB()) {
            age = mBItem.getAge();
        }
        else if (containsC()) {
            age = mCItem.getAge();
        }
        else {…}
        return age;
    }

    private boolean containsB() {
        return (mType == TYPE_B);    
    }

    private boolean containsC() {
        return (mType == TYPE_C);
    }
}

Solution

  • I mean, you have:

    public class B extends A {…}
    public class C extends A {…}
    

    So you could just make your item hold an A:

    public class ListItem {
    
        private final A item;
    
        public ListItem (A item) { 
            this.item = item; 
        }
    
    }
    

    And you have any number of options on how to handle it from there, e.g. simply:

        public A getItem () {
            return item;
        }
    

    And do any appropriate casts or tests from the caller. That's the most flexible way. Or I suppose something like:

        public B getB () {
            return item instanceof B ? (B)item : null;
        }
    
        public C getC () {
            return item instanceof C ? (C)item : null;
        }
    

    But that's starting to get sloppy, and limits you from storing any other A-derived stuff in your ListItem.

    You might want to start questioning your design here. For example, if you're adding some functionality to A that is common to B and C and necessary for the proper operation of a ListItem, consider deriving some class from A to add the common stuff, derive B and C from that, and make your ListItem store that base (of course, if you do this you can't store an A any more):

    public class AgedA extends A {
        ...
        public int getAge () { return ... }
    }
    
    // Then your B and C both extend AgedA, and your ListItem stores an
    // AgedA and can use getAge().
    

    You could make AgedA be abstract and getAge() be virtual, if that's more appropriate. Or you could declare an interface that defines getAge() and use that.

    Yet another option is you could (as pointed out in comments) make ListItem be either an interface, or a class that extends A, then have your B and C implement or extend that.

    I suppose you could also use generics for ListItem e.g. <? extends A>, although that may not be appropriate especially if your ListItems are mixed. The major benefit of using generics is that e.g. getters can now directly return the subtypes without casts and you give yourself compile-time ability to restrict types to a specific subtype in function parameters and stuff, but otherwise it's essentially the same as just storing an A.

    In any case, aside from the advice to just store A in ListItem and handle different types at a higher level, I can't in good conscience give you any other approach suggestions, because the need for all of this seems questionable to me.