当前位置: 首页 > 编程语言 > C#

ReadOnlyCollectionvsLiskov–如何正确建模可变集合的不可变表示Share

时间:2023-04-10 15:37:11 C#

ReadOnlyCollectionvsLiskov–如何正确建模可变集合的不可变表示。据我了解,这会导致ReadOnlyCollection违反Liskov。ICollection的契约公开了Add和Remove操作,但只读子类型不满足此契约。例如,IListcollection=newList();collection=newSystem.Collections.ObjectModel.ReadOnlyCollection(集合);collection.Add(新对象());--不支持的异常显然需要不可变的集合。.NET的建模方法有问题吗?还有什么比这更好的方法呢?IEnumerable很好地公开了一个集合,至少看起来是不可变的。但是,语义非常不同,主要是因为IEnumerable没有显式公开任何状态。在我的特定情况下,我正在尝试构建一个不可变的DAG类来支持FSM。我一开始显然需要AddNode/AddEdge方法,但我不希望它在运行后能够更改状态机。我很难表达DAG的不可变和可变表示之间的相似性。现在,我的设计涉及预先使用DAGBuilder,然后创建一次不可变图,此时它不再是可编辑的。Builder和具体的不可变DAG之间唯一的公共接口是Accept(IVisitorvisitor)。我担心面对可能更简单的选项时,这可能会过度设计/过于抽象。同时,我不能接受我可以在我的GUI上公开方法,如果客户端获得特定实现,这些方法可能会抛出NotSupportedException。处理这个问题的正确方法是什么?您始终可以拥有一个(只读)图形接口并使用可读/写可修改图形接口对其进行扩展:publicinterfaceIDirectedAcyclicGraph{intGetNodeCount();boolGetConnected(intfrom,intto);}publicinterfaceIModifiableDAG:IDirectedAcyclicGraph{voidSetNodeCount(intnodeCount);voidSetConnected(intfrom,intto,boolconnected);}(我不知道如何将这些方法拆分为属性的get/set一半。)//垃圾实现publicclassConcreteModifiableDAG:IModifiableDAG{privateintnodeCount;私人词典>连接;publicvoidSetNodeCount(intnodeCount){this.nodeCount=nodeCount;}publicvoidSetConnected(intfrom,intto,boolconnected)[from][to]=connected;}publicintGetNodeCount(){返回节点数;}publicboolGetConnected(intfrom,intto){returnconnections[from][to];}}//创建图形IModifiableDAGmdag=newConcreteModifiableDAG();mdag.SetNodeCount(5);mdag.SetConnected(1,5,true);//传递固定图IDirectedAcyclicGraphdag=(IDirectedAcyclicGraph)mdag;dag.SetNodeCount(5);//不存在dag。设置连接(1、5、真);//不存在这就是我希望微软对其只读集合类所做的——为获取计数、按索引获取行为等创建接口,并添加接口扩展它以更改值等等我不认为您当前使用构建器的解决方案是过度设计的。它解决了两个问题:违反LSP您有一个可编辑接口,其实现永远不会在AddNode/AddEdge上抛出NotSupportedException,并且您有一个根本没有这些方法的不可编辑接口。时间耦合如果您使用一个接口而不是两个接口,那么一个接口需要以某种方式支持“初始化阶段”和“不可变阶段”,很可能通过某种方式来标记这些阶段的开始和结束。.Net中的只读集合不违反LSP。您似乎对只读集合在调用add方法时抛出不受支持的异常感到困扰,但没有什么特别的。许多类表示可以处于几种状态之一的域对象,并不是每个操作在所有状态下都有效:流只能打开一次,处理后不能显示窗口等。在这些情况下抛出异常只要有效因为有一种方法可以测试当前状态并避免异常。.Net集合旨在支持状态:只读和读/写。这就是IsReadWrite方法存在的原因。它允许调用者测试集合的状态并避免异常。LSP要求子类型服从超类型的契约,但契约不仅仅是方法列表;它是基于对象状态的输入和预期行为的列表:“如果你给我这个输入,当我处于这种状态时,预期会发生什么情况?”ReadOnlyCollection通过在集合状态为只读时抛出不受支持的异常来完全遵守ICollection的约定。请参阅ICollection文档中的异常部分。您可以使用显式接口实现将修改方法与只读版本中所需的操作分开。只读实现上还有一个方法将方法作为参数。这允许您将DAC的构造与导航和查询隔离开来。请参阅下面的代码及其评论://你的只读操作和//允许构建公共接口IDac的方法{IDacBuild(Action>f);//其他导航方法}//可修改的操作,它仍然是一个IDac公共接口IModifiableDac:IDac{voidAddEdge(Titem);IModifiableDacCreateChildNode();}//实现显式实现IModifableDac,因此//修改方法的意外调用不会发生//(需要显式转换为IModifiable)publicclassDac:IDac,IModifiableDac{publicIDacBuild(Action>f){f(this);归还这个;}voidIModifiableDac.AddEdge(Titem){thrownewNotImplementedException();}publicIModifiableDacCreateChildNode(){//创建、添加、子节点并返回它thrownewNotImplementedException();}publicvoidDoStuff(){}}publicclassDacConsumer{publicvoidFoo(){vardac=newDac();//构建你的图表varnewDac=dac.Build(m=>{m.AddEdge(1);varnode=m.CreateChildNode();node.添加边(2);//ETC。});//现在做任何你想做的事,IDac没有修改方法newDac.DoStuff();从这段代码中,用户只能调用Build(Action>m)来访问可修改的版本,方法调用返回一个不可变的方法。如果没有故意显式转换,则不能以IModifiable形式访问它,这在您的对象的合同中没有定义。我喜欢的方式(但也许只有我喜欢)是在界面中使用读取方法,在类本身中使用编辑方法。对于您的DAG,您不太可能有数据结构的多个实现,因此拥有一个用于编辑图形的界面是一种矫枉过正,而且通常不是很漂亮。我发现有一个表示数据结构的类和一个作为读取结构的接口非常干净。例如:publicinterfaceIDAG{publicintNodeCount{get;}publicboolAreConnected(intfrom,intto);公共TGetItem(int节点);}公共类DAG:IDAG{publicvoidSetCount(...){...}publicvoidSetEdge(...){...}publicintNodeCount{get{...}}publicboolAreConnected(...){...}publicTGetItem(...){...}}然后,当您需要编辑结构时,如果您只想要只读结构,则传递类,然后传递接口。这是一个伪造的“只读”,因为您始终可以成为班级演员,但只读永远不会是真的……这使您可以拥有更复杂的读取结构。与Linq一样,您可以使用接口上定义的扩展方法来扩展您的阅读结构。例如:publicstaticclassIDAGExtensions{publicstaticListFindPathBetween(thisIDAGdag,intfrom,intto){//使用回溯来确定`from`和`to`之间是否存在路径}publicstaticIDAGCast(thisIDAGdag){//为DAG类创建一个包装器,将所有T输出转换为U}}这对于将数据结构的定义与“您可以用它做什么”分开很有用。此结构允许的另一件事是将泛型类型设置为T,这允许您对参数类型进行反转。我喜欢首先将我的数据结构设计为不可变的想法。有时这是不可行的,但有一种方法可以使这种情况经常发生。对于您的DAG,您很可能在文件或UI中有一些数据结构,并且您可以将所有节点和边作为IEnumerables传递给不可变DAG类的构造函数。然后,您可以使用Linq方法将源数据转换为节点和边。然后,构造函数(或工厂方法)可以以一种对您的算法有效的方式构建类的私有结构,并使用非循环的前期数据验证。此解决方案与构建器模式的不同之处在于,数据结构的迭代构建是不可能的,但通常并不是真正需要的。就个人而言,我不喜欢具有由同一类实现的用于读和读/写访问的单独接口的解决方案,因为写功能实际上并未隐藏...将实例强制转换为读/写传入接口公开了变异方法.在这种情况下更好的解决方案是使用AsReadOnly方法创建一个真正不可变的数据结构来从中复制数据。以上就是C#学习教程的全部内容:ReadOnlyCollectionvsLiskov-Howtocorrectlymodeltheimmutablerepresentationofmutablecollections。如果对你有用,需要了解更多C#学习教程,希望大家多加关注——本文来自网络收藏,不代表立场,如涉及侵权,请点击有权联系管理员删除。如需转载请注明出处: