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).
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.