6

我想让我的类不可变。明显的方法是将所有字段声明为get; private set;并在构造函数中初始化所有字段。因此客户端必须在构造函数中提供所有内容。问题是当有大约 10 个或更多字段在构造函数中传递它们时变得非常不可读,因为每个字段都没有标签。

例如,这是非常易读的:

info = new StockInfo
        {
            Name = data[0] as string,
            Status = s,
            LotSize = (int)data[1],
            ISIN = data[2] as string,
            MinStep = (decimal)data[3]
        };

与此相比:

new StockInfo(data[0] as string, s, (int) data[1], data[2] as string, (decimal) data[3])

现在成像我有 10 个或更多参数。

那么我怎样才能使类不可变保存可读性呢?

我建议在使用构造函数时只使用相同的格式:

info = new StockInfo(
            data[0] as string,           // Name
            s,                           // Status
            (int)data[1],                // LotSize
            data[2] as string,           // ISIN
            (decimal)data[3]             // MinStep
       );

你能推荐一些更好的东西吗?

4

6 回答 6

8

这是使用 C# 的命名参数的方法:

var info = new StockInfo
(
    Name: data[0] as string,
    Status: s,
    LotSize: (int)data[1],
    ISIN: data[2] as string,
    MinStep: (decimal)data[3]
);
于 2012-08-14T15:22:21.160 回答
8

这里有一些选项。您必须决定什么最适合您:

使用带有命名参数的经典不可变对象(具有大量构造函数)以提高可读性。(缺点:有些人不赞成有许多构造函数参数。在不支持命名参数的情况下,从其他 .NET 语言中使用可能会很不方便。)

info = new StockInfo
           (
             name: data[0] as string,
             status: s,
             ...
           )

通过不可变接口公开可变对象。(缺点:对象仍然可以通过强制转换来改变。要编写的额外类型。)

public interface IStockInfo
{
   string Name { get; }
   string Status { get; }
}

IStockInfo info = new StockInfo
                      {
                          Name = data[0] as string,
                          Status = s,
                          ...
                      }

公开可变对象的只读视图ReadOnlyCollection<T>-例如参见。(缺点:要实现的额外类型。创建了额外的对象。额外的间接。)

var readOnlyInfo = new ReadOnlyStockInfoDecorator(info);

公开可变对象的不可变克隆。(缺点:要实现的额外类型。创建了额外的对象。需要复制。)

var immutableInfo = new ImmutableStockInfo(info);

使用可冻结的对象。(缺点:冻结后的突变尝试直到执行时才会被捕获。)

info.Freeze();
info.Name = "Test"; // Make this throw an exception.

使用流利风格的构建器或类似的(缺点:有些人可能不熟悉该模式。要编写大量额外代码。创建大量副本。中间状态可能是非法的)

info = StockInfo.FromName(data[0] as string)
                .WithStatus(s) // Make this create a modified copy
                .WithXXX() ;
于 2012-08-14T15:32:43.240 回答
6

不,这是不可能的。要么你有不可变的对象,要么你想要修改对象的能力。

  1. 您可以使用命名参数。
  2. 您可以考虑传递其他对象(和组参数),以便一个对象仅包含在某种程度上非常相似的参数。

查看您的代码,我可能还建议您先提取参数,而不是传递像data[0] as string您使用string stockName = data[0] as string;然后使用的东西stockName。这应该使您的代码更具可读性。

如果您将这么多参数传递给对象的构造函数,那么修改您的设计可能是一个好主意。您可能违反了单一职责原则

于 2012-08-14T15:21:40.300 回答
1

那么我怎样才能使类不可变保存可读性呢?

您可以使用命名参数

info = new StockInfo(
        name: data[0] as string,
        status: s,              
        lotSize: (int)data[1],  
        isin: data[2] as string,
        minStep: (decimal)data[3]
   );

请注意,使用对象初始值设定项的目标不是可读性——它们不应被视为构造函数的替代品。始终在构造函数中包含正确初始化类型所需的每个参数是一个非常好的主意。不可变类型必须在构造期间通过构造函数或工厂方法传入所有参数。

