首页 > 解决方案 > 具有受保护创建和公共读取访问权限的可区分联合的功能设计模式是什么?

问题描述

可区分联合通常用作数据持有者并提供有关它们所持有内容的信息,但有时我发现自己需要防止创建可区分联合,但仍然能够使用熟悉的语法对其进行模式匹配。

为了论证,假设我们用一个字符串表示一个 URI,但我想创建一个有保证的经过验证的 URI 的类型(即,它根据 RFC 是有效的),它也是一个字符串。仅使用 Some/None 在这里不起作用,因为我仍然想访问任何无效字符串。此外,我喜欢对当前代码库进行温和的重构体验(在多行代码中用新的单例联合替换现有的单例联合比使用多案例联合要容易得多)。

我可以按如下方式解决这个问题,我认为这表明了我打算做什么(为简单起见,省略了错误情况):

[<AutoOpen>]
module VerifiedUriModule =
    module VerifiedUri =
        type VerifiedUri = 
            private 
            | VerifiedUri of string

        let create uri = VerifiedUri uri  // validation and error cases go here

        let tryCreate uri = Some <| VerifiedUri uri  // or here

        let get (VerifiedUri uri) = uri

    let (|VerifiedUri|) x =
        VerifiedUri.get x

的额外级别AutoOpen只是允许使用活动识别器的无限制访问。

我最终可能会使用一个典型的Result类型,但我想知道这是否是一种典型的编码实践,或者每当我发现自己在做这样的事情时,我是否应该在脑海中听到一个声音在说“回滚,回滚!”,因为我我违反了经典的函数式编程原则(是吗?)。

我意识到这是一种信息隐藏的情况,它看起来很像用数据模仿 OO 类行为。典型的 F#'ish 方法是什么(除了创建具有私有 ctor 的类)?


编辑 2019-12-10:现在正在讨论这个问题,以便将其作为语言功能包含在 F# 中。如果您认为它应该在 :) 中,请投票。

标签: design-patternsf#abstract-data-typediscriminated-union

解决方案


在相当普遍的意义上,我认为您描述的模式是抽象数据类型- 这不是特定 F# 实现的名称,但它非常适合您的描述。

引用Barbara Liskov 和 Stephen Zilles 在 1974 年的“抽象数据类型编程”:

抽象数据类型定义了一类抽象对象,其特征完全在于这些对象上可用的操作。这意味着可以通过定义该类型的特征化操作来定义抽象数据类型。

在您的示例中,您正在定义一个VerifiedUrl由三个操作描述的抽象数据类型。操作create(或tryCreate)创建抽象数据类型的值,并且操作get允许您获取该值。创建值的操作还捕捉到您只能VerifiedUrl从有效的 URL 字符串创建一个事实。

这种模式可能更关注这样一个事实,即您隐藏了实现细节并仅公开某些操作来操作它 - 而在您的情况下,另一个重要事实是抽象数据类型的值满足某些属性 - 但您可以将它们视为关于抽象数据类型的不变量。我想不出一个更好的概念来捕捉这个想法。


推荐阅读