Eri*_*ric 49 java inheritance design-patterns builder
随着继承树的深入,我有一个对象层次结构,其复杂性会增加.这些都不是抽象的,因此,它们的所有实例都具有或多或少的复杂目的.
由于参数的数量非常多,我想使用Builder Pattern来设置属性而不是编码几个构造函数.由于我需要满足所有排列,我的继承树中的叶类将具有伸缩构造函数.
当我在设计中遇到一些问题时,我在这里寻找答案.首先,让我举一个简单,浅薄的例子来说明问题.
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们已经有了一些代码继续进行,我希望构建一个Lop:
Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);
Run Code Online (Sandbox Code Playgroud)
此调用将无法编译,因为无法解析最后一个链式调用,Builder而无法定义该方法earLength.因此,这种方式要求所有调用都以特定的顺序链接,这是非常不切实际的,特别是对于深层次结构树.
现在,在我寻找答案的过程中,我遇到了Subclassing一个Java Builder类,它建议使用Curiously Recursive Generic Pattern.但是,由于我的层次结构不包含抽象类,因此该解决方案对我不起作用.但是这种方法依赖于抽象和多态来起作用,这就是为什么我不相信我可以根据我的需要调整它.
我目前解决的一种方法是覆盖Builder层次结构中超类的所有方法,并简单地执行以下操作:
public ConcreteBuilder someOverridenMethod(Object someParameter)
{
super(someParameter);
return this;
}
Run Code Online (Sandbox Code Playgroud)
通过这种方法,我可以保证我将返回一个可以发出链接调用的实例.虽然这并不像Telescoping Anti-pattern那么糟糕,但它是一个接近的第二,我认为它有点"hacky".
我不知道有什么问题可以解决我的问题吗?优选地,解决方案与设计模式一致.谢谢!
Rad*_*def 52
这对于递归绑定肯定是可能的,但子类型构建器也需要是通用的,并且您需要一些临时抽象类.它有点麻烦,但它仍然比非通用版本更容易.
/**
* 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);
}
}
Run Code Online (Sandbox Code Playgroud)
有一种方法可以避免使用"具体"叶子类,如果我们有这个:
class MammalBuilder<B extends MammalBuilder<B>> {
...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
extends MammalBuilder<B> {
...
}
Run Code Online (Sandbox Code Playgroud)
然后,您需要使用菱形创建新实例,并在引用类型中使用通配符:
static RabbitBuilder<?> builder() {
return new RabbitBuilder<>();
}
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为类型变量的绑定确保所有方法RabbitBuilder都具有返回类型RabbitBuilder,即使类型参数只是通配符.
不过,我并不喜欢它,因为你需要在任何地方都使用通配符,而且你只能使用菱形或原始类型创建一个新实例.我想你最终会有一点尴尬.
顺便说一句,关于这个:
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
Run Code Online (Sandbox Code Playgroud)
有一种方法可以避免这种未经检查的强制转换,即使方法具有抽象性:
abstract B self();
Run Code Online (Sandbox Code Playgroud)
然后在叶子类中覆盖它:
@Override
RabbitBuilder self() { return this; }
Run Code Online (Sandbox Code Playgroud)
这样做的问题是虽然它更安全,但是子类可以返回除了之外的其他东西this.基本上,无论哪种方式,子类都有机会做错事,所以我真的没有理由更喜欢这些方法中的一种而不是另一种.
面对同样的问题,我使用了 emcmanus 提出的解决方案:https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses
我只是在这里重新复制他/她的首选解决方案。假设我们有两个班级,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;
}
}
Run Code Online (Sandbox Code Playgroud)
有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;
}
}
Run Code Online (Sandbox Code Playgroud)
实例化Rectangle:
new Rectangle.Builder().opacity(1.0D).height(1.0D).build();
Run Code Online (Sandbox Code Playgroud)
同样,一个抽象Init类,继承自Shape.Init,并且 aBuild是实际的实现。每个Builder类都实现该self方法,该方法负责返回其自身的正确转换版本。
Shape.Init <-- Shape.Builder
^
|
|
Rectangle.Init <-- Rectangle.Builder
Run Code Online (Sandbox Code Playgroud)