Java 构建器模式和继承
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/21086417/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Builder Pattern and Inheritance
提问by Eric
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.
由于参数数量相当多,我想使用 Builder Pattern 来设置属性而不是编写多个构造函数。因为我需要满足所有排列,所以我的继承树中的叶类将具有伸缩构造函数。
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 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.
此调用不会编译,因为最后一个链接调用无法解析,Builder
未定义方法earLength
。因此,这种方式要求所有调用都以特定顺序链接,这是非常不切实际的,尤其是对于深层次树。
Now, during my search for an answer, I came across Subclassing a Java Builder classwhich 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.
现在,在我寻找答案的过程中,我遇到了Subclassing a Java Builder 类,它建议使用Curiously Recursive Generic Pattern。但是,由于我的层次结构不包含抽象类,因此此解决方案对我不起作用。但是这种方法依赖于抽象和多态来发挥作用,这就是为什么我不相信我可以适应我的需求。
An approach I have currently settled with is to override all methods of the superclass Builder
in the hierarchy and simply do the following:
我目前已经解决的一种方法是覆盖Builder
层次结构中超类的所有方法,只需执行以下操作:
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".
通过这种方法,我可以保证我会返回一个可以发出链式调用的实例。虽然这并不像 Telescoping Anti-pattern 那样糟糕,但它紧随其后,我认为它有点“hacky”。
Is there another solution to my problem that I am not aware of? Preferably a solution consistent with the design pattern. Thank you!
我的问题还有其他我不知道的解决方案吗?最好是与设计模式一致的解决方案。谢谢!
采纳答案by Radiodef
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.
这是有效的,因为类型变量上的绑定确保 eg 的所有方法RabbitBuilder
都有一个返回类型 with RabbitBuilder
,即使类型参数只是一个通配符。
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.
不过,我不太喜欢这个,因为您需要在任何地方使用通配符,而且您只能使用 Diamond 或raw type创建一个新实例。我想你最终会有点尴尬。
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.
这样做的问题在于,虽然它更类型安全,但子类可以返回this
. 基本上,无论哪种方式,子类都有机会做错事,所以我真的看不出有什么理由更喜欢其中一种方法而不是另一种方法。
回答by Samurai Girl
As you cannot use generics, now probably the main task is to somehow loosen typing. I don't know how you process those properties afterwards, but what if you used a HashMap for storing them as key-value pairs? So there will be just one set(key, value) wrapper method in the builder (or builder might not be necessary any more).
由于您不能使用泛型,现在的主要任务可能是以某种方式放松输入。我不知道您之后如何处理这些属性,但是如果您使用 HashMap 将它们存储为键值对呢?因此,构建器中将只有一个 set(key, value) 包装器方法(或者构建器可能不再需要)。
The downside would be additional type castings while processing the stored data.
缺点是在处理存储的数据时会进行额外的类型转换。
If this case is too loose, then you could keep the existing properties, but have a general set method, which uses reflection and searches for setter method on the basis of 'key' name. Although I think reflection would be an overkill.
如果这种情况太松散,那么您可以保留现有属性,但有一个通用的 set 方法,该方法使用反射并根据“key”名称搜索 setter 方法。虽然我认为反思是一种矫枉过正。
回答by OldCurmudgeon
This form seems to nearly work. It is not very tidy but it looks like it avoids your issues:
这种形式似乎几乎有效。它不是很整洁,但看起来可以避免您的问题:
class Rabbit<B extends Rabbit.Builder<B>> {
String name;
public Rabbit(Builder<B> builder) {
this.name = builder.colour;
}
public static class Builder<B extends Rabbit.Builder<B>> {
protected String colour;
public B colour(String colour) {
this.colour = colour;
return (B)this;
}
public Rabbit<B> build () {
return new Rabbit<>(this);
}
}
}
class Lop<B extends Lop.Builder<B>> extends Rabbit<B> {
float earLength;
public Lop(Builder<B> builder) {
super(builder);
this.earLength = builder.earLength;
}
public static class Builder<B extends Lop.Builder<B>> extends Rabbit.Builder<B> {
protected float earLength;
public B earLength(float earLength) {
this.earLength = earLength;
return (B)this;
}
@Override
public Lop<B> build () {
return new Lop<>(this);
}
}
}
public class Test {
public void test() {
Rabbit rabbit = new Rabbit.Builder<>().colour("White").build();
Lop lop1 = new Lop.Builder<>().earLength(1.4F).colour("Brown").build();
Lop lop2 = new Lop.Builder<>().colour("Brown").earLength(1.4F).build();
//Lop.Builder<Lop, Lop.Builder> builder = new Lop.Builder<>();
}
public static void main(String args[]) {
try {
new Test().test();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
}
Although I have successfully built Rabbit
and Lop
(in both forms) I cannot at this stage work out how to actually instantiate one of the Builder
objects with it's full type.
尽管我已经成功构建Rabbit
并且Lop
(以两种形式)我在这个阶段还不能弄清楚如何Builder
用它的完整类型实际实例化一个对象。
The essence of this method relies on the cast to (B)
in the Builder
methods. This allow you to define the type of object and the type of the Builder
and retain that within the object while it is constructed.
这种方法的实质依赖于投来(B)
的Builder
方法。这允许您定义对象的类型和对象的类型,Builder
并在构造对象时将其保留在对象中。
If anyone could work out the correct syntax for this (which is wrong) I would appreciate it.
如果有人能为此找出正确的语法(这是错误的),我将不胜感激。
Lop.Builder<Lop.Builder> builder = new Lop.Builder<>();
回答by R. Zagórski
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:
它的主要元素是父类Builder必须实现的接口:
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):
子类Builder
必须实现相同的接口(具有不同的泛型类型):
public static class LopBuilder implements RabbitBuilder<LopBuilder>
Inside the child class Builder
the field referencing parentBuilder
:
在子类中Builder
,引用 parent 的字段Builder
:
private Rabbit.Builder baseBuilder;
this ensures that parent Builder
methods are called in the child, however, their implementation is different:
这确保在子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);
}
The constructor of Builder:
Builder的构造函数:
public LopBuilder() {
baseBuilder = new Rabbit.Builder();
}
The constructor of builded child class:
构建子类的构造函数:
public Lop(LopBuilder builder) {
super(builder.baseBuilder);
}
回答by Niels Basjes
I did some experimenting and I found this to work quite nicely for me. Note that I prefer to create the actual instance at the start and the call all the setters on that instance. This is just a preference.
我做了一些实验,我发现这对我来说非常有效。请注意,我更喜欢在开始时创建实际实例并调用该实例上的所有设置器。这只是一种偏好。
The main differences with the accepted answer is that
与接受的答案的主要区别在于
- I pass a parameter that indicated the return type
- There is no need for an Abstract... and a final builder.
- I create a 'newBuilder' convenience method.
- 我传递了一个指示返回类型的参数
- 不需要抽象……和最终的构建器。
- 我创建了一个“newBuilder”方便的方法。
The code:
编码:
public class MySuper {
private int superProperty;
public MySuper() { }
public void setSuperProperty(int superProperty) {
this.superProperty = superProperty;
}
public static SuperBuilder<? extends MySuper, ? extends SuperBuilder> newBuilder() {
return new SuperBuilder<>(new MySuper());
}
public static class SuperBuilder<R extends MySuper, B extends SuperBuilder<R, B>> {
private final R mySuper;
public SuperBuilder(R mySuper) {
this.mySuper = mySuper;
}
public B withSuper(int value) {
mySuper.setSuperProperty(value);
return (B) this;
}
public R build() {
return mySuper;
}
}
}
and then a subclass look like this:
然后一个子类看起来像这样:
public class MySub extends MySuper {
int subProperty;
public MySub() {
}
public void setSubProperty(int subProperty) {
this.subProperty = subProperty;
}
public static SubBuilder<? extends MySub, ? extends SubBuilder> newBuilder() {
return new SubBuilder(new MySub());
}
public static class SubBuilder<R extends MySub, B extends SubBuilder<R, B>>
extends SuperBuilder<R, B> {
private final R mySub;
public SubBuilder(R mySub) {
super(mySub);
this.mySub = mySub;
}
public B withSub(int value) {
mySub.setSubProperty(value);
return (B) this;
}
}
}
and a subsub class
和一个子子类
public class MySubSub extends MySub {
private int subSubProperty;
public MySubSub() {
}
public void setSubSubProperty(int subProperty) {
this.subSubProperty = subProperty;
}
public static SubSubBuilder<? extends MySubSub, ? extends SubSubBuilder> newBuilder() {
return new SubSubBuilder<>(new MySubSub());
}
public static class SubSubBuilder<R extends MySubSub, B extends SubSubBuilder<R, B>>
extends SubBuilder<R, B> {
private final R mySubSub;
public SubSubBuilder(R mySub) {
super(mySub);
this.mySubSub = mySub;
}
public B withSubSub(int value) {
mySubSub.setSubSubProperty(value);
return (B)this;
}
}
}
To verify it fully works I used this test:
为了验证它完全有效,我使用了这个测试:
MySubSub subSub = MySubSub
.newBuilder()
.withSuper (1)
.withSub (2)
.withSubSub(3)
.withSub (2)
.withSuper (1)
.withSubSub(3)
.withSuper (1)
.withSub (2)
.build();
回答by benez
The most easy fix would be to simply override the setter methods of the parent class.
最简单的解决方法是简单地覆盖父类的 setter 方法。
You avoid generics, it's easy to use, extend and to understand and you also avoid code duplication when calling super.setter.
您避免使用泛型,它易于使用、扩展和理解,并且在调用 super.setter 时还可以避免代码重复。
public class Lop extends Rabbit {
public final float earLength;
public final String furColour;
public Lop(final 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() {}
@Override
public LopBuilder sex(final String sex) {
super.sex(sex);
return this;
}
@Override
public LopBuilder name(final String name) {
super.name(name);
return this;
}
public LopBuilder earLength(final float length) {
this.earLength = length;
return this;
}
public LopBuilder furColour(final String colour) {
this.furColour = colour;
return this;
}
@Override
public Lop build() {
return new Lop(this);
}
}
}
回答by jmgonet
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
面对同样的问题,我使用了 emcmanus 提出的解决方案: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
.
我只是在这里重新复制他/她的首选解决方案。假设我们有两个类,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;
}
}
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
:
还有就是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;
}
}
To instantiate the Rectangle
:
实例化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.
同样,一个抽象Init
类,继承自Shape.Init
,而 aBuild
是实际的实现。每个Builder
类都实现了该self
方法,该方法负责返回自身的正确转换版本。
Shape.Init <-- Shape.Builder
^
|
|
Rectangle.Init <-- Rectangle.Builder
回答by mc00x1
The following IEEE conference contribution Refined Fluent Builder in Javagives a comprehensive solution to the problem.
以下 IEEE 会议文稿Refined Fluent Builder in Java给出了该问题的综合解决方案。
It dissects the original question into two sub-problems of inheritance deficiencyand quasi invarianceand shows how a solution to these two sub-problems opens for inheritance support with code reuse in the classical builder pattern in Java.
它将原始问题分解为继承缺陷和准不变性两个子问题,并展示了这两个子问题的解决方案如何通过 Java 中的经典构建器模式中的代码重用来支持继承支持。