2013-02-26 68 views
5

假设该场景不允许实现不可变类型。在这个假设之后,我想就如何正确地设计一种在消费之后变得不可变的类型的意见/例子。设计一个可变类,消耗后变为不可变

public class ObjectAConfig { 

    private int _valueB; 
    private string _valueA; 
    internal bool Consumed { get; set; } 

    public int ValueB { 
    get { return _valueB; } 
    set 
    { 
     if (Consumed) throw new InvalidOperationException(); 
     _valueB = value; 
    } 
    } 

    public string ValueA { 
    get { return _valueA; } 
    set 
    { 
     if (Consumed) throw new InvalidOperationException(); 
     _valueA = value; 
    } 
    } 
} 

ObjectA消耗ObjectAConfig

public ObjectA { 

    public ObjectA(ObjectAConfig config) { 

    _config = config; 
    _config.Consumed = true; 
    } 
} 

我不满意,这只是工作,我想知道是否有一个更好的模式(排除在外,因为说着,做ObjectAConfig不变的从开始设计)。

例如:

  • 可以使感限定像Once<T>一个单子在允许包裹值仅被初始化一次?

  • 可以合理的定义一个返回类型本身改变私人领域的类型?即你可以冻结它 -

+0

*为什么*你不能实现一个不可变的类型?如果你解释了你正在尝试解决的真正问题,它会有所帮助。 – 2013-02-26 12:12:02

+0

[这个问题](http://stackoverflow.com/questions/4168382)有一些有趣的模式做类似的事情。 – 2013-02-26 12:18:29

回答

10

什么,你有时会执行名为“popsicle immutability”下进入。你目前的方法将工作 - 事实上,我在很多地方都使用这种模式。

你可以很可能是通过类似减少一些重复:

private void SetField<T>(ref T field, T value) { 
    if (Consumed) throw new InvalidOperationException(); 
    field = value; 
} 
public int ValueB { 
    get { return _valueB; } 
    set { SetField(ref _valueB, value); } 
}  
public string ValueA { 
    get { return _valueA; } 
    set { SetField(ref _valueA, value); } 
} 

还有一个相关的方法,但:一个建设者。例如,把你现有的类:

public interface IConfig 
{ 
    string ValueA { get; } 
    int ValueB { get; } 
} 
public class ObjectAConfig : IConfig 
{ 
    private class ImmutableConfig : IConfig { 
     private readonly string valueA; 
     private readonly int valueB; 
     public ImmutableConfig(string valueA, int valueB) 
     { 
      this.valueA = valueA; 
      this.valueB = valueB; 
     } 
    } 
    public IConfig Build() 
    { 
     return new ImmutableConfig(ValueA, ValueB); 
    } 
    ... snip: implementation of ObjectAConfig 
} 

在这里有一个真正不变的实施IConfig,和你原来的执行。如果您想要冻结版本,请致电Build()

+0

+1 @Marc Gravell,我很欣赏样品;但我更加欣赏这种模式作为一个具有坚实基础概念的名称。我也会潜入E.Lippert的文章。 – jay 2013-02-26 12:29:10

+0

对于冰棍免疫是线程安全的,所有对象设置器和冻结方法都必须使用锁定或其他方式来确保对象在被修改时不能被冻结[如果一个对象被修改,可能会使用互锁基元愿意有不恰当的线程使用会触发'freeze'方法中的异常]。请注意,即使一个类没有宣告自己是线程安全的,但它应该保证,如果一个实例已经报告自己是不可变的,并且已经报告了它的状态的任何方面,那么这个方面将永远不会改变。 – supercat 2013-03-12 17:47:43

+0

@supercar在许多情况下,假设某些东西会在一个孤立的区域中存在,冷冻,然后***只会暴露于多个线程是合理的。 – 2013-03-12 18:53:49