代码之家  ›  专栏  ›  技术社区  ›  jonp

不变类结构设计

  •  5
  • jonp  · 技术社区  · 14 年前

    因此,我们都认识到不可变类型的好处,特别是在多线程场景中。(或者至少我们都应该认识到这一点;请参见例如System.String。)

    然而,我还没有看到的是很多讨论 创建

    例如,假设我们希望具有以下不可变类:

    class ParagraphStyle {
        public TextAlignment Alignment {get;}
        public float FirstLineHeadIndent {get;}
        // ...
    }
    

    我见过的最常见的方法是拥有可变/不可变类型的“对”,例如可变 List<T> ReadOnlyCollection<T> 类型还是可变的 StringBuilder String 类型。

    要模仿这种现有模式,需要引入某种类型的“可变” ParagraphStyle 键入“复制”成员的类型(以提供setter),然后提供 段落风格

    // Option 1:
    class ParagraphStyleCreator {
        public TextAlignment {get; set;}
        public float FirstLineIndent {get; set;}
        // ...
    }
    
    class ParagraphStyle {
        // ... as before...
        public ParagraphStyle (ParagraphStyleCreator value) {...}
    }
    
    // Usage:
    var paragraphStyle = new ParagraphStyle (new ParagraphStyleCreator {
        TextAlignment = ...,
        FirstLineIndent = ...,
    });
    

    因此,这是可行的,支持IDE中的代码完成,并使如何构造东西变得相当明显……但它看起来确实相当重复。

    有更好的办法吗?

    例如,C#匿名类型是不可变的,允许使用“普通”属性设置器进行初始化:

    var anonymousTypeInstance = new {
        Foo = "string",
        Bar = 42;
    };
    anonymousTypeInstance.Foo = "another-value"; // compiler error
    

    不幸的是,在C#中复制这些语义的最接近的方法是使用构造函数参数:

    // Option 2:
    class ParagraphStyle {
        public ParagraphStyle (TextAlignment alignment, float firstLineHeadIndent,
                /* ... */ ) {...}
    }
    

    但这并不能很好地“扩展”;如果你的类型有15个属性,那么一个有15个参数的构造函数绝对不是友好的,为所有15个属性提供“有用”的重载是一个噩梦。我完全拒绝这个。

    // Option 3:
    class ParagraphStyle {
        bool alignmentSet;
        TextAlignment alignment;
    
        public TextAlignment Alignment {
            get {return alignment;}
            set {
                if (alignmentSet) throw new InvalidOperationException ();
                alignment = value;
                alignmentSet = true;
            }
        }
        // ...
    }
    

    Commit() 方法,以便对象可以知道开发人员已完成属性设置(因此,如果调用setter,则会导致以前未设置的所有属性被抛出),但这似乎是一种让事情变得更糟而不是更好的方法。

    有比可变/不可变类拆分更好的设计吗?还是我注定要处理会员重复?

    1 回复  |  直到 14 年前
        1
  •  2
  •   olegz    13 年前

    在几个项目中,我使用了流畅的方法。也就是说,大多数通用属性(例如名称、位置、标题)是通过ctor定义的,而其他属性是通过返回新的不可变实例的Set方法更新的。

    class ParagraphStyle {
      public TextAlignment Alignment {get; private set;}
      public float FirstLineHeadIndent {get; private set;}
      // ...
      public ParagraphStyle WithAlignment(TextAlignment ta) {
          var newStyle = (ParagraphStyle)MemberwiseClone();
          newStyle.TextAlignment = ta;
      }
      // ...
    }