对象初始化器永远不会与不可变类型一起使用,因为它们通过在构造函数之后设置值来工作。

于 2012-08-14T15:23:26.927 回答
0

更多可能的解决方案:

约定俗成的不变性

该对象是可变的,您只需表现良好,并且在设置后永远不会更改它。这对于大多数用途来说是完全不合适的(任何对象一开始是公开的),但对于内部“工人”对象来说可以很好地工作,因为它们使用的地方数量有限(因此你可以使用的地方数量有限)搞砸并改变它们)。

更深层次

假设你的真实类有超过 5 个字段(不是那么难读,特别是如果你有一个带有工具提示的 IDE),有些可能是可组合的。例如,如果您在同一个类中有名称、地址和纬度和经度的不同部分,您可以将其分解为名称、地址和坐标类。

在某些此类情况下发生的一个好处是,如果您有很多(我的意思是很多,这是值得的,少于几千个,这是浪费时间)这样的对象,并且有一些这样的字段之间是相同的它们,您有时可以以这样一种方式构建它们,即这些共享值在每种情况下都具有相同的对象,而不是不同的相同对象 - 所有可能因别名而出错的事情都不会发生,因为它们毕竟是不可变的。

生成器类

例子是StringBuilderUriBuilder。在这里,您恰好遇到了问题-您想要不变性的好处,但是至少在某些时候,您希望能够在多个步骤中构造对象。

因此,您创建了一个不同的可变类,它具有等效的属性,但具有 setter 和 getter,以及其他变异方法(Append()当然,类似的东西是否有意义取决于类),以及构造不可变类实例的方法.

我已经将它用于其构造函数具有多达 30 个参数的类,因为确实有 30 条不同的信息属于同一问题的一部分。在这种情况下,我调用构造函数的唯一地方是在相应的构建器类中。

于 2012-08-14T16:30:50.633 回答
0

我建议为“可能可变”的对象提供一个接口可能会有所帮助,其中包括AsMutableAsNewMutableAsImmutable方法。不可变对象可以AsImmutable通过简单地返回自身来实现。可变对象应AsImmutable通过返回使用可变对象作为构造函数参数创建的新不可变对象或已知等效的不可变对象来实现。不可变对象的构造函数应该使用原始内容加载新对象,但调用AsImmutable所有可能可变的字段。

可变对象应该简单地返回自身以响应AsMutable,而不可变对象应该构造一个新的“浅层”可变对象。如果代码希望改变一个由“可能可变”属性引用的对象,它应该将该属性设置为它的AsMutable等价物。

在不可变对象上调用该AsNewMutable方法的行为应该类似于AsMutable. 在可变对象上调用它可以表现得等同于AsImmutable.AsMutable,或者可以创建任何嵌套可变对象的可变克隆(有时两种方法都可能更好,这取决于最终会改变哪些嵌套对象)。

If you use this pattern, you should be able to reap many of the benefits of both immutable objects (most notably, the ability to take a snap shot of a "deep" object without needing to make a "deep" copy), and mutable ones (being able to produce an object by performing many steps on the same instance). Performance may be enhanced by having each a mutable object keep a reference to an immutable object whose state was at some time identical to its own; after constructing an immutable instance, the object could check whether it matches the other and, if so, discard the new instance and return the old one. While this would seem to represent extra work, it could in fact improve performance considerably in the scenario where a mutable object has AsImmutable called upon it more than once between mutations. If one calls AsImmutable twice on a deep tree structure when most of the tree has in fact not been mutated, having the non-mutated parts of the tree return the same object instances both times would facilitate future comparisons.

Note: If one uses this pattern, one should override GetHashCode and Equals for the deeply-immutable type, but not the mutable one. Immutable objects which hold identical values should be considered interchangeable and thus equivalent, but mutable objects should not be equivalent to anything but themselves regardless of their values. Note also that some care may be needed if objects hold anything of type double, float, or Decimal, since those types override Object.Equals to mean something other than equivalence.

于 2012-08-14T17:05:36.817 回答