多态性在现实世界中如何使用?[关闭]


17

我试图了解在现实生活中的项目中如何使用多态,但是我只能找到经典的示例(或类似的例子),该示例具有一个Animal带方法的父类speak(),以及许多重写此方法的子类,现在您可以speak()在任何子对象上调用该方法,例如:

Animal animal;

animal = dog;
animal.speak();

animal = cat;
animal.speak();



1
您每天都会看到和使用的集合本身足以理解什么是多态性。但是如何有效地利用多态性解决问题是您从经验中获得的最大技能,而不仅仅是通过讨论获得。继续弄脏你的手。
Durgadass S

如果您有一组将全部支持某种最小接口的类型(例如,需要绘制的一组对象),则接口通常很适合隐藏对象之间的差异以进行绘制。同样,如果您正在制作(或使用)一种API,该API的方法可以为基础对象提供服务,并且有许多类型可以或多或少地以相同的方式从基础对象继承,则多态性可能是抽象化两者之间差异的最佳方法。这些类型。
jrh

通常,如果您经常使用重载方法来处理不同类型并且代码相似,或者如果您if(x is SomeType) DoSomething()经常编写,则使用多态可能是值得的。对我来说,多态性是一个决定何时创建单独方法的决定,如果我发现我重复了几次代码,通常将其重构为一个方法,并且如果发现自己if object is this type do this经常在编写代码,则可能是值得重构和添加接口或类。
jrh

Answers:


35

是多态性的一个很好的例子。

流表示“可以读取或写入的字节序列”。但是此序列可能来自文件,内存或多种网络连接。或者它可以充当装饰器,包装现有流并以某种方式转换字节,例如加密或压缩。

这样,使用Stream的客户端无需关心字节来自何处。只是可以按顺序读取它们。

有人会说Stream多态性的例子是错误的,因为它定义了实现者不支持的许多“功能”,例如网络流仅允许读取或写入,但不能同时允许两者。还是缺乏寻求。但这只是复杂性的问题,Stream可以细分为可以独立实现的许多部分。


2
在具有多重和虚拟继承的语言(例如C ++)中,该示例甚至可以通过从基本流类派生输入和输出流类,并扩展二者以创建I / O流来演示“可怕的钻石”模式
gyre

2
@gyre做得好,没有理由“恐惧”钻石图案。需要了解钻石中的相对对象并且不引起名称冲突是很重要的,也是挑战,烦​​人以及避免在可行的情况下避免钻石图案的原因……但不要太过恐惧仅仅使用一个命名约定就可以解决问题。
KRyan

+1 Stream是我一直以来最喜欢的多态示例。我什至不再尝试教给人们有缺陷的“动物,哺乳动物,狗”模型,Stream但是做得更好。
法拉普

@KRyan我并不是通过称其为“可怕的钻石”来表达自己的想法,我只是听说过这种说法。我完全同意; 我认为这是每个开发人员都应该能够正确使用和使用的东西。
gyre '18

@gyre哦,是的,我实际上明白了;这就是为什么我从“和”开始,以表明这是您思想的延伸,而不是矛盾。
KRyan

7

与游戏相关的典型示例是基类Entity,提供诸如draw()或的常见成员update()

对于更纯粹的面向数据的示例,可以有一个Serializable提供common saveToStream()和的基类loadFromStream()


6

有多种不同的多态性,有趣的一种通常是运行时多态性/动态调度。

运行时多态性的一个非常高级的描述是,方法调用根据其参数的运行时类型执行不同的操作:对象本身负责解决方法调用。这提供了极大的灵活性。

使用这种灵活性的最常见方法之一是进行依赖项注入,例如,这样我就可以在不同的实现之间切换或注入模拟对象进行测试。如果我事先知道只有少数几种可能的选择,我可以尝试使用条件对它们进行硬编码,例如:

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)。这是一个非常好的方法,只是许多“方法”调用与多态无关。


6

您会在大多数UI工具包中看到很多继承和多态性。

例如,在JavaFX UI工具包中,Button继承自ButtonBase继承自Labeled继承自Control继承自Region继承自继承自Parent继承自Node继承自继承自Object。许多层都覆盖了先前方法中的某些方法。

