Search code examples
javaconstructorcompositionextendingconstructor-injection

How do you extend a class using composition which requires a more specialized constructor argument?


I am trying to extend classes using composition where possible. But I am having trouble with a particular problem.

Lets say I have a Base class and an Extending class. The Extending class takes in an instance of the Base class rather than inheriting from it if we are using composition and we want to inject our dependencies:

public class Base
{
    private ISomeDependency dependency;

    public Base(ISomeDependency dependency)
    {
        this.dependency = dependency;
    }

    public ISomeDependency getDependency()
    {
        return dependency;
    }
}
public class Extending
{
    private Base base;

    public Extending(Base base)
    {
        this.base = base;
    }
}

The Base class takes in some dependency which we can then use in the Extending class using the getDependency() method.

However, what if we want to specialize what subtype of ISomeDependency we need in the extending class.

If we were using inheritance we could simply pass the subtype into the superclass constructor like so:

public class Extending extends Base
{
    private ISomeDependencySubtype dependency;

    public Extending(ISomeDependencySubtype dependency)
    {
        super(dependency);
        this.dependency = dependency;
    }
}

But if we are using composition then this becomes more difficult. I have thought of a number of options:

Having an initialization method instead of a constructor in Base

public class Extending
{
    private ISomeDependencySubtype dependency;
    private Base base;

    public Extending(Base base, ISomeDependencySubtype dependency)
    {
        base.init(dependency);
        this.base = base;
        this.dependency = dependency;
    }
}

This option hides the fact that the Extending class uses the init() method on the Base class so the interface becomes perhaps unclear. Upon using the Extending class another programmer might assume that the Base instance must have init() called on it before it is passed into the Extending instance.

Adding a generic argument

public class Base<T extends ISomeDependency> 
{
    private T dependency;

    public Base(T dependency)
    {
        this.dependency = dependency;
    }

    public T getDependency()
    {
        return dependency;
    }
}

public class Extending
{
    private ISomeDependencySubtype dependency;
    private Base<ISomeDependencySubtype> base;

    public Extending(Base<ISomeDependencySubtype> base)
    {
        this.base = base;
        this.dependency = base.getDependency();
    }
}

This seems like an abuse of generics. If the Base class is a class in its own right and can be used without the Extending class then it shouldn't need to care which subtype of ISomeDependency is passed to it. The generic argument would only exist for this specific scenario despite everything needing to be refactored to accommodate it.

Instantiating Base inside of Extending

public class Extending
{
    private ISomeDependencySubtype dependency;
    private Base base;

    public Extending(ISomeDependencySubtype dependency)
    {
        this.base = new Base(dependency);
        this.dependency = dependency;
    }
}

This means that the Base instance cannot be passed in which means the dependency cannot be injected. This is inflexible for reasons which are probably obvious to most.

So I would like to know what people think is the most preferable option using composition to overcome this problem (no inheritance based answers please).


Solution

  • This – like all problems with covariant dependencies – is a somewhat pathological problem and there likely is no perfect solution.

    In addition to your solutions, you could also pass only the type of the Base and an instance of ISomeDependency to Extending's constructor and instantiate the Base object dynamically.

    public class Extending {
    
        // Keep an additional copy (ugly and probably unsafe).
        final dependendcy dependendcy;
        final Base base;
    
        public Extending(final Class<? extends Base> cls,
                         final ISomeDependencySubtype dependendcy) throws Exception {
            this.dependency = dependency;
            this.base = cls.getConstructor(ISomeDependency.class).newInstance(dependency);
        }
    }
    

    This has the obvious disadvantage that you have to assume an appropriate constructor of the Base which cannot be enforced statically by Java's type system.

    If you prefer, you could also assume a default constructor (might seem an easier requirement) and then invoke an init method with the dependency. [I don't like init methods.] This way, you will remove the ambiguity whether the Base should be initialized by the caller.

    All solutions that rely on keeping an additional reference to the dependency are unsafe in my opinion since there is no guarantee that the Base will not replace the object so

    assert this.dependency == this.base.getDependency();
    

    might well fail. Of course, it isn't any safer to downcast the reference returned by getDependency since for the same reason,

    assert this.base.getDependency() instanceof ISomeDependencySubtype;
    

    might fail as well.

    Even if Extending extends Base (which you say you don't want), this problem isn't getting any better.

    The generics approach can solve this issue but it buys you all other kinds of problems.

    Since I don't think a perfect solution exists, it probably depends much on the actual use-case which evil to accept.