当前位置:   PHOTOSHOP > 正文

C#中的不可变对象模式 - 你怎么看?

c#,design-patterns,functional-programming,immutability,安全,编辑器,DevBox,在线流程图,编程,编程问答,程序员,开发者工具,开发工具,json解析,二维码生成,unix时间戳,在线开发工具,前端开发工具,开发人员工具,站长工具

我在一些项目的过程中开发了一种用于创建不可变(只读)对象和不可变对象图的模式.不可变对象具有100%线程安全的优点,因此可以跨线程重用.在我的工作中,我经常在Web应用程序中使用此模式来配置设置以及我在内存中加载和缓存的其他对象.缓存对象应始终是不可变的,因为您希望保证它们不会意外更改.

现在,您可以轻松地设计不可变对象,如下例所示:

public class SampleElement
{
  private Guid id;
  private string name;

  public SampleElement(Guid id, string name)
  {
    this.id = id;
    this.name = name;
  }

  public Guid Id
  {
    get { return id; }
  }

  public string Name
  {
    get { return name; }
  }
}

这对于简单的类来说很好 - 但是对于更复杂的类,我不喜欢通过构造函数传递所有值的概念.在属性上设置setter是更理想的,构建新对象的代码更容易阅读.

那么如何使用setter创建不可变对象?

好吧,在我的模式中,对象开始是完全可变的,直到你用一个方法调用冻结它们.一旦一个对象被冻结,它将永远保持不变 - 它不能再次变成一个可变对象.如果您需要对象的可变版本,则只需克隆它即可.

好的,现在谈谈一些代码.我在下面的代码片段中试图将模式简化为最简单的形式.IElement是所有不可变对象必须最终实现的基接口.

public interface IElement : ICloneable
{
  bool IsReadOnly { get; }
  void MakeReadOnly();
}

Element类是IElement接口的默认实现:

public abstract class Element : IElement
{
  private bool immutable;

  public bool IsReadOnly
  {
    get { return immutable; }
  }

  public virtual void MakeReadOnly()
  {
    immutable = true;
  }

  protected virtual void FailIfImmutable()
  {
    if (immutable) throw new ImmutableElementException(this);
  }

  ...
}

让我们重构上面的SampleElement类来实现不可变对象模式:

public class SampleElement : Element
{
  private Guid id;
  private string name;

  public SampleElement() {}

  public Guid Id
  {
    get 
    { 
      return id; 
    }
    set
    {
      FailIfImmutable();
      id = value;
    }
  }

  public string Name
  {
    get 
    { 
      return name; 
    }
    set
    {
      FailIfImmutable();
      name = value;
    }
  }
}

现在,只要未通过调用MakeReadOnly()方法将对象标记为不可变,就可以更改Id属性和Name属性.一旦它是不可变的,调用setter将产生一个ImmutableElementException.

最后注意:完整模式比此处显示的代码片段更复杂.它还包含对不可变对象集合的支持以及不可变对象图的完整对象图.完整模式使您可以通过调用最外层对象上的MakeReadOnly()方法将整个对象图变为不可变.一旦您开始使用此模式创建更大的对象模型,泄漏对象的风险就会增加.漏洞对象是在对对象进行更改之前无法调用FailIfImmutable()方法的对象.为了测试泄漏,我还开发了一个通用的泄漏检测器类,用于单元测试.它使用反射来测试所有属性和方法是否将ImmutableElementException抛出为immutable状态.换句话说,这里使用TDD.

我已经逐渐喜欢这种模式,并在其中找到了很大的好处.所以我想知道的是,如果你们中的任何一个人使用类似的模式?如果是,您是否知道记录它的任何好资源?我基本上正在寻找潜在的改进以及可能已存在于此主题的任何标准.



1> Marc Gravell..:

有关信息,第二种方法称为"冰棒不变性".

Eric Lippert从这里开始有一系列关于不变性的博客文章.我仍然在掌握CTP(C#4.0),但看起来很有趣的是可选/命名参数(到.ctor)可以在这里做什么(当映射到只读字段时)... [更新:我发表了博客在这里 ]

有关信息,我可能不会制作这些方法virtual- 我们可能不希望子类能够使其不可冻结.如果你希望他们能够添加额外的代码,我会建议:

[public|protected] void Freeze()
{
    if(!frozen)
    {
        frozen = true;
        OnFrozen();
    }
}
protected virtual void OnFrozen() {} // subclass can add code here.

此外 - AOP(如PostSharp)可能是添加所有ThrowIfFrozen()检查的可行选项.

(如果我更改了术语/方法名称,请道歉 - 在撰写回复时SO不会保留原始帖子)



2> Herms..:

另一种选择是创建某种Builder类.

例如,在Java(以及C#和许多其他语言)中,String是不可变的.如果要执行多个操作来创建String,可以使用StringBuilder.这是可变的,然后一旦你完成,你就会让它返回给你最后的String对象.从那时起,它是不可改变的.

您可以为其他课程做类似的事情.你有不可变元素,然后是ElementBuilder.所有构建器都会存储您设置的选项,然后在完成它时构造并返回不可变元素.

这是一个更多的代码,但我认为它比在一个应该是不可变的类的setter上更清晰.



3> Konrad Rudol..:

在我最初System.Drawing.Point对每次修改都要创建一个新内容的事情感到不安之后,几年前我完全接受了这个概念.实际上,我现在readonly默认创建每个字段,只有在有令人信服的理由时才将其更改为可变 - 这种情况很少令人惊讶.

我不太关心跨线程问题(我很少使用相关的代码).由于语义表达,我发现它更好,更好.不可变性是一个很难错误使用的界面的缩影.



4> Cory Foy..:

您仍在处理状态,因此如果您的对象在变为不可变之前并行化,则仍然可能被咬住.

更实用的方法可能是使用每个setter返回对象的新实例.或者创建一个可变对象并将其传递给构造函数.



5> Charles Bret..:

(相对)新的软件设计范例称为域驱动设计,它区分实体对象和值对象.

实体对象被定义为必须映射到持久性数据存储中的密钥驱动对象的任何内容,例如员工,客户端或发票等......其中更改对象的属性意味着您需要将更改保存到某个地方的数据存储中,并且具有相同"键"的类的多个实例的存在意味着需要同步它们,或者将它们的持久性协调到数据存储,以便一个实例的更改不会覆盖其他实例.更改实体对象的属性意味着您正在更改有关该对象的内容 - 而不是更改您引用的WHICH对象...

值对象otoh是可以被视为不可变的对象,其效用严格按其属性值定义,并且多个实例不需要以任何方式协调...如地址,电话号码或轮子在汽车上,或文件中的字母......这些东西完全由它们的属性定义......文本编辑器中的大写"A"对象可以与整个文档中的任何其他大写"A"对象透明地互换,你不需要一把钥匙来区别于其他所有'A'在这个意义上它是不可变的,因为如果你把它改成'B'(就像更改电话号码对象中的电话号码字符串一样,你不是更改与某个可变实体关联的数据,您将从一个值切换到另一个值...就像你改变一个字符串的值一样......

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/PHOTOSHOP/detail/15448
推荐阅读
相关标签
  

闽ICP备14008679号