使用接口分离读写问题的最佳方法?

时间:2010-11-30 05:59:29

标签: c# .net interface immutability

最近我一直在意识到(一些人会过度使用)不可变对象的好处,以大大减少我的对象模型中的读写依赖性问题及其产生的条件和副作用,最终使代码更简单管理(函数编程类型)。

这种做法促使我创建只读对象,这些对象在创建/构建时提供值,然后只为外部调用者提供公共getter以访问属性。受保护的内部和私有设置器允许在写入对象模型时保持内部控制。

在通过我的对象模型创建API时创建接口时,我开始考虑有关不变性的相同问题。例如,通过在我的接口上仅提供公共getter,并将其留给实现者来决定setter以及如何处理这个方面。

我正在谈论的实现“只读”接口的一个例子是这个有价值的项目(仅用于演示):

public interface IValuableItem {
    decimal Amount {get;}
    string Currency {get;}
}

但是我想知道我应该如何提供一个允许写入(如果我应该)的伴随接口,而不是在同一个界面中组合这些操作,以免“污染”它的不变性

我想到了以下想法,就在我的脑海中。没有提供我认为对每个人的利弊,你认为最好的方法是什么?有没有一种行业中常用的编码方法来管理这个概念?

// companion writer
public interface IValuableModifier {
    decimal Amount {set;}
    string Currency {set;}
}

// explicit methods to enforce importance of or deviance in the programming
public interface IValuableModifier {
    void SetAmount(decimal val);
    void SetCurrency(string cur);
}

// companion writer that inherits the original interface
public interface IValuableModifier : IValuableItem { //...

// Let a concrete class choose one and/or the other.
class Concrete  : IValuableModifer, IValuableItem { //...

等...
还有什么可以帮助我灌输我不可改变的编程模型,并保持适度的灵活性,或者至少将关注点分开以便更好地控制它?

5 个答案:

答案 0 :(得分:10)

我想我可能会使用你的想法的变体,如下所示:

public interface IValuableItem
{
    decimal Amount { get; }
    string Currency { get; }
}

public interface IMutableValuable : IValuableItem
{
    new decimal Amount { set; get; }
    new string Currency { set; get; }
}

class Item : IMutableValuable
{
    public decimal Amount { get; set; }
    public string Currency { get; set; }
}

这样你的可变接口有完整的getter和setter(我觉得有一个接口有setter但没有getter是不合理的),但任何实现它的对象也会有一个不可变的接口版本你可以使用任何纯功能代码。

答案 1 :(得分:4)

你应该为ReadableFoo,ImmutableFoo和MutableFoo提供单独的接口。后两者应该从第一个继承。 ReadableFoo应该包含一个“AsImmutable”方法,它将返回一个保证不可变的Foo(一个不可变的实例应该自己返回;一个可变实例应该返回一个包含其数据的新的不可变实例),并且可能是一个“AsNewMutable”成员(这将创建一个包含相同数据的新的可变实例,无论原始是否可变)。

没有类应该实现ImmutableFoo和MutableFoo。

答案 2 :(得分:3)

如果您的对象是不可变的(并且您围绕不可变数据的概念设计应用程序)那么对象实际上必须保持不变。

在不可变场景中修改数据的规范方法是创建新对象,所以我会建议这样的事情:

public interface IValuableItem<T>
{
    decimal Amount { get; }
    string Currency { get; }
    T CreateCopy(decimal amount, string currency);
}

public class SomeImmutableObject : IValuableItem<SomeImmutableObject>
{
    public decimal Amount { get; private set; }
    public string Currency { get; private set; }

    public SomeImmutableObject(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public SomeImmutableObject CreateCopy(decimal amount, string currency)
    {
        return new SomeImmutableObject(amount, currency);
    }
}

SomeImmutableObject obj = new SomeImmutableObject(123.33m, "GBP");
SomeImmutableObject newObj = obj.CreateCopy(120m, obj.Currency);

答案 3 :(得分:2)

考虑使用构建器模式:构建器对象构造核心对象的不可变实例。 .NET字符串就像这样 - 字符串对象是不可变的,并且有一个StringBuilder类可以有效地构造字符串对象。 (string + string + string的效率远低于使用StringBuilder执行相同的操作)

另请注意,构建器对象仅用于构建目标对象 - 构建器不是目标对象的实例/不自己实现目标接口。

让您的系统在不可变对象上运行是值得的,因为不可变性在线程/并发/并行执行方案以及数据缓存/数据版本控制方案中消除了许多令人头疼的问题。

答案 4 :(得分:1)

我相信结合你的第三和第四选择是一种更好的方式来实现可变的&amp;不可变类型。

Public interface ImmutableItem {
    decimal Amount {get;}
    string Currency {get;}
}

Public interface MutableItem: ImmutableItem {
    decimal Amount {set;}
    string Currency {set;}
}

class Concrete : ImmutableItem  {
    //Only getters
}


class Concrete : MutableItem  {
   //Both getters & setters
}

这很干净,让具体课程决定哪种可变性要暴露给外界。