我不禁要问,在大型系统中使用区分联盟是否违反了开放/关闭原则。

我了解“打开/关闭”原理是面向对象的,而不是功能性的。但是,我有理由相信存在相同的代码气味。

我经常避免使用switch语句,因为通常我不得不处理最初没有考虑的情况。因此,我发现自己不得不用一个新案例和一些相对行为来更新每个参考。

因此,我仍然相信歧视联盟具有与切换语句相同的代码气味。

我的想法正确吗?

为什么对转换声明不屑一顾,却接受歧视联盟?

我们是否在使用区分联盟时遇到了与维护声明一样的问题,而随着代码库的发展或转移而进行切换语句时又遇到了同样的问题?

最佳答案

在我看来,开放/封闭原则有点模糊-“开放扩展”实际上是什么意思?

这是否意味着使用新数据扩展或使用新行为扩展,或两者兼而有之?

这是贝特兰·迈耶(Betrand Meyer)的报价(摘自Wikipedia):


一个类是封闭的,因为它可以被编译,存储在库中,作为基线并由客户端类使用。
但是它也是开放的,因为任何新类都可以将其用作父类,从而增加新功能。
定义后代类时,无需更改原始类或打扰其客户。


这是罗伯特·马丁(Robert Martin)的article的一句话:


开闭原理以一种非常直接的方式对此进行了攻击。它说你应该设计
永不改变的模块。当需求改变时,您可以扩展这种行为
通过添加新代码而不是更改已经起作用的旧代码来创建模块。


我从这些引用中脱颖而出的是,强调永不中断依赖您的客户。

在面向对象的范式(基于行为)中,我会将其解释为使用接口(或抽象基类)的建议。然后,如果
需求变更时,您可以创建现有接口的新实现,或者,如果需要新的行为,请创建一个新的扩展接口
原来的一个。 (顺便说一句,switch语句不是OO-you should be using polymorphism!)

在功能范式中,从设计的角度来看,等效的接口是一种功能。就像您将接口传递给OO设计中的对象一样,
您会将一个函数作为参数传递给FP设计中的另一个函数。
此外,在FP中,每个功能签名都会自动成为一个“接口”!该功能的实现可以在以后更改,只要
其功能签名不变。

如果确实需要新的行为,则只需定义一个新功能-旧功能的现有客户端将不会受到影响,而客户端
需要此新功能的用户将需要进行修改以接受新参数。

扩展DU

现在,在更改F#中DU的要求的特定情况下,您可以扩展它而不用两种方式影响客户端。


使用合成从旧的数据类型构建新的数据类型,或者
从客户端隐藏案例并使用活动模式。


假设您有一个简单的DU,如下所示:

type NumberCategory =
    | IsBig of int
    | IsSmall of int


您想添加一个新案例IsMedium

在合成方法中,您将创建一个新类型而不接触旧类型,例如:

type NumberCategoryV2 =
    | IsBigOrSmall of NumberCategory
    | IsMedium of int


对于只需要原始NumberCategory组件的客户端,可以将新类型转换为旧类型,如下所示:

// 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


哪种方法最好?

好吧,对于我完全控制的代码,我不会使用-只是更改DU并修复编译器错误!

对于作为API公开给我不控制的客户端的代码,我将使用主动模式方法。

关于design-patterns - 受歧视的工会与开放封闭原则是否冲突?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34597685/

10-10 01:15