当您希望该按钮出现在屏幕上时,可以将其添加到 Pane,它可以接受从Node子级继承的任何内容。但是,当窗格仅将其视为通用Node对象时,如何知道如何处理Button?该对象可以是任何东西。该窗格可以执行此操作,因为Button使用任何特定于按钮的逻辑重新定义了Node的方法。该窗格仅调用Node中定义的方法,其余的留给对象本身。这是应用多态性的完美示例。

UI工具包在现实世界中具有很高的意义,使其在学术和实践方面都非常有用。

但是,UI工具箱也有一个很大的缺点:它们往往很大。当新手软件工程师试图了解通用UI框架的内部工作原理时,他们经常会遇到一百多个类,其中大多数服务于非常深奥的目的。“这到底是ReadOnlyJavaBeanLongPropertyBuilder什么?重要吗?我是否必须了解它的好处?” 初学者很容易迷失在那个兔子洞中。因此,他们要么逃避恐怖,要么呆在表面上,他们只是在学习语法,并尽量不要对引擎盖下的实际情况考虑得太深。


3

尽管这里已经有不错的例子,但另一个例子是用设备代替动物:

  • Device可以powerOn()powerOff()setSleep()和CAN getSerialNumber()
  • SensorDevice能做到这一切,并提供多态功能,例如getMeasuredDimension()getMeasure()alertAt(threashhold)autoTest()
  • 当然,getMeasure()对于温度传感器,光检测器,声音检测器或体积传感器,将不会以相同的方式实现。当然,这些更专业的传感器中的每一个可能都有一些可用的附加功能。

2

表示是一个非常常见的应用程序,也许最常见的是ToString()。基本上是Animal.Speak():您告诉对象要表现出来。

一般来说,您告诉对象“做它的事”。考虑保存,加载,初始化,处置,ProcessData,GetStatus。


2

我对多态性的第一个实际用法是在Java中实现Heap。

我有实现方法insert,removeTop的实现的基类,其中max和min堆之间的差异仅是方法比较的工作方式。

abstract class Heap {  

 abstract boolean compare ( int x , int y );

 boolean insert(int x ) { ... }

 int removeTop() { ... }
}

因此,当我想拥有MaxHeap和MinHeap时,我可以使用继承。

class MaxHeap extends Heap {

   MaxHeap(int maxSize) {super(maxSize);}

   @Override
   boolean compare(int x, int y) {
       return x>y; // x<y for minHeap
   }
}

1

这是Web应用程序/数据库表多态性的真实场景

我使用Ruby on Rails开发Web应用程序,许多项目的共同点是可以上传文件(照片,PDF等)。因此,例如,a User可能有多个个人资料图片,而a Product也可能有许多产品图片。两者都具有上载和存储图像的行为,以及调整大小,生成缩略图等。为了保持DRY并共享的行为Picture,我们想使Picture多态性使其同时属于UserProduct

在Rails中,我将这样设计模型:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class User < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end

和数据库迁移以创建pictures表:

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end

    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

imageable_idimageable_type由Rails内部使用。基本上,imageable_type保留类的名称("User""Product"等),并且imageable_id是关联记录的ID。因此imageable_type = "User"imageable_id = 1将是users表中的记录id = 1

这使我们可以做一些事情,例如user.pictures访问用户的图片以及product.pictures获取产品的图片。然后,所有与图片有关的行为都封装在Photo该类中(对于需要照片的每个模型,则不是一个单独的类),因此所有内容都保持DRY状态。

更多阅读:Rails多态关联


0

有很多可用的排序算法,例如冒泡排序,插入排序,快速排序,堆排序等,它们具有不同的复杂度,哪种算法最适合使用取决于各种因素(例如:数组的大小)

提供排序接口的客户端只关心提供数组作为输入,然后接收排序后的数组。在运行期间,根据某些因素,可以使用适当的排序实现。这是使用多态性的真实示例。

我上面所描述的是运行时多态性的一个示例,而方法重载是编译时多态性的一个示例,其中,取决于i / p和o / p参数类型的编译器以及在编译时本身使用正确方法绑定参数的调用者的数量。

希望这可以澄清。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.