问题描述
动机
最近,我搜索了一种初始化复杂对象的方法,而无需将大量参数传递给构造函数.我尝试使用构建器图案尝试过,但是我不喜欢这样的事实,如果我真的设置了所有需要的值,我将无法在编译时间检查.
传统建筑商模式
当我使用构建器模式创建我的Complex对象时,创建更为" typesafe",因为更容易查看参数的用途:
new ComplexBuilder() .setFirst( "first" ) .setSecond( "second" ) .setThird( "third" ) ... .build();
但是现在我有问题,我很容易错过一个重要的参数.我可以在build()方法中检查它,但这仅在运行时.在编译时,如果我错过了什么,没有任何警告.
增强的构建器模式
现在,我的想法是创建一个建筑商,如果我错过了必要的参数,请"提醒我".我的第一次尝试看起来像:
public class Complex { private String m_first; private String m_second; private String m_third; private Complex() {} public static class ComplexBuilder { private Complex m_complex; public ComplexBuilder() { m_complex = new Complex(); } public Builder2 setFirst( String first ) { m_complex.m_first = first; return new Builder2(); } public class Builder2 { private Builder2() {} Builder3 setSecond( String second ) { m_complex.m_second = second; return new Builder3(); } } public class Builder3 { private Builder3() {} Builder4 setThird( String third ) { m_complex.m_third = third; return new Builder4(); } } public class Builder4 { private Builder4() {} Complex build() { return m_complex; } } } }
您可以看到,构建器类的每个设置者都会返回另一个内部构建器类.每个内部构建器类都提供一个固定器方法,最后一个方法仅提供构建()方法.
现在,对象的构造再次看起来像这样:
new ComplexBuilder() .setFirst( "first" ) .setSecond( "second" ) .setThird( "third" ) .build();
...但是没有办法忘记所需的参数.编译器不会接受.
可选参数
如果我有可选的参数,我将使用最后一个内部构建器类Builder4将它们设置为"传统"构建器,返回自己.
问题
- 这是一个众所周知的模式吗?它有特殊名称吗?
- 您看到任何陷阱吗?
- 您是否有任何提高实施的想法 - 从较少的代码行? 的意义上?
推荐答案
不,这不是新事物.您实际在那里做的是创建一种 dsl "> dsl 支持分支的模式,这是确保建造者不会为实际对象产生一组冲突的设置的绝佳方法.
我个人认为这是建造器模式的绝佳扩展,您可以对其进行各种有趣的事情,例如,在工作中,我们有一些DSL构建器用于我们的某些数据完整性测试,使我们能够做
其他推荐答案
传统的构建器模式已经处理以下操作:只需将构造函数中的强制性参数.当然,没有什么可以阻止呼叫者通过null,但是您的方法也没有.
我在您的方法中看到的一个大问题是,您要么具有与强制性参数数量的类的组合爆炸,要么迫使用户将参数设置为一个特定的sqeuence,这很烦人.
另外,这是很多其他工作.
其他推荐答案
public class Complex { private final String first; private final String second; private final String third; public static class False {} public static class True {} public static class Builder<Has1,Has2,Has3> { private String first; private String second; private String third; private Builder() {} public static Builder<False,False,False> create() { return new Builder<>(); } public Builder<True,Has2,Has3> setFirst(String first) { this.first = first; return (Builder<True,Has2,Has3>)this; } public Builder<Has1,True,Has3> setSecond(String second) { this.second = second; return (Builder<Has1,True,Has3>)this; } public Builder<Has1,Has2,True> setThird(String third) { this.third = third; return (Builder<Has1,Has2,True>)this; } } public Complex(Builder<True,True,True> builder) { first = builder.first; second = builder.second; third = builder.third; } public static void test() { // Compile Error! Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2")); // Compile Error! Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3")); // Works!, all params supplied. Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3")); } }
问题描述
Motivation
Recently I searched for a way to initialize a complex object without passing a lot of parameter to the constructor. I tried it with the builder pattern, but I don't like the fact, that I'm not able to check at compile time if I really set all needed values.
Traditional builder pattern
When I use the builder pattern to create my Complex object, the creation is more "typesafe", because it's easier to see what an argument is used for:
new ComplexBuilder() .setFirst( "first" ) .setSecond( "second" ) .setThird( "third" ) ... .build();
But now I have the problem, that I can easily miss an important parameter. I can check for it inside the build() method, but that is only at runtime. At compile time there is nothing that warns me, if I missed something.
Enhanced builder pattern
Now my idea was to create a builder, that "reminds" me if I missed a needed parameter. My first try looks like this:
public class Complex { private String m_first; private String m_second; private String m_third; private Complex() {} public static class ComplexBuilder { private Complex m_complex; public ComplexBuilder() { m_complex = new Complex(); } public Builder2 setFirst( String first ) { m_complex.m_first = first; return new Builder2(); } public class Builder2 { private Builder2() {} Builder3 setSecond( String second ) { m_complex.m_second = second; return new Builder3(); } } public class Builder3 { private Builder3() {} Builder4 setThird( String third ) { m_complex.m_third = third; return new Builder4(); } } public class Builder4 { private Builder4() {} Complex build() { return m_complex; } } } }
As you can see, each setter of the builder class returns a different internal builder class. Each internal builder class provides exactly one setter method and the last one provides only a build() method.
Now the construction of an object again looks like this:
new ComplexBuilder() .setFirst( "first" ) .setSecond( "second" ) .setThird( "third" ) .build();
...but there is no way to forget a needed parameter. The compiler wouldn't accept it.
Optional parameters
If I had optional parameters, I would use the last internal builder class Builder4 to set them like a "traditional" builder does, returning itself.
Questions
- Is this a well known pattern? Does it have a special name?
- Do you see any pitfalls?
- Do you have any ideas to improve the implementation - in the sense of fewer lines of code?
推荐答案
No, it's not new. What you're actually doing there is creating a sort of a DSL by extending the standard builder pattern to support branches which is among other things an excellent way to make sure the builder doesn't produce a set of conflicting settings to the actual object.
Personally I think this is a great extension to builder pattern and you can do all sorts of interesting things with it, for example at work we have DSL builders for some of our data integrity tests which allow us to do things like assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn();. OK, maybe not the best possible example but I think you get the point.
其他推荐答案
The traditional builder pattern already handles this: simply take the mandatory parameters in the constructor. Of course, nothing prevents a caller from passing null, but neither does your method.
The big problem I see with your method is that you either have a combinatorical explosion of classes with the number of mandatory parameters, or force the user to set the parameters in one particular sqeuence, which is annoying.
Also, it is a lot of additional work.
其他推荐答案
public class Complex { private final String first; private final String second; private final String third; public static class False {} public static class True {} public static class Builder<Has1,Has2,Has3> { private String first; private String second; private String third; private Builder() {} public static Builder<False,False,False> create() { return new Builder<>(); } public Builder<True,Has2,Has3> setFirst(String first) { this.first = first; return (Builder<True,Has2,Has3>)this; } public Builder<Has1,True,Has3> setSecond(String second) { this.second = second; return (Builder<Has1,True,Has3>)this; } public Builder<Has1,Has2,True> setThird(String third) { this.third = third; return (Builder<Has1,Has2,True>)this; } } public Complex(Builder<True,True,True> builder) { first = builder.first; second = builder.second; third = builder.third; } public static void test() { // Compile Error! Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2")); // Compile Error! Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3")); // Works!, all params supplied. Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3")); } }