好吧,这听起来好像您的语义域具有IS-A关系,但是您对于使用子类型/继承对此建模感到有些警惕-尤其是由于运行时类型反射。但是,我认为您担心错误的事情-子类型化确实有危险,但是在运行时查询对象这一事实并不是问题。您会明白我的意思的。
面向对象的程序设计在很大程度上依赖于IS-A关系的概念,可以说它过于依赖它,导致了两个著名的关键概念:
但是我认为,还有另一种基于功能编程的方式来查看IS-A关系,这些方法也许没有这些困难。首先,我们要在程序中对马和独角兽进行建模,因此我们将拥有Horse
和Unicorn
类型。这些类型的值是什么?好吧,我这样说:
- 这些类型的值分别是马和独角兽的表示或描述;
- 它们是模式化的表示或描述-它们不是自由格式,而是根据非常严格的规则构造的。
这听起来似乎很明显,但是我认为人们陷入诸如圆椭圆问题之类的方法之一就是没有足够仔细地考虑这些问题。每个圆都是一个椭圆,但这并不意味着圆的每个模式化描述都会自动根据不同的模式对椭圆进行模式化描述。换句话说,仅仅说一个圆是一个椭圆并不意味着a Circle
是一个Ellipse
。但这确实意味着:
- 有一个总功能可以将任何
Circle
(化学式的圆圈描述)转换Ellipse
为描述相同圆圈的(不同类型的描述);
- 有一个带和的偏函数
Ellipse
,如果描述一个圆,则返回相应的Circle
。
因此,用函数式编程术语来说,您的Unicorn
类型根本不需要是其子类型Horse
,您只需要执行以下操作:
-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse
-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn
并且toUnicorn
必须是toHorse
:
toUnicorn (toHorse x) = Just x
Haskell的Maybe
类型是其他语言称为“选项”类型的类型。例如,Java 8 Optional<Unicorn>
类型为an Unicorn
或为空。请注意,您的两个选择(引发异常或返回“默认值或魔术值”)与选项类型非常相似。
因此,基本上,我在这里所做的是在不使用子类型或继承的情况下,根据类型和功能来重建概念IS-A关系。我从中得到的是:
- 您的模型需要有一个
Horse
类型;
- 该
Horse
类型需要编码足够的信息,以便明确确定是否有任何值描述了独角兽。
- 该
Horse
类型的某些操作需要公开该信息,以便该类型的客户可以观察给定的对象Horse
是否为独角兽。
- 该
Horse
类型的客户端将必须在运行时使用后面的这些操作来区分独角兽和马匹。
因此,从根本上讲,这是一个“问Horse
是否是独角兽”的模型。您对该模型保持警惕,但我认为是错误的。如果我给您一个Horse
s 列表,则该类型所保证的全部是列表中各项所描述的都是马,因此,您不可避免地需要在运行时执行某些操作以告诉他们哪个是独角兽。因此,我认为这是无可避免的-您需要实施能够为您做到这一点的操作。
在面向对象的编程中,熟悉的方法如下:
- 有
Horse
类型;
- 有
Unicorn
作为的一个亚型Horse
;
- 使用运行时类型反射作为客户端可访问的操作,该操作可识别给定
Horse
是否为Unicorn
。
从我上面介绍的“事物与描述”的角度来看,这确实有一个很大的弱点:
- 如果您有一个
Horse
描述独角兽的Unicorn
实例而不是一个实例怎么办?
回到开始,这是我认为使用子类型化和下流式建模此IS-A关系的真正可怕的部分,而不是必须进行运行时检查的事实。稍微滥用字体,询问它Horse
是否是Unicorn
实例并不等于询问它Horse
是否是独角兽(是否是Horse
对马的描述也是独角兽)。除非您的程序竭尽全力以封装所构造的代码,否则Horses
每次客户端尝试构造Horse
描述独角兽的a时,Unicorn
该类都将实例化。以我的经验,程序员很少会认真地做这些事情。
因此,我将采用一种方法,其中存在将Horse
s 转换为Unicorn
s 的显式,非向下转换操作。这可以是以下Horse
类型的方法:
interface Horse {
// ...
Optional<Unicorn> toUnicorn();
}
...或者它可能是一个外部对象(您的“马的单独对象,告诉您马是否为独角兽”):
class HorseToUnicornCoercion {
Optional<Unicorn> convert(Horse horse) {
// ...
}
}
两者之间的选择Horse -> Maybe Unicorn
取决于程序的组织方式–在两种情况下,您都可以从上面获得与我相同的操作,只是以不同的方式打包(这无疑会对Horse
类型所需的操作产生连锁反应)向客户展示)。