首页 > 解决方案 > 什么时候需要动态多态性(与静态多态性相比)?

问题描述

我来自函数式语言(例如 Haskell),我非常喜欢使用类型类来实现多态性,这也是实现临时重载的一种结构性方法。

但是,最近我开始了解OOP对实际问题建模的方式,我很好奇为什么我们需要OOP 语言(例如 Java)中的动态多态性。根据我的经验,大多数函数调用都可以在编译时解决,因为许多函数式语言不支持子类型化

所以我的问题是,在什么样的情况下我们必须使用动态多态而不是编译时多态?我的猜测是:

标签: javaoopfunctional-programmingpolymorphismdynamic-dispatch

解决方案


在 Haskell 中,我们将“动态多态性”替换为高阶函数。

考虑以下问题:我们要定义一个表示谓词的类型。当我们实现列表时,我们最终将使用这种类型的 Predicate,以便我们可以定义过滤器函数。我们希望能够轻松定义相等谓词、小于谓词,并能够通过用“and”连接两个谓词来组合它们。

一个合理的 Java 尝试应该是这样的。

interface Predicate<T> {
    public abstract boolean predicate(T x);
}

class EqualsPredicate<T> implements Predicate<T> {
    private final T value;

    public EqualsPredicate(T value) {
        this.value = value;
    }

    @Override
    public boolean predicate(T x) {
        return this.value.equals(x);
    }
}

class LessPredicate<T implements Comparable<T>> implements Predicate<T>{
    private final T value;

    public LessPredicate(T value) {
        this.value = value;
    }

    @Override
    public boolean predicate(T x) {
        return this.value.compareTo(x) < 0;
    }
}

class AndPredicate<T> implements Predicate<T> {
    private final Predicate<T> p1;
    private final Predicate<T> p2;

    public AndPredicate(Predicate<T> p1, Predicate<T> p2) {
        this.p1 = p1;
        this.p2 = p2;
    }

    @Override
    public boolean predicate(T x) {
        return p1.predicate(x) && p2.predicate(x);
    }
}

在 Haskell 中,这个难题的答案是显而易见的。我们只定义

type Predicate t = t -> Bool

makeEqualsPredicate :: Eq t => t -> Predicate t
makeEqualsPredicate = (==)

makeLessPredicate :: Ord t => t -> Predicate t
makeLessPredicate = (<)

makeAndPredicate :: Predicate t -> Predicate t -> Predicate t
makeAndPredicate p1 p2 x = p1 x && p2 x
-- or, even more succinctly, makeAndPredicate = liftA2 (&&)

Java 允许通过子类化“动态分派”方法。Haskell 允许通过高阶函数“动态调度”函数。

但是等等,你说。Predicate 是一个只有一种方法的接口。如果我们想要有两种方法,我们应该怎么做?

那么,如果一个接口有一个方法对应一个函数,那么一个接口有两个方法一定对应一对函数。这就是所谓的“组合优于继承”的 OOP 原则。

所以我们总是可以用 Haskell 风格的高阶函数代替 Java 风格的动态多态性。

事实上,您实际上在现代 Java 中也看到了这种观察结果。从 Java 8 开始,您可以@FunctionalInterface使用一种方法将注释添加到接口,这允许您使用 lambdas 创建该接口的实例。所以你可以用 Java 8 编写

@FunctionalInterface
interface Predicate<T> {
    public abstract boolean predicate(T x);

    public static Predicate<J> makeEqualsPredicate(J t) {
        return (x) -> t.equals(x);
    }

    public static Predicate<J implements Comparable<J>> makeLessPredicate(J t) {
        return (x) -> t.compareTo(x) < 0;
    }

    public Predicate<T> makeAndPredicate(Predicate<T> other) {
        return (x) -> this.predicate(x) && other.predicate(x);
    }
}

推荐阅读