【问题标题】:Abstract factories in HaskellHaskell 中的抽象工厂
【发布时间】:2015-01-20 12:43:26
【问题描述】:

我想知道如何在函数式语言中实现抽象工厂设计模式,这在面向对象语言中很常见。特别是,我对 Haskell 实现感兴趣。

我尝试使用类型类来实现该模式:

class Product p where
  toString :: p -> String

class Factory f where
  createProduct :: Product p => f -> p

data FirstProduct = FirstProduct
data FirstFactory = FirstFactory

instance Product FirstProduct where
  toString _ = "first product"

instance Factory FirstFactory where
  createProduct _ = FirstProduct

编译此代码时,会返回以下错误:

Could not deduce (p ~ FirstProduct)
from the context (Product p)
  bound by the type signature for
             createProduct :: Product p => FirstFactory -> p
  at test.hs:14:3-15
  ‘p’ is a rigid type variable bound by
      the type signature for
        createProduct :: Product p => FirstFactory -> p
      at test.hs:14:3
Relevant bindings include
  createProduct :: FirstFactory -> p (bound at test.hs:14:3)
In the expression: FirstProduct
In an equation for ‘createProduct’: createProduct _ = FirstProduct

编译器似乎对createProduct 的实现不满意,尤其是它的返回值。我虽然返回 Product 类型类的任何实例都可以解决问题,但显然不行。

我想知道是否可以在 Haskell 中实现类似于抽象工厂的东西,或者我的方法是否完全错误。我可以使用其他“模式”来获得类似的结果吗?

编辑

根据leftaroundabout的建议和解释,我想出了一个不同的解决方案,在不滥用类型类的情况下满足我的需求。解决方案可能会有所改进,但目前这是我能做到的最好的。

库必须定义类似于以下内容的内容:

data ProductSystem p = ProductSystem {create :: p, toString :: p -> String }

productUser :: ProductSystem p -> String
productUser system = toString system $ create system

该库的一些用户可以提供ProductSystem 的“实现”以满足他们的具体需求:

data FirstProduct = FirstProduct

firstSystem :: ProductSystem FirstProduct
firstSystem = ProductSystem create toString
  where 
    create = FirstProduct
    toString p = "first"

data SecondProduct = SecondProduct

secondSystem :: ProductSystem SecondProduct
secondSystem = ProductSystem create toString
  where
    create = SecondProduct
    toString p = "second"

在其他地方,这两个部分可以统一执行所需的行为:

productUser firstSystem
productUser secondSystem

【问题讨论】:

  • 抽象工厂模式或多或少是一种可笑的复杂方式来解决语言的无能,因此只需定义一个函数。为什么在 Haskell 中需要它? — 更客观的评论:Haskell 类型的类不是对于 OO 语言中的类的好插件。如果你想在 Haskell 中为抽象工厂建模,你最好使用 data 作为“类”。
  • createProduct的类型表示它可以创建Product的任何实例,而实现只能返回FirstProduct的实例。
  • 有关如何(不)在 Haskell 中执行 OO 的更多信息,请考虑 haskell.org/haskellwiki/OOP_vs_type_classesstackoverflow.com/questions/20184286/…lukepalmer.wordpress.com/2010/01/24/…
  • 我可以使用data 来定义工厂和产品集,但这会限制我系统的可扩展性。如果我定义data Factory = FirstFactory | SecondFactory,那么我的库只限于这两个实现。相反,我想定义一个我可以依赖的Factory API,以便我的库的用户可以提供它的不同实现。你能解释一下如何使用函数来实现类似的目标吗?
  • 不不,您只需定义一个记录:对于来自Product 的所有需要,即对于所有接口方法的结果。 (在您的示例中,这基本上只是一个String)。在 Haskell 中,所有必须是派生类的字段或方法的东西都不是真正需要的:这类东西可以自动打包在生成Products 的函数(“工厂”)的闭包中。它实际上很像某些动态语言提供的“ad-hoc-object-orientation”。

标签: haskell design-patterns


【解决方案1】:

正如我已经评论过的,整个想法是徒劳的:你不需要 Haskell 中的抽象工厂。但除此之外,这就是您的特定尝试无法编译的原因。


签名

  createProduct :: Product p => f -> p

的意思显然不是你想的那样:在 Java 中,这会说“我将生成 Product 的子类的一些对象,但不要问是哪个。”在 Haskell 中——它的子类是 not 子类型! – 这意味着,“对于您请求的 任何(具体!)Product 实例,我会给您一个具体类型的对象。”这是不可能的,因为FirstFactory 显然无法提供SecondProduct

为了表达你想说的话,你需要一个显式的包装器来包含“任何子类”。我们称之为existential,它是这样写的:

{-# LANGUAGE GADTs       #-}

data AnyProduct where
  AnyProduct :: Product p => p -> AnyProduct

有了这个,你就可以写了

class Factory f where
  createProduct :: f -> AnyProduct

对于某些yourProduct :: AnyProduct,您可以这样“调用toString 方法”:

      ...
      productString = case yourProduct of
                        AnyProduct p -> toString p
      ...

但由于这实际上是唯一您可以使用 AnyProduct 值(就像在 OO 语言中,您不能访问未知子类的字段/方法),整个 @ 987654333@ 类型实际上完全等同于单独的String!通过同样的论点,AnyFactory 将再次等价于那个。所以基本上,你发布的整个代码相当于

type Product = String

...

Existentials 相当 generally frowned upon,你应该只在特殊情况下使用它们,而不是仅仅因为 OO 语言通过子类化来实现它。

【讨论】:

  • 这看起来很有趣,但正如你之前所说,我的问题可以用更简单的结构来解决。我编辑了我的问题以添加另一个我想到的解决方案。不管怎样,今天我确实学到了一些东西!
猜你喜欢
  • 2011-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-14
  • 1970-01-01
  • 2012-04-08
  • 2011-09-08
  • 1970-01-01
相关资源
最近更新 更多