scala 无需使用具有长参数列表的构造函数即可构建大的、不可变的对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2848938/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 02:09:08  来源:igfitidea点击:

Building big, immutable objects without using constructors having long parameter lists

javaoopscalaimmutability

提问by Malax

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.

我有一些大的(超过 3 个字段)对象可以而且应该是不可变的。每次遇到这种情况时,我都倾向于创建带有长参数列表的构造函数。

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.

如果字段是某种集合类型(如列表),则情况更糟。一个简单的方法addSibling(S s)可以大大简化对象的创建,但会使对象可变。

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.

我在使用 Scala 和 Java,但我认为只要语言是面向对象的,问题就与语言无关。

Solutions I can think of:

我能想到的解决方案:

  1. "Constructor abominations with long parameter lists"
  2. The Builder Pattern
  1. “具有长参数列表的构造函数可憎”
  2. 建造者模式

采纳答案by SyntaxT3rr0r

Well, you want both an easier to read and immutable object once created?

好吧,您想要一个更易于阅读且创建后不可变的对象吗?

I think a fluent interface CORRECTLY DONEwould help you.

我认为正确完成的流畅界面会对您有所帮助。

It would look like this (purely made up example):

它看起来像这样(纯示例):

final Foo immutable = FooFactory.create()
    .whereRangeConstraintsAre(100,300)
    .withColor(Color.BLUE)
    .withArea(234)
    .withInterspacing(12)
    .build();

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.

我用粗体写了“CORRECTLY DONE”,因为大多数 Java 程序员弄错了流畅的接口,并用构建对象所需的方法污染了他们的对象,这当然是完全错误的。

The trick is that only the build() method actually creates a Foo(hence you Foo can be immutable).

诀窍是只有 build() 方法实际上创建了一个 Foo(因此你 Foo 可以是不可变的)。

FooFactory.create(), whereXXX(..)and withXXX(..)all create "something else".

FooFactory.create()whereXXX(..)withXXX(..)都创建了“别的东西”。

That something else may be a FooFactory, here's one way to do it....

其他东西可能是 FooFactory,这是一种方法......

You FooFactory would look like this:

你的 FooFactory 看起来像这样:

// 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 );
}

回答by Martin Odersky

In Scala 2.8, you could use named and default parameters as well as the copymethod on a case class. Here's some example code:

在 Scala 2.8 中,您可以使用命名和默认参数以及copy案例类上的方法。下面是一些示例代码:

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))

回答by Daniel C. Sobral

Well, consider this on Scala 2.8:

好吧,在 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))

This does have its share of problems, of course. For instance, try making espouseand Option[Person], and then getting two persons married to each other. I can't think of a way to solve that without resorting to either a private varand/or a privateconstructor plus a factory.

当然,这确实存在一些问题。例如,尝试制作espouseOption[Person],然后让两个人彼此结婚。如果不求助于一个private var和/或一个private构造函数加上一个工厂,我想不出一种方法来解决这个问题。

回答by Little Bobby Tables

Here are a couple of more options:

这里有几个选项:

Option 1

选项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.

使实现本身可变,但将它暴露给可变和不可变的接口分开。这取自 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
  }
}

Option 2

选项 2

If your application contains a large but pre-defined set of immutable objects (e.g., configuration objects), you might consider using the Springframework.

如果您的应用程序包含大量预定义的不可变对象(例如,配置对象),您可以考虑使用Spring框架。

回答by Juliet

It helps to remember there are different kindsof immutability. For your case, I think "popsicle" immutability will work really well:

记住有不同种类的不变性是有帮助。对于您的情况,我认为“冰棒”不变性将非常有效:

Popsicle immutability: is what I whimsically call a slight weakening of write-once immutability. One could imagine an object or a field which remained mutable for a little while during its initialization, and then got “frozen” forever. This kind of immutability is particularly useful for immutable objects which circularly reference each other, or immutable objects which have been serialized to disk and upon deserialization need to be “fluid” until the entire deserialization process is done, at which point all the objects may be frozen.

冰棒不变性:我异想天开地称之为一次写入不变性的轻微削弱。可以想象一个对象或字段在初始化期间保持可变一段时间,然后永远“冻结”。这种不变性对于相互循环引用的不可变对象,或者已经序列化到磁盘并在反序列化时需要“流动”的不可变对象特别有用,直到整个反序列化过程完成,此时所有对象可能冻结。

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.

所以你初始化你的对象,然后设置某种“冻结”标志,表明它不再可写。最好,您将更改隐藏在函数后面,以便该函数对于使用您的 API 的客户来说仍然是纯粹的。

回答by bmargulies

Consider four possibilities:

考虑四种可能性:

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");

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.

对我来说,2、3 和 4 中的每一个都适用于不同的情况。由于 OP 引用的原因,第一个很难让人喜欢,并且通常是设计遭受一些蠕变并需要进行一些重构的症状。

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).

当“工厂”后面没有状态时,我列出的(2)是好的,而(3)是有状态时的选择设计。当我不想担心线程和同步时,我发现自己使用 (2) 而不是 (3),而且我不需要担心在生产许多对象时分摊一些昂贵的设置。(3) 另一方面,在实际工作进入工厂建设时调用(从 SPI 设置,读取配置文件等)。

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.

最后,其他人的回答提到了选项 (4),在那里你有很多小的不可变对象,最好的模式是从旧对象中获取新闻。

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.

请注意,我不是“模式粉丝俱乐部”的成员——当然,有些事情值得效仿,但在我看来,一旦人们给他们起了名字和有趣的帽子,他们就会过着自己无益的生活。

回答by ziggystar

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.

您还可以让不可变对象公开看起来像修改器(如 addSibling)的方法,但让它们返回一个新实例。这就是不可变的 Scala 集合所做的。

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.

例如,没有目的地的图边还不是有效的图边。

回答by Carl

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.

另一个潜在的选择是重构以减少可配置字段。如果字段组仅(大部分)彼此工作,请将它们收集到自己的小型不可变对象中。那个“小”对象的构造器/构建器应该更易于管理,这个“大”对象的构造器/构建器也应该更易于管理。

回答by Joren

I use C#, and these are my approaches. Consider:

我使用 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.
}

Option 1.Constructor with optional parameters

选项 1.带有可选参数的构造函数

public Foo(int i = 0, string s = "bla", Bar b = null)
{
    this.i = i;
    this.s = s;
    this.b = b;
}

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.

用作例如new Foo(5, b: new Bar(whatever))。不适用于 4.0 之前的 Java 或 C# 版本。但仍然值得展示,因为它是一个例子,并不是所有的解决方案都是语言不可知的。

Option 2.Constructor taking a single parameter object

选项 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() { }
}

Usage example:

用法示例:

FooParameters fp = new FooParameters();
fp.I = 5;
fp.S = "bla";
fp.B = new Bar();
Foo f = new Foo(fp);`

C# from 3.0 on makes this more elegant with object initializer syntax (semantically equivalent to the previous example):

从 3.0 开始的 C# 使用对象初始值设定项语法使这更加优雅(在语义上等同于前面的示例):

FooParameters fp = new FooParameters { I = 5, S = "bla", B = new Bar() };
Foo f = new Foo(fp);

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.

选项 3:
重新设计您的类,使其不需要如此大量的参数。您可以将其职责划分为多个类别。或者不将参数传递给构造函数,而仅根​​据需要传递给特定方法。并不总是可行的,但如果可行,就值得去做。