当需要设计模块化架构时,对象是否是现实的必然?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当需要设计模块化架构时,对象是否是现实的必然?相关的知识,希望对你有一定的参考价值。
通常会读到对象转换是一种不好的做法,应该避免,例如Why should casting be avoided?问题得到了一些很好的论据答案:
- By Jerry Coffin: 更普遍地看待事情,情况非常简单(至少是IMO):演员(显然足够)意味着你正在将某种东西从一种类型转换为另一种类型。当/如果你这样做,它提出了一个问题“为什么?”如果你真的想要一些特定类型的东西,你为什么不把它定义为开始的那种类型?这并不是说从来没有理由进行这样的转换,但是只要它发生,它应该提示你是否可以重新设计代码以便在整个过程中使用正确的类型。
- By Eric Lippert: 两种演员阵容都是红旗。第一种类型的演员提出了一个问题“为什么开发人员知道编译器不知道的东西到底是什么?”如果您处于这种情况,那么更好的做法通常是更改程序,以便编译器确实能够处理现实。那你就不需要演员;分析在编译时完成。 第二种类型的演员提出了“为什么不首先在目标数据类型中进行操作?”的问题。如果你需要一个ints的结果,那你为什么要先拿一个双?你不应该持一个int吗?
继续我的问题,最近我开始研究最初由Mark Seemann开发的着名开源项目qazxsw poi的源代码,我非常感谢。
该库的主要组件之一是接口AutoFixture,它定义了一种不确定的抽象方法:
ISpecimenBuilder
正如您所看到的,请求参数类型是对象,因此它接受完全不同的类型,接口的不同实现按其运行时类型处理不同的请求,检查它是否是有线处理的内容,否则返回某种无响应表示。
似乎界面的设计并不遵循稀疏使用对象投射的“良好实践”。
我想自己是否有更好的方法来设计这个合同,以一种方式击败所有的铸造,但找不到任何解决方案。
显然,对象参数可以用一些标记界面替换,但它不会为我们节省铸造问题,我也认为可以使用访问者模式的一些变化,如object Create(object request, ISpecimenContext context);
所述,但它似乎不是很可扩展,访问者必须拥有许多不同的方法,因为有很多不同的接口实现能够处理不同类型的请求。
虽然我基本同意反对在这个特定场景中使用铸造作为优秀设计的一部分的观点,但它似乎不仅是最佳选择,而且也是唯一现实的选择。
总而言之,当需要设计模块化和可扩展的架构时,对象投射和非常一般的契约是现实的必然吗?
对于任何类型的应用程序或框架,我认为我不能回答这个问题,但我可以提供一个专门讨论AutoFixture的答案,并提供一些关于其他使用场景的猜测。
如果我今天必须从头开始编写AutoFixture,那肯定会有不同的做法。特别是,我不会像here那样设计日常API。相反,我将围绕仿函数和monad的概念设计数据操作API,如ISpecimenBuilder
。
此设计完全基于泛型,但它确实需要在编译时已知的静态类型构建块(也在文章中描述)。
这与outlined here的作品密切相关。编写基于QuickCheck的测试时,必须为所有自定义类型提供生成器。 Haskell不支持运行时转换值,而是完全依赖于泛型和一些编译时自动化。当然,Haskell的泛型比C#更强大,所以你不一定能将从Haskell获得的知识转移到C#。但是,它的确表明,可以完全编写代码,而无需依赖于运行时强制转换。
但是,AutoFixture支持用户定义的类型,而无需用户编写自定义生成器。它通过.NET Reflection完成此操作。在.NET中,Reflection API是无类型的;生成对象和调用成员的所有方法都将QuickCheck作为输入并返回object
作为输出。
任何基于Reflection的应用程序,库或框架都必须执行一些运行时转换。我不知道怎么解决这个问题。
是否有可能在没有Reflection的情况下编写数据生成器?我没有尝试过以下内容,但也许可以采用一种策略,即直接在IL中为数据生成器编写“代码”,并使用Reflection emit动态编译包含生成器的内存中程序集。
这有点像object
的工作方式,IIRC。我想可以围绕这个概念设计其他类型的通用框架,但我很少看到它在.NET中完成。
以上是关于当需要设计模块化架构时,对象是否是现实的必然?的主要内容,如果未能解决你的问题,请参考以下文章