当前位置:  开发笔记 > 编程语言 > 正文

被歧视的联盟与公开关闭原则发生冲突

如何解决《被歧视的联盟与公开关闭原则发生冲突》经验,为你挑选了2个好方法。

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

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

我经常避免使用switch语句,因为我经常被迫处理最初没有考虑的案例.因此,我发现自己必须使用新案例和一些相对行为来更新每个引用.

因此,我仍然认为,Discriminated Unions具有与switch-statements相同的代码味道.

我的想法准确吗?

为什么switch语句不受欢迎,但被歧视的联盟被接受了?

使用Discriminated Unions时是否会遇到与我们在代码库演变或离题时执行switch语句相同的维护问题?



1> kvb..:

对象和受歧视的联盟具有彼此双重的限制:

使用接口时,很容易添加实现接口的新类而不影响其他实现,但很难添加新方法(即,如果添加新方法,则需要将方法的实现添加到实现接口的每个类).

在设计DU类型时,很容易使用该类型添加新方法而不影响其他方法,但很难添加新案例(即,如果添加新案例,则需要更新每个现有方法来处理它).

因此,DUs绝对不适合对每个问题进行建模; 但传统的OO设计也不是.通常,你知道你需要在哪个"方向"进行未来的修改,所以它很容易选择(例如列表肯定是空的或者有头和尾,所以通过DU建模它们是有意义的).

有时您希望能够在两个方向上扩展(添加新的"种类"对象并添加新的"操作") - 这与表达式问题有关,并且在经典OO编程中没有特别干净的解决方案或经典的FP编程(虽然有点巴洛克式的解决方案是可能的,例如见的Vesa Karvonen的评论在这里,我已经向音译F#这里).

可以看到DU比switch语句更有利的一个原因是F#编译器对穷举和冗余检查的支持可能比C#编译器检查switch语句更彻底(例如,如果我有match x with | A -> 'a' | B -> 'b',我添加一个新的DU情况C然后我会得到一个警告/错误,但是当enum在C#中使用时我需要有一个default案例,所以编译时检查不能那么强.



2> Grundoon..:

在我看来,开放/封闭原则有点模糊 - "开放扩展"究竟意味着什么?

这是指用新数据扩展,还是用新行为扩展,或两者兼而有之?

以下是Betrand Meyer的引用(取自维基百科):

类是关闭的,因为它可以被编译,存储在库中,基线化并由客户端类使用.但它也是开放的,因为任何新类都可以将它用作父级,添加新功能.定义后代类时,无需更改原始内容或干扰其客户端.

以下是罗伯特·马丁的文章引用:

开放封闭原则以非常直接的方式对此进行攻击.它说你应该设计永不改变的模块.当需求发生变化时,您可以通过添加新代码来扩展此类模块的行为,而不是通过更改已经运行的旧代码.

我从这些引言中剔除的是强调永不打破依赖于你的客户.

在面向对象的范例(基于行为)中,我将其解释为使用接口(或抽象基类)的建议.然后,如果需求发生更改,您可以创建现有接口的新实现,或者,如果需要新行为,则创建一个扩展原始接口的新接口.(顺便说一句,switch语句不是OO - 你应该使用多态!)

在功能范例中,从设计的角度来看,接口的等价物是一个功能.就像在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公开给我无法控制的客户端的代码,我会使用主动模式方法.

推荐阅读
ar_wen2402851455
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有