有多种不同的多态性,有趣的一种通常是运行时多态性/动态调度。
运行时多态性的一个非常高级的描述是,方法调用根据其参数的运行时类型执行不同的操作:对象本身负责解决方法调用。这提供了极大的灵活性。
使用这种灵活性的最常见方法之一是进行依赖项注入,例如,这样我就可以在不同的实现之间切换或注入模拟对象进行测试。如果我事先知道只有少数几种可能的选择,我可以尝试使用条件对它们进行硬编码,例如:
void foo() {
if (isTesting) {
... // do mock stuff
} else {
... // do normal stuff
}
}
这使得代码难以遵循。另一种方法是为该foo操作引入一个接口,并编写该接口的常规实现和模拟实现,然后在运行时“注入”所需的实现。“依赖注入”是用于“将正确的对象作为参数传递”的复杂术语。
作为一个真实的例子,我目前正在研究一种机器学习问题。我有一个需要预测模型的算法。但是我想尝试不同的机器学习算法。所以我定义了一个接口。我的预测模型需要什么?给定一些输入样本,预测及其错误:
interface Model {
def predict(sample) -> (prediction: float, std: float);
}
我的算法采用训练模型的工厂功能:
def my_algorithm(..., train_model: (observations) -> Model, ...) {
...
Model model = train_model(observations);
...
y, std = model.predict(x)
...
}
我现在有模型接口的各种实现,并且可以将它们彼此进行基准测试。这些实现之一实际上采用了另外两个模型,并将它们组合为增强模型。因此,感谢此接口:
- 我的算法不需要事先了解特定模型,
- 我可以轻松换出模型,并且
- 我在实现模型方面有很大的灵活性。
GUI中是多态性的经典用例。在Java AWT / Swing /…之类的GUI框架中,存在不同的组件。组件接口/基类描述了诸如将自身绘制到屏幕上或对鼠标单击做出反应之类的操作。许多组件是管理子组件的容器。这样的容器如何吸引自己?
void paint(Graphics g) {
super.paint(g);
for (Component child : this.subComponents)
child.paint(g);
}
在这里,容器不需要事先知道子组件的确切类型,只要它们符合Component
接口,容器就可以简单地调用多态paint()
方法。这使我可以自由地使用任意新组件扩展AWT类层次结构。
通过将多态性作为一种技术可以解决整个软件开发中经常出现的问题。这些重复出现的问题-解决方案对称为设计模式,其中一些收集在同名书中。用那本书的术语来说,我注入的机器学习模型将是一种策略,我可以用来“定义一系列算法,封装每个算法,并使它们可互换”。组件可以包含子组件的Java-AWT示例是Composite的示例。
但是并不是每个设计都需要使用多态性(除了在单元测试中启用依赖注入外,这是一个非常好的用例)。否则大多数问题都是静态的。结果,类和方法通常不用于多态,而只是用作方便的命名空间和漂亮的方法调用语法。例如,许多开发人员更喜欢方法调用,而account.getBalance()
不是在很大程度上等效的函数调用Account_getBalance(account)
。这是一个非常好的方法,只是许多“方法”调用与多态无关。