Java 在不使用具有长参数列表的构造函数的情况下构建大的、不可变的对象
我有一些大的(超过3个字段)对象可以而且应该是不可变的。每次遇到这种情况,我都倾向于创建具有长参数列表的构造函数 它感觉不正确,难以使用,可读性受到影响 如果字段是某种集合类型(如列表),则情况更糟。一个简单的Java 在不使用具有长参数列表的构造函数的情况下构建大的、不可变的对象,java,oop,scala,immutability,Java,Oop,Scala,Immutability,我有一些大的(超过3个字段)对象可以而且应该是不可变的。每次遇到这种情况,我都倾向于创建具有长参数列表的构造函数 它感觉不正确,难以使用,可读性受到影响 如果字段是某种集合类型(如列表),则情况更糟。一个简单的addSibling将大大简化对象的创建,但会使对象变得可变 在这种情况下你们用什么 我研究Scala和Java,但我认为问题在于语言不可知,只要语言是面向对象的 我能想到的解决办法是: “具有长参数列表的构造函数” 构建器模式 考虑四种可能性: new Immutable(one, fi
addSibling
将大大简化对象的创建,但会使对象变得可变
在这种情况下你们用什么
我研究Scala和Java,但我认为问题在于语言不可知,只要语言是面向对象的
我能想到的解决办法是:
考虑四种可能性:
new Immutable(one, fish, two, fish, red, fish, blue, fish); /*1 */
params = new ImmutableParameters(); /*2 */
params.setType("fowl");
new Immutable(params);
factory = new ImmutableFactory(); /*3 */
factory.setType("fish");
factory.getInstance();
Immutable boringImmutable = new Immutable(); /* 4 */
Immutable lessBoring = boringImmutable.setType("vegetable");
对我来说,2、3和4中的每一个都适应了不同的情况。第一个是很难被喜欢的,因为OP引用了一些原因,并且通常是一个已经经历了一些蠕变并且需要一些重构的设计的症状
当“工厂”后面没有状态时,我列出的(2)是好的,而(3)是有状态时选择的设计。当我不想担心线程和同步时,我发现自己使用的是(2)而不是(3),我也不需要担心在生产许多对象的过程中使一些昂贵的设置变得更为昂贵。(3) 另一方面,当实际工作进入工厂建设时(从SPI设置、读取配置文件等),会调用
最后,其他人的回答提到了选项(4),其中有很多小的不可变对象,更好的模式是从旧对象中获取新闻对象
请注意,我不是“模式粉丝俱乐部”的成员——当然,有些东西是值得效仿的,但在我看来,一旦人们给他们起了名字,给他们戴上了滑稽的帽子,他们自己的生活就变得毫无用处了。你也可以让不可变对象公开看起来像变种人的方法(比如addSibling)但让它们返回一个新实例。这就是不可变的Scala集合所做的 缺点是,您可能会创建过多的实例。除非您不想处理部分构建的对象,否则它也仅适用于存在中间有效配置的情况(如某些节点没有兄弟节点,这在大多数情况下是可以的)
例如,没有目标的图边不是有效的图边。另一个可能的选择是重构以具有较少的可配置字段。如果字段组仅(主要)相互作用,则将它们聚集到自己的小不可变对象中。“小”对象的构造函数/构建器应该更易于管理,这个“大”对象的构造函数/构建器也应该更易于管理。我使用C#,这些是我的方法。考虑:
class Foo
{
// private fields only to be written inside a constructor
private readonly int i;
private readonly string s;
private readonly Bar b;
// public getter properties
public int I { get { return i; } }
// etc.
}
选项1。带可选参数的构造函数
public Foo(int i = 0, string s = "bla", Bar b = null)
{
this.i = i;
this.s = s;
this.b = b;
}
用作例如新Foo(5,b:新条(无论什么))
。不适用于4.0之前的Java或C版本。但仍然值得展示,因为这是一个例子,说明并非所有的解决方案都是语言不可知的
选项2。采用单参数对象的构造函数
public Foo(FooParameters parameters)
{
this.i = parameters.I;
// etc.
}
class FooParameters
{
// public properties with automatically generated private backing fields
public int I { get; set; }
public string S { get; set; }
public Bar B { get; set; }
// All properties are public, so we don't need a full constructor.
// For convenience, you could include some commonly used initialization
// patterns as additional constructors.
public FooParameters() { }
}
用法示例:
FooParameters fp = new FooParameters();
fp.I = 5;
fp.S = "bla";
fp.B = new Bar();
Foo f = new Foo(fp);`
从3.0开始的C#使用对象初始值设定项语法(语义上与前面的示例相同)使这一点更加优雅:
选项3:重新设计类,使其不需要如此大量的参数。您可以将其repsonability拆分为多个类。或者根据需要,不将参数传递给构造函数,而只传递给特定方法。不总是可行的,但当可行时,它是值得的。好吧,您希望创建一个易于阅读且不可变的对象吗 我认为一个流畅的界面正确地完成将对您有所帮助 它看起来像这样(纯粹虚构的例子): 我用粗体写了“正确完成”,因为大多数Java程序员错误地获得了流畅的接口,并用构建对象所需的方法污染了他们的对象,这当然是完全错误的 诀窍在于只有build()方法实际创建了一个Foo(因此Foo可以是不可变的) create(),其中xxx(..)和withXXX(..)都创建“其他东西” 还有一种可能是食品工厂,这里有一种方法 您的工厂将如下所示:
// Notice the private FooFactory constructor
private FooFactory() {
}
public static FooFactory create() {
return new FooFactory();
}
public FooFactory withColor( final Color col ) {
this.color = color;
return this;
}
public Foo build() {
return new FooImpl( color, and, all, the, other, parameters, go, here );
}
这里还有几个选项: 选择1 使实现本身可变,但将其公开给可变和不可变的接口分开。这取自Swing库设计
public interface Foo {
X getX();
Y getY();
}
public interface MutableFoo extends Foo {
void setX(X x);
void setY(Y y);
}
public class FooImpl implements MutableFoo {...}
public SomeClassThatUsesFoo {
public Foo makeFoo(...) {
MutableFoo ret = new MutableFoo...
ret.setX(...);
ret.setY(...);
return ret; // As Foo, not MutableFoo
}
}
选择2
如果您的应用程序包含一个大但预定义的不可变对象集(例如,配置对象),则可以考虑使用框架。在Scala 2.8中,可以使用命名和默认参数以及case类中的<代码>复制< /> >方法。下面是一些示例代码:
case class Person(name: String, age: Int, children: List[Person] = List()) {
def addChild(p: Person) = copy(children = p :: this.children)
}
val parent = Person(name = "Bob", age = 55)
.addChild(Person("Lisa", 23))
.addChild(Person("Peter", 16))
嗯,考虑Scala 2.8:
case class Person(name: String,
married: Boolean = false,
espouse: Option[String] = None,
children: Set[String] = Set.empty) {
def marriedTo(whom: String) = this.copy(married = true, espouse = Some(whom))
def addChild(whom: String) = this.copy(children = children + whom)
}
scala> Person("Joseph").marriedTo("Mary").addChild("Jesus")
res1: Person = Person(Joseph,true,Some(Mary),Set(Jesus))
当然,这也有一些问题。例如,尝试使用
支持和选项[Person]
,然后让两个人结婚。如果不借助于private var
和/或private
构造函数加上工厂,我想不出解决这个问题的方法。记住有。对于您的情况,我认为“冰棒”不变性将非常有效:
冰棒不变性:这就是我所想的
异想天开地称之为轻微的削弱
写一次是不可变的。可以
想象一个物体或一个区域
暂时保持不变
在其初始化期间,然后
永远“冻结”。这种
不变性特别有用
对于循环的不可变对象
相互引用,或不可变
已序列化为的对象
磁盘和反序列化时需要
保持“流动”,直到整个
反序列化过程在
哪一点
case class Person(name: String, age: Int, children: List[Person] = List()) {
def addChild(p: Person) = copy(children = p :: this.children)
}
val parent = Person(name = "Bob", age = 55)
.addChild(Person("Lisa", 23))
.addChild(Person("Peter", 16))
case class Person(name: String,
married: Boolean = false,
espouse: Option[String] = None,
children: Set[String] = Set.empty) {
def marriedTo(whom: String) = this.copy(married = true, espouse = Some(whom))
def addChild(whom: String) = this.copy(children = children + whom)
}
scala> Person("Joseph").marriedTo("Mary").addChild("Jesus")
res1: Person = Person(Joseph,true,Some(Mary),Set(Jesus))