ReadOnlyCollection vs Liskov - 如何正确建模可变集合的不可变表示

sma*_*man 23 c# liskov-substitution-principle immutability readonly-collection directed-acyclic-graphs

Liskov替换原则要求子类型必须满足超类型的契约.根据我的理解,这将导致ReadOnlyCollection<T>违反Liskov. ICollection<T>合同公开AddRemove运营,但只读子类型不符合本合同.例如,

IList<object> collection = new List<object>();
collection = new System.Collections.ObjectModel.ReadOnlyCollection<object>(collection);
collection.Add(new object());

    -- not supported exception
Run Code Online (Sandbox Code Playgroud)

显然需要不可变的集合.有没有关于.NET的建模方法的事情?有什么更好的方法呢? IEnumerable<T>在一个集合中做得很好,至少看起来是不可改变的.但是,语义非常不同,主要是因为IEnumerable没有明确地暴露任何状态.

在我的特定情况下,我正在尝试构建一个不可变的DAG类来支持FSM.我显然在开始时需要AddNode/ AddEdge方法,但我不希望它一旦运行就可以更改状态机.我很难表示DAG的不可变和可变表示之间的相似性.

现在,我的设计涉及预先使用DAG Builder,然后创建一次不可变图,此时它不再可编辑.Builder和具体的不可变DAG之间唯一的通用接口是Accept(IVisitor visitor).我担心,面对可能更简单的选择,这可能是过度设计/过于抽象.与此同时,我无法接受我可以在我的图形界面上公开可能NotSupportedException在客户端获得特定实现时抛出的方法.处理这个问题的正确方法是什么?

Raw*_*ing 10

您可以始终拥有(只读)图形界面,并使用读/写可修改图形界面对其进行扩展:

public interface IDirectedAcyclicGraph
{
    int GetNodeCount();
    bool GetConnected(int from, int to);
}

public interface IModifiableDAG : IDirectedAcyclicGraph
{
    void SetNodeCount(int nodeCount);
    void SetConnected(int from, int to, bool connected);
}
Run Code Online (Sandbox Code Playgroud)

(我不能弄清楚如何分割这些方法进get/ set属性的两半).

// Rubbish implementation
public class ConcreteModifiableDAG : IModifiableDAG
{
    private int nodeCount;
    private Dictionary<int, Dictionary<int, bool>> connections;

    public void SetNodeCount(int nodeCount) {
        this.nodeCount = nodeCount;
    }

    public void SetConnected(int from, int to, bool connected) {
        connections[from][to] = connected;
    }

    public int GetNodeCount() {
        return nodeCount;
    }

    public bool GetConnected(int from, int to) {
        return connections[from][to];
    }
}

// Create graph
IModifiableDAG mdag = new ConcreteModifiableDAG();
mdag.SetNodeCount(5);
mdag.SetConnected(1, 5, true);

// Pass fixed graph
IDirectedAcyclicGraph dag = (IDirectedAcyclicGraph)mdag;
dag.SetNodeCount(5);          // Doesn't exist
dag.SetConnected(1, 5, true); // Doesn't exist
Run Code Online (Sandbox Code Playgroud)

这就是我希望Microsoft用他们的只读集合类做的 - 为get-count,get-by-index行为等创建一个接口,并通过添加,更改值等接口扩展它.


Dan*_*rth 3

我不认为您当前与构建器的解决方案是过度设计的。

它解决了两个问题:

  1. 违反 LSP
    您有一个可编辑的接口,其实现永远不会在/NotSupportedException上抛出 s ,并且您有一个根本没有这些方法的不可编辑的接口。AddNodeAddEdge

  2. 时间耦合
    如果您使用一个接口而不是两个接口,那么该接口将需要以某种方式支持“初始化阶段”和“不可变阶段”,很可能通过某种方法标记这些阶段的开始和可能的结束。

  • 我也不认为我当前的解决方案设计过度。问题是,我也不认为有人认为他们的解决方案是过度设计的。不过还是谢谢。 (2认同)