构建大的、不可变的对象,而不使用具有长参数列表的构造函数
我有一些大的(超过 3 个字段)对象,它们可以而且应该是不可变的。每次遇到这种情况,我都会创建带有长参数列表的令人厌恶的构造函数。
感觉不太对劲,很难使用,而且可读性也受到影响。
如果字段是某种集合类型(例如列表),情况会更糟。一个简单的 addSibling(S s)
可以大大简化对象的创建,但会使对象变得可变。
你们在这种情况下用什么?
我使用 Scala 和 Java,但我认为只要语言是面向对象的,问题就与语言无关。
我能想到的解决方案:
- “带有长参数列表的构造函数令人厌恶”
- 构建器模式
I have some big (more than 3 fields) objects that can and should be immutable. Every time I run into that case I tend to create constructor abominations with long parameter lists.
It doesn't feel right, it is hard to use, and readability suffers.
It is even worse if the fields are some sort of collection type like lists. A simple addSibling(S s)
would ease the object creation so much but renders the object mutable.
What do you guys use in such cases?
I'm on Scala and Java, but I think the problem is language agnostic as long as the language is object oriented.
Solutions I can think of:
- "Constructor abominations with long parameter lists"
- The Builder Pattern
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
那么,您想要一个更易于阅读且创建后不可变的对象吗?
我认为流畅的界面正确完成会对您有所帮助。
它看起来像这样(纯粹是编造的例子):
我用粗体写了“正确完成”,因为大多数 Java 程序员都会错误地使用流畅的接口,并使用构建对象所需的方法污染他们的对象,这是当然完全错误。
诀窍是只有 build() 方法实际上创建了 Foo(因此 Foo 可以是不可变的)。
FooFactory.create()、whereXXX(..) 和 withXXX(..) 都创建“其他东西”。
其他的东西可能是 FooFactory,这是一种方法......
你的 FooFactory 看起来像这样:
Well, you want both an easier to read and immutable object once created?
I think a fluent interface CORRECTLY DONE would help you.
It would look like this (purely made up example):
I wrote "CORRECTLY DONE" in bold because most Java programmers get fluent interfaces wrong and pollute their object with the method necessary to build the object, which is of course completely wrong.
The trick is that only the build() method actually creates a Foo (hence you Foo can be immutable).
FooFactory.create(), whereXXX(..) and withXXX(..) all create "something else".
That something else may be a FooFactory, here's one way to do it....
You FooFactory would look like this:
在 Scala 2.8 中,您可以在案例类上使用命名参数和默认参数以及
copy
方法。这是一些示例代码:In Scala 2.8, you could use named and default parameters as well as the
copy
method on a case class. Here's some example code:好吧,考虑一下 Scala 2.8:
当然,这确实存在一些问题。例如,尝试创建
espouse
和Option[Person]
,然后让两个人结婚。我想不出一种方法来解决这个问题,而不求助于private var
和/或private
构造函数加上工厂。Well, consider this on Scala 2.8:
This does have its share of problems, of course. For instance, try making
espouse
andOption[Person]
, and then getting two persons married to each other. I can't think of a way to solve that without resorting to either aprivate var
and/or aprivate
constructor plus a factory.这里还有几个选项:
选项 1
使实现本身可变,但将其公开的接口分为可变接口和不可变接口。这是取自 Swing 库的设计。
选项 2
如果您的应用程序包含大量预定义的不可变对象(例如配置对象),您可以考虑使用 Spring 框架。
Here are a couple of more options:
Option 1
Make the implementation itself mutable, but separate the interfaces that it exposes to mutable and immutable. This is taken from the Swing library design.
Option 2
If your application contains a large but pre-defined set of immutable objects (e.g., configuration objects), you might consider using the Spring framework.
它有助于记住有 不同种类的不变性。对于你的情况,我认为“冰棒”不变性会非常有效:
因此,您初始化对象,然后设置某种“冻结”标志,表明它不再可写。最好将突变隐藏在函数后面,这样该函数对于使用 API 的客户端来说仍然是纯粹的。
It helps to remember there are different kinds of immutability. For your case, I think "popsicle" immutability will work really well:
So you initialize your object, then set a "freeze" flag of some sort indicating that its no longer writable. Preferably, you'd hide the mutation behind a function so the function is still pure to clients consuming your API.
您还可以使不可变对象公开看起来像变异器(如 addSibling)的方法,但让它们返回一个新实例。这就是不可变的 Scala 集合的作用。
缺点是您可能会创建不必要的实例。它也仅适用于存在中间有效配置的情况(例如某些没有兄弟节点的节点,这在大多数情况下都可以),除非您不想处理部分构建的对象。
例如,还没有目的地的图边不是有效的图边。
You could also make the immutable objects expose methods that look like mutators (like addSibling) but let them return a new instance. That's what the immutable Scala collections do.
The downside is that you might create more instances than necessary. It's also only applicable when there exist intermediate valid configurations (like some node without siblings which is ok in most cases) unless you don't want to deal with partially built objects.
For example a graph edge which has no destination yet isn't a valid graph edge.
考虑四种可能性:
对我来说,2、3 和 4 中的每一种都适用于不同的情况。由于OP引用的原因,第一个很难让人喜欢,并且通常是设计遭受一些蠕变并需要一些重构的症状。
当“工厂”背后没有状态时,我列出的(2)是好的,而(3)是有状态时选择的设计。当我不想担心线程和同步时,我发现自己使用(2)而不是(3),并且我不需要担心在许多对象的生产中摊销一些昂贵的设置。另一方面,当实际工作进入工厂构建(从 SPI 设置、读取配置文件等)时,就会调用 (3)。
最后,其他人的答案提到了选项(4),其中有很多不可变的小对象,更好的模式是从旧对象中获取新对象。
请注意,我不是“模式粉丝俱乐部”的成员——当然,有些东西值得效仿,但在我看来,一旦人们给它们起了名字和有趣的帽子,它们就会过上无益的生活。
Consider four possibilities:
To me, each of 2, 3, and 4 is adapted to a difference situation. The first one is hard to love, for the reasons cited by the OP, and is generally a symptom of a design that has suffered some creep and needs some refactoring.
What I'm listing as (2) is good when there is no state behind the 'factory', whereas (3) is the design of choice when there is state. I find myself using (2) rather than (3) when I don't want to worry about threads and synchronization, and I don't need to worry about amortizing some expensive setup over the production of many objects. (3), on the other hand, is called forth when real work goes into the construction of the factory (setting up from an SPI, reading configuration files, etc).
Finally, someone else's answer mentioned option (4), where you have lots of little immutable objects and the preferable pattern is to get news ones from old ones.
Note that I'm not a member of the 'pattern fan club' -- sure, some things are worth emulating, but it seems to me that they take on an unhelpful life of their own once people give them names and funny hats.
另一个潜在的选择是重构以减少可配置字段。如果字段组(大部分)只能相互协作,请将它们收集到自己的小型不可变对象中。该“小”对象的构造函数/构建器应该更易于管理,该“大”对象的构造函数/构建器也应该更易于管理。
Another potential option is to refactor to have fewer configurable fields. If groups of fields only work (mostly) with each other, gather them up into their own small immutable object. That "small" object's constructors/builders should be more manageable, as will the constructor/builder for this "big" object.
我使用 C#,这些是我的方法。考虑:
选项 1. 具有可选参数的构造函数
用作例如
new Foo(5, b: new Bar(whatever))
。不适用于 4.0 之前的 Java 或 C# 版本。但仍然值得展示,因为它是一个例子,说明并非所有解决方案都是与语言无关的。选项 2. 构造函数采用单个参数对象
使用示例:
从 3.0 开始,C# 使用对象初始值设定项语法使其更加优雅(语义上与前面的示例相同):
选项 3:
重新设计您的类,使其不需要如此大量的参数。您可以将其职责分为多个类。或者根据需要,不将参数传递给构造函数,而仅传递给特定方法。并不总是可行,但当可行时,就值得去做。
I use C#, and these are my approaches. Consider:
Option 1. Constructor with optional parameters
Used as e.g.
new Foo(5, b: new Bar(whatever))
. Not for Java or C# versions before 4.0. but still worth showing, as it's an example how not all solutions are language agnostic.Option 2. Constructor taking a single parameter object
Usage example:
C# from 3.0 on makes this more elegant with object initializer syntax (semantically equivalent to the previous example):
Option 3:
Redesign your class not to need such a huge number of parameters. You could split its repsonsibilities into multiple classes. Or pass parameters not to the constructor but only to specific methods, on demand. Not always viable, but when it is, it's worth doing.