问题描述
我想知道如何以一种功能语言实现面向对象语言中常见的Abstract Factory设计模式.我尤其对Haskell实现感兴趣.
I was wondering how to implement the Abstract Factory design pattern, common in object-oriented languages, in a functional language. In particular, I am interested in an Haskell implementation.
我试图使用类型类来实现模式:
I tried to implement the pattern using type classes:
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
编译此代码时,将返回以下错误:
When compiling this code, the following error is returned instead:
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
类型类的任何实例都可以解决问题,但显然没有.
It looks like the compiler is not happy with the implementation of createProduct
, and in particular with its return value. I though that returning any instance of the Product
type class could do the trick, but it obviously doesn't.
我想知道在Haskell中是否可以实现类似于Abstract Factory的东西,或者我的方法是否完全错误.还有其他我可以用来达到类似结果的模式"吗?
I would like to know if something similar to an Abstract Factory can be implemented in Haskell or if my approach is completely wrong. Are there any other "patterns" I could use to achieve a similar result?
修改
根据关于"leftaboutabout"的建议和解释,我想出了另一种解决方案,可以满足我的需求而不会滥用类型类.该解决方案可能会得到改进,但是目前这是我所能达到的最佳效果.
According to the suggestions and explanations of leftaroundabout, I came up with a different solution that fulfills my needs without misusing type classes. The solution could be probably be improved, but at the moment this is the best I was able to achieve.
该库必须定义类似于以下内容的内容:
The library would have to define something similar to the following:
data ProductSystem p = ProductSystem {create :: p, toString :: p -> String }
productUser :: ProductSystem p -> String
productUser system = toString system $ create system
该库的某些用户可以为他们的具体需求提供 ProductSystem
的实现":
Some users of the library could provide "implementations" of the ProductSystem
for their concrete needs:
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"
在其他地方,可以将这两部分统一起来执行所需的行为:
Somewhere else, the two pieces could be unified to execute the wanted behavior:
productUser firstSystem
productUser secondSystem
推荐答案
正如我已经评论过的,整个想法是徒劳的:在Haskell中不需要抽象工厂.除此之外,这就是为什么您的特定尝试无法编译的原因.
签名
createProduct :: Product p => f -> p
并不是您显然想的那样:在Java中,这表示我将生成 Product
的子类的某个对象,但不要问是哪个."在Haskell–中其子类是 not 子类型!–这意味着,对于您请求的 Product
的任何(特定!)实例,我将为您提供该具体类型的对象."这是不可能的,因为 FirstFactory
显然不能提供 SecondProduct
.
means not what you apparently think: in Java, this would say "I'll generate some object of a subclass of Product
, don't ask which though." In Haskell – whose subclasses are not subtypes! – this means, "for any (specific!) instance of Product
that you request, I'll give you an object of that concrete type." Which isn't possible, since FirstFactory
apparently can't supply a SecondProduct
.
要表达您想说的话,您需要一个显式包装器来包含任何子类".我们称其为 existential ,它的写法是:
To express what you tried to say, you need an explicit wrapper to contain "any subclass". We call that an existential, it's written thus:
{-# LANGUAGE GADTs #-}
data AnyProduct where
AnyProduct :: Product p => p -> AnyProduct
有了这个,你可以写
class Factory f where
createProduct :: f -> AnyProduct
对于某些 yourProduct :: AnyProduct
,您可以通过以下方式调用 toString
方法":
For some yourProduct :: AnyProduct
, you can then "call the toString
method" this way:
...
productString = case yourProduct of
AnyProduct p -> toString p
...
但是由于实际上这是唯一的事情,因此您可以使用 AnyProduct
值进行操作(就像在OO语言中一样,您无法访问未知子类的字段/方法),整个 AnyProduct
类型实际上完全等同于 String
!同样, AnyFactory
也将等同于此.因此,基本上,您发布的整个代码等同于
But since this is actually the only thing you can do with an AnyProduct
value (like in OO language, you can't access fields/methods of the unknown subclass), the whole AnyProduct
type is actually completely equivalent to String
alone! And by the same argument, AnyFactory
would again be equivalent to that. So basically, the entire code you posted is equivalent to
type Product = String
...
存在的内容通常皱着眉头,您只应在特殊情况下使用它们,而不应仅用于OO语言通过子类实现.
Existentials are quite generally frowned upon, you should only use them in special cases, not for anything just because OO languages do it with subclassing.
这篇关于哈斯克尔的抽象工厂的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!