问题描述
我有一个对象层次结构,随着继承树的加深,复杂性会增加.这些都不是抽象的,因此,它们的所有实例或多或少都具有成熟的目的.
由于参数的数量很高,因此我想使用构建器模式来设置属性,而不是代码多个构造函数.由于我需要迎合所有排列,因此继承树中的叶子类将具有望远镜的构造函数.
当我在设计过程中遇到一些问题时,我在这里浏览了一个答案.首先,让我给你一个简单,浅的例子来说明问题.
public class Rabbit { public String sex; public String name; public Rabbit(Builder builder) { sex = builder.sex; name = builder.name; } public static class Builder { protected String sex; protected String name; public Builder() { } public Builder sex(String sex) { this.sex = sex; return this; } public Builder name(String name) { this.name = name; return this; } public Rabbit build() { return new Rabbit(this); } } } public class Lop extends Rabbit { public float earLength; public String furColour; public Lop(LopBuilder builder) { super(builder); this.earLength = builder.earLength; this.furColour = builder.furColour; } public static class LopBuilder extends Rabbit.Builder { protected float earLength; protected String furColour; public LopBuilder() { } public Builder earLength(float length) { this.earLength = length; return this; } public Builder furColour(String colour) { this.furColour = colour; return this; } public Lop build() { return new Lop(this); } } }
现在我们有一些代码可以继续,我想构建Lop:
Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);
此调用不会编译,因为无法解决最后的链式呼叫,Builder未定义方法earLength.因此,这样需要将所有调用都以特定顺序链接,这是非常不切实际的,尤其是在深层层次中.
现在,在搜索答案时,我遇到了子分类java Builder类建议使用
我不知道我的问题还有另一种解决方案吗?最好是与设计模式一致的解决方案.谢谢! 递归界限肯定是可能的,但是子类型的构建器也需要是通用的,并且您需要一些临时抽象类.有点麻烦,但仍然比非生成版本容易. 有一种方法可以避免使用"混凝土"叶类,如果我们有这样的话: 然后,您需要用钻石创建新实例,并在参考类型中使用通配符: 之所以起作用,是因为类型变量上的绑定可确保所有方法的所有方法RabbitBuilder具有RabbitBuilder的返回类型,即使类型参数只是通配符. 不过,我并不是什么粉丝,因为您需要在各地使用通配符,并且只能使用钻石或原始类型.我想你最终都会变得有些尴尬. ,顺便说一句: 有一种方法可以避免这种不受控制的演员,这就是使方法抽象: 然后将其覆盖在叶子子类中: 这样做的问题是,尽管它更具类型安全性,但子类可以返回this以外的其他东西.基本上,无论哪种方式,子类都有机会做错事,所以我真的没有看到其中一种方法而不是另一种方法. 面对同一问题,我使用了Emcmanus提出的解决方案: https://community.oracle.com/blogs/emcmanus/2010/10/10/24/ususe-builder-pattern-subclasses 推荐答案
/**
* Extend this for Mammal subtype builders.
*/
abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> {
String sex;
String name;
B sex(String sex) {
this.sex = sex;
return self();
}
B name(String name) {
this.name = name;
return self();
}
abstract Mammal build();
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
}
/**
* Use this to actually build new Mammal instances.
*/
final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> {
@Override
Mammal build() {
return new Mammal(this);
}
}
/**
* Extend this for Rabbit subtype builders, e.g. LopBuilder.
*/
abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>>
extends GenericMammalBuilder<B> {
Color furColor;
B furColor(Color furColor) {
this.furColor = furColor;
return self();
}
@Override
abstract Rabbit build();
}
/**
* Use this to actually build new Rabbit instances.
*/
final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> {
@Override
Rabbit build() {
return new Rabbit(this);
}
}
class MammalBuilder<B extends MammalBuilder<B>> {
...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
extends MammalBuilder<B> {
...
}
static RabbitBuilder<?> builder() {
return new RabbitBuilder<>();
}
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
abstract B self();
@Override
RabbitBuilder self() { return this; }
其他推荐答案
我只是在这里重新复制他/她的首选解决方案.假设我们有两个类,Shape和Rectangle. Rectangle从Shape继承.
public class Shape { private final double opacity; public double getOpacity() { return opacity; } protected static abstract class Init<T extends Init<T>> { private double opacity; protected abstract T self(); public T opacity(double opacity) { this.opacity = opacity; return self(); } public Shape build() { return new Shape(this); } } public static class Builder extends Init<Builder> { @Override protected Builder self() { return this; } } protected Shape(Init<?> init) { this.opacity = init.opacity; } }
有一个抽象的Init内类,而Builder内类是一个实际的实现.实现Rectangle时将很有用:
public class Rectangle extends Shape { private final double height; public double getHeight() { return height; } protected static abstract class Init<T extends Init<T>> extends Shape.Init<T> { private double height; public T height(double height) { this.height = height; return self(); } public Rectangle build() { return new Rectangle(this); } } public static class Builder extends Init<Builder> { @Override protected Builder self() { return this; } } protected Rectangle(Init<?> init) { super(init); this.height = init.height; } }
实例化Rectangle:
new Rectangle.Builder().opacity(1.0D).height(1.0D).build();
再次,一个摘要Init类,从Shape.Init和a Build是实际的实现.每个Builder类实现self方法,该方法负责返回正确的铸造版本.
Shape.Init <-- Shape.Builder ^ | | Rectangle.Init <-- Rectangle.Builder
其他推荐答案
如果有人仍然遇到相同的问题,我建议采用以下解决方案,它符合"比继承"设计模式.
父级
它的主要元素是父类构建器必须实现的接口:
public interface RabbitBuilder<T> { public T sex(String sex); public T name(String name); }
这是带有更改的父类别类别:
public class Rabbit { public String sex; public String name; public Rabbit(Builder builder) { sex = builder.sex; name = builder.name; } public static class Builder implements RabbitBuilder<Builder> { protected String sex; protected String name; public Builder() {} public Rabbit build() { return new Rabbit(this); } @Override public Builder sex(String sex) { this.sex = sex; return this; } @Override public Builder name(String name) { this.name = name; return this; } } }
儿童班
儿童类Builder必须实现相同的接口(具有不同的通用类型):
public static class LopBuilder implements RabbitBuilder<LopBuilder>
在子类中Builder字段引用父Builder:
private Rabbit.Builder baseBuilder;
这确保父Builder在孩子中调用方法,但是,他们的实现是不同的:
@Override public LopBuilder sex(String sex) { baseBuilder.sex(sex); return this; } @Override public LopBuilder name(String name) { baseBuilder.name(name); return this; } public Rabbit build() { return new Lop(this); }
建筑商的构造函数:
public LopBuilder() { baseBuilder = new Rabbit.Builder(); }
建造子类的构造函数:
public Lop(LopBuilder builder) { super(builder.baseBuilder); }
问题描述
I have an object hierarchy that increases in complexity as the inheritance tree deepens. None of these are abstract, hence, all of their instances serve a, more or less sophisticated, purpose.
As the number of parameters is quite high, I would want to use the Builder Pattern to set properties rather than code several constructors. As I need to cater to all permutations, leaf classes in my inheritance tree would have telescoping constructors.
I have browsed for an answer here when I hit some problems during my design. First of, let me give you a simple, shallow example to illustrate the problem.
public class Rabbit { public String sex; public String name; public Rabbit(Builder builder) { sex = builder.sex; name = builder.name; } public static class Builder { protected String sex; protected String name; public Builder() { } public Builder sex(String sex) { this.sex = sex; return this; } public Builder name(String name) { this.name = name; return this; } public Rabbit build() { return new Rabbit(this); } } } public class Lop extends Rabbit { public float earLength; public String furColour; public Lop(LopBuilder builder) { super(builder); this.earLength = builder.earLength; this.furColour = builder.furColour; } public static class LopBuilder extends Rabbit.Builder { protected float earLength; protected String furColour; public LopBuilder() { } public Builder earLength(float length) { this.earLength = length; return this; } public Builder furColour(String colour) { this.furColour = colour; return this; } public Lop build() { return new Lop(this); } } }
Now that we have some code to go on, imaging I want to build a Lop:
Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);
This call will not compile as the last chained call cannot be resolved, Builder not defining the method earLength. So this way requires that all calls be chained in a specific order which is very impractical, especially with a deep hierarchy tree.
Now, during my search for an answer, I came across Subclassing a Java Builder class which suggests using the Curiously Recursive Generic Pattern. However, as my hierarchy does not contain an abstract class, this solution will not work for me. But the approach relies on abstraction and polymorphism to function which is why I don't believe I can adapt it to my needs.
An approach I have currently settled with is to override all methods of the superclass Builder in the hierarchy and simply do the following:
public ConcreteBuilder someOverridenMethod(Object someParameter) { super(someParameter); return this; }
With this approach I can assure I am being returned an instance I can issue chain calls on. While this is not as worse as the Telescoping Anti-pattern, it is a close second and I consider it a bit "hacky".
Is there another solution to my problem that I am not aware of? Preferably a solution consistent with the design pattern. Thank you!
推荐答案
This is certainly possible with the recursive bound, but the subtype builders need to also be generic, and you need a few interim abstract classes. It's a little bit cumbersome, but it's still easier than the non-generic version.
/** * Extend this for Mammal subtype builders. */ abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> { String sex; String name; B sex(String sex) { this.sex = sex; return self(); } B name(String name) { this.name = name; return self(); } abstract Mammal build(); @SuppressWarnings("unchecked") final B self() { return (B) this; } } /** * Use this to actually build new Mammal instances. */ final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> { @Override Mammal build() { return new Mammal(this); } } /** * Extend this for Rabbit subtype builders, e.g. LopBuilder. */ abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>> extends GenericMammalBuilder<B> { Color furColor; B furColor(Color furColor) { this.furColor = furColor; return self(); } @Override abstract Rabbit build(); } /** * Use this to actually build new Rabbit instances. */ final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> { @Override Rabbit build() { return new Rabbit(this); } }
There's a way to avoid having the "concrete" leaf classes, where if we had this:
class MammalBuilder<B extends MammalBuilder<B>> { ... } class RabbitBuilder<B extends RabbitBuilder<B>> extends MammalBuilder<B> { ... }
Then you need to create new instances with a diamond, and use wildcards in the reference type:
static RabbitBuilder<?> builder() { return new RabbitBuilder<>(); }
That works because the bound on the type variable ensures that all the methods of e.g. RabbitBuilder have a return type with RabbitBuilder, even when the type argument is just a wildcard.
I'm not much of a fan of that, though, because you need to use wildcards everywhere, and you can only create a new instance using the diamond or a raw type. I suppose you end up with a little awkwardness either way.
And by the way, about this:
@SuppressWarnings("unchecked") final B self() { return (B) this; }
There's a way to avoid that unchecked cast, which is to make the method abstract:
abstract B self();
And then override it in the leaf subclass:
@Override RabbitBuilder self() { return this; }
The issue with doing it that way is that although it's more type-safe, the subclass can return something other than this. Basically, either way, the subclass has the opportunity to do something wrong, so I don't really see much of a reason to prefer one of those approaches over the other.
其他推荐答案
Confronted with the same issue, I used the solution proposed by emcmanus at: https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses
I'm just recopying his/her preferred solution here. Let say we have two classes, Shape and Rectangle. Rectangle inherits from Shape.
public class Shape { private final double opacity; public double getOpacity() { return opacity; } protected static abstract class Init<T extends Init<T>> { private double opacity; protected abstract T self(); public T opacity(double opacity) { this.opacity = opacity; return self(); } public Shape build() { return new Shape(this); } } public static class Builder extends Init<Builder> { @Override protected Builder self() { return this; } } protected Shape(Init<?> init) { this.opacity = init.opacity; } }
There is the Init inner class, which is abstract, and the Builder inner class, that is an actual implementation. Will be useful when implementing the Rectangle:
public class Rectangle extends Shape { private final double height; public double getHeight() { return height; } protected static abstract class Init<T extends Init<T>> extends Shape.Init<T> { private double height; public T height(double height) { this.height = height; return self(); } public Rectangle build() { return new Rectangle(this); } } public static class Builder extends Init<Builder> { @Override protected Builder self() { return this; } } protected Rectangle(Init<?> init) { super(init); this.height = init.height; } }
To instantiate the Rectangle:
new Rectangle.Builder().opacity(1.0D).height(1.0D).build();
Again, an abstract Init class, inheriting from Shape.Init, and a Build that is the actual implementation. Each Builder class implement the self method, which is responsible to return a correctly cast version of itself.
Shape.Init <-- Shape.Builder ^ | | Rectangle.Init <-- Rectangle.Builder
其他推荐答案
If anyone still bumped into the same problem, I suggest the following solution, that conforms "Prefer composition over inheritance" design pattern.
Parent class
The main element of it is the interface that parent class Builder must implement:
public interface RabbitBuilder<T> { public T sex(String sex); public T name(String name); }
Here is the changed parent class with the change:
public class Rabbit { public String sex; public String name; public Rabbit(Builder builder) { sex = builder.sex; name = builder.name; } public static class Builder implements RabbitBuilder<Builder> { protected String sex; protected String name; public Builder() {} public Rabbit build() { return new Rabbit(this); } @Override public Builder sex(String sex) { this.sex = sex; return this; } @Override public Builder name(String name) { this.name = name; return this; } } }
The child class
The child class Builder must implement the same interface (with different generic type):
public static class LopBuilder implements RabbitBuilder<LopBuilder>
Inside the child class Builder the field referencing parentBuilder:
private Rabbit.Builder baseBuilder;
this ensures that parent Builder methods are called in the child, however, their implementation is different:
@Override public LopBuilder sex(String sex) { baseBuilder.sex(sex); return this; } @Override public LopBuilder name(String name) { baseBuilder.name(name); return this; } public Rabbit build() { return new Lop(this); }
The constructor of Builder:
public LopBuilder() { baseBuilder = new Rabbit.Builder(); }
The constructor of builded child class:
public Lop(LopBuilder builder) { super(builder.baseBuilder); }