Design patterns 受歧视的工会是否与开合原则相冲突
我不禁怀疑,在一个大系统中使用歧视性工会是否违反了开放/封闭原则 我理解开/关原则是面向对象的,而不是功能性的。然而,我有理由相信同样的代码气味也存在 我经常避免使用switch语句,因为我通常被迫处理最初没有说明的案例。因此,我发现自己必须用一个新的案例和一些相关的行为来更新每个引用 因此,我仍然相信,受歧视的工会与switch语句具有相同的代码气味 我的想法准确吗 为什么不赞成交换声明,但接受歧视的工会Design patterns 受歧视的工会是否与开合原则相冲突,design-patterns,f#,discriminated-union,Design Patterns,F#,Discriminated Union,我不禁怀疑,在一个大系统中使用歧视性工会是否违反了开放/封闭原则 我理解开/关原则是面向对象的,而不是功能性的。然而,我有理由相信同样的代码气味也存在 我经常避免使用switch语句,因为我通常被迫处理最初没有说明的案例。因此,我发现自己必须用一个新的案例和一些相关的行为来更新每个引用 因此,我仍然相信,受歧视的工会与switch语句具有相同的代码气味 我的想法准确吗 为什么不赞成交换声明,但接受歧视的工会 当代码库发展或偏离时,我们是否会遇到使用歧视联合的维护问题,就像我们切换语句一样 对象和
当代码库发展或偏离时,我们是否会遇到使用歧视联合的维护问题,就像我们切换语句一样 对象和受歧视的联合具有相互双重的限制:
- 在使用接口时,添加实现接口的新类很容易,而不会影响其他实现,但添加新方法很难(即,如果添加新方法,则需要将该方法的实现添加到实现接口的每个类)
- 在设计DU类型时,很容易使用该类型添加新方法,而不会影响其他方法,但很难添加新案例(即,如果添加新案例,则需要更新所有现有方法来处理它)
DU可能比switch语句更受欢迎的一个原因是F#编译器对穷举性和冗余检查的支持可能比C#编译器对switch语句的检查更彻底(例如,如果我有
将x与|A->'A'| B->'B'
匹配,并且我添加了一个新的DU caseC
那么我将得到一个警告/错误,但是在C#中使用枚举
时,我仍然需要一个默认值
case,这样编译时检查就不会那么强).我不确定你用OO实现开闭原则的方法是什么,但我通常会通过使用来实现这样的原则性代码,我采用的另一种方法是使用接口。我倾向于避免基类
对于DU,您可以使用相同的方法,方法是在其他更硬编码的有用情况(如以下情况)之上添加一个open for extension case,其中有一个functor作为参数:
类型案例=
|弦的情况1
|int案例2
|IFoo案例3
|打开机箱(单元->T)
当使用OpenCase时,您可以传递一个函数,该函数特定于您实例化此判别联合值的站点
为什么不赞成交换声明,但接受歧视的工会
您可以将DU与模式匹配进行比较,因此我将尝试澄清:
模式匹配是一种代码构造(如开关
),而DU是一种类型构造(如类或结构的封闭层次结构或枚举)
在F#中使用match
的模式匹配比在C#中使用switch
的能力更强
当代码库发展或偏离时,我们是否会遇到使用歧视联合的维护问题,就像我们切换语句一样
与模式匹配一起使用的区分联合比常规switch语句具有更多的类型安全性/穷尽性属性,编译器更有用,因为它将对不完整的匹配发出警告,而这是使用C#中的switch语句无法得到的
您可能会对OO代码有维护方面的顾虑,它是开放-关闭原则,我觉得DU与此无关。在我看来,开放/关闭原则有点模糊——“开放-扩展”实际上是什么意思 它是指用新数据扩展,还是用新行为扩展,或者两者兼而有之 以下是来自Betrand Meyer(摘自): 类是封闭的,因为它可以被编译、存储在库中、基线化并由客户机类使用。 但它也是开放的,因为任何新类都可以使用它作为父类,添加新功能。 定义子类时,无需更改原始类或干扰其客户端。 这里引用罗伯特·马丁的一句话: 开闭原则以一种非常简单的方式来攻击这一点,它说你应该设计 永不改变的模块。当需求改变时,您可以扩展这些模块的行为 通过添加新代码,而不是更改已经运行的旧代码来创建模块 我从这些引语中得到的是强调永远不要破坏依赖你的客户 在面向对象的范例(基于行为)中,我将其解释为使用接口(或抽象基类)的建议 当需求发生变化时,您可以创建现有接口的新实现,或者,如果需要新的行为,可以创建扩展的新接口 原来的一个。(顺便说一句,switch语句不是OO-!) 在功能范式中,从设计的角度来看,接口的等价物是一个函数。就像在OO设计中将接口传递给对象一样, 您可以将一个函数作为参数传递给中的另一个函数
type NumberCategory =
| IsBig of int
| IsSmall of int
type NumberCategoryV2 =
| IsBigOrSmall of NumberCategory
| IsMedium of int
// convert from NumberCategoryV2 to NumberCategory
let toOriginal (catV2:NumberCategoryV2) =
match catV2 with
| IsBigOrSmall original -> original
| IsMedium i -> IsSmall i
type NumberCategory =
private // now private!
| IsBig of int
| IsSmall of int
let createNumberCategory i =
if i > 100 then IsBig i
else IsSmall i
// active pattern used to extract data since type is private
let (|IsBig|IsSmall|) numberCat =
match numberCat with
| IsBig i -> IsBig i
| IsSmall i -> IsSmall i
type NumberCategory =
private
| IsBig of int
| IsSmall of int
| IsMedium of int // new case added
let createNumberCategory i =
if i > 100 then IsBig i
elif i > 10 then IsMedium i
else IsSmall i
// active pattern used to extract data since type is private
let (|IsBig|IsSmall|) numberCat =
match numberCat with
| IsBig i -> IsBig i
| IsSmall i -> IsSmall i
| IsMedium i -> IsSmall i // compatible with old definition