Reputation: 6394
Let's consider this example:
class InheritedType extends Type {
public Type simplify() {
Type newLeft = left.simplify();
Type newRight = right.simplify();
Type newExpr = new InheritedType(newLeft, newRight);
return newExpr.simplify();
}
}
Now I'd create another inherited type
class InheritedType2 extends Type {
public Type simplify() {
Type newLeft = left.simplify();
Type newRight = right.simplify();
Type newExpr = new InheritedType2(newLeft, newRight);
return newExpr.simplify();
}
}
This method is just the same, only the constructor name differs. Is there any way to avoid code duplication? The following code doesn't work
class Type {
public Type simplify() {
Type newLeft = left.simplify();
Type newRight = right.simplify();
Type newExpr = new this(newLeft, newRight);
return newExpr.simplify();
}
}
Upvotes: 2
Views: 84
Reputation: 137064
The typical way around this problem is to introduce a common superclass for InheritedType
and InheritedType2
. This is because, clearly, those two types share logic, mainly: they operate both on left and right operands. So let us create a LeftRightType
(for lack of a better name):
abstract class LeftRightType extends Type {
private Type left, right;
public LeftRightType(Type left, Type right) {
this.left = left;
this.right = right;
}
@Override
public Type simplify() {
return newInstance(left.simplify(), right.simplify()).simplify();
}
public abstract Type newInstance(Type left, Type right);
}
It holds the logic you are currently duplicating and delegate the creation of the concrete instance into a newInstance
abstract method that implementors will override. Then you can simply have
class InheritedType extends LeftRightType {
public InheritedType(Type left, Type right) {
super(left, right);
}
@Override
public Type newInstance(Type left, Type right) {
return new InheritedType(left, right);
}
}
class InheritedType2 extends LeftRightType {
public InheritedType2(Type left, Type right) {
super(left, right);
}
@Override
public Type newInstance(Type left, Type right) {
return new InheritedType2(left, right);
}
}
Note that if you're using Java 8, you can condense that a lot by giving the concrete class to return directly in the constructor. You don't even need an abstract method anymore.
abstract class LeftRightType extends Type {
private Type left, right;
private BinaryOperator<Type> typeSupplier;
public LeftRightType(Type left, Type right, BinaryOperator<Type> typeSupplier) {
this.left = left;
this.right = right;
this.typeSupplier = typeSupplier;
}
public Type simplify() {
return typeSupplier.apply(left.simplify(), right.simplify()).simplify();
}
}
and then have
class InheritedType extends LeftRightType {
public InheritedType(Type left, Type right) {
super(left, right, InheritedType::new);
}
}
class InheritedType2 extends LeftRightType {
public InheritedType2(Type left, Type right) {
super(left, right, InheritedType2::new);
}
}
Upvotes: 4
Reputation: 316
You can you "curiously recurring template pattern" https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern:
abstract class Type<This extends Type<This>> {
protected abstract This newInstance(Type<?> newLeft, Type<?> newRight);
public This simplify() {
Type<?> newLeft = left.simplify();
Type<?> newRight = right.simplify();
This newExpr = newInstance(newLeft, newRight);
return newExpr.simplify();
}
}
class InheritedType extends Type<InheritedType> {
protected InheritedType newInstance(Type<?> left, Type<?> right) {
new InheritedType(left, right);
}
}
class InheritedType2 extends Type<InheritedType2> {
protected InheritedType2 newInstance(Type<?> left, Type<?> right) {
new InheritedType2(left, right);
}
}
Upvotes: 2