【发布时间】: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_classes、stackoverflow.com/questions/20184286/… 和 lukepalmer.wordpress.com/2010/01/24/…。
-
我可以使用
data来定义工厂和产品集,但这会限制我系统的可扩展性。如果我定义data Factory = FirstFactory | SecondFactory,那么我的库只限于这两个实现。相反,我想定义一个我可以依赖的FactoryAPI,以便我的库的用户可以提供它的不同实现。你能解释一下如何使用函数来实现类似的目标吗? -
不不,您只需定义一个记录:对于来自
Product的所有需要,即对于所有接口方法的结果。 (在您的示例中,这基本上只是一个String)。在 Haskell 中,所有必须是派生类的字段或方法的东西都不是真正需要的:这类东西可以自动打包在生成Products 的函数(“工厂”)的闭包中。它实际上很像某些动态语言提供的“ad-hoc-object-orientation”。