1.分派的概念
变量被声明时的类型叫做变量的静态类型(Static Type) 又叫明显类型(Apparent Type)。变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type)。
根据对象的类型而对方法进行的选择,就是分派(Dispatch)。根据分派发生的时期,可以将分派分为两种,即分派分静态分派和动态分派。
静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。方法重载(Overload)就是静态分派。(所谓的:编译时多态)
动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。面向对象的语言利用动态分派来实现方法置换产生的多态性。(所谓的:运行时多态)
方法重载(静态分派)
Java通过方法重载来支持静态分派。下面考察下墨子骑马的故事。
public class Mozi
{
public void ride(Horse h)
{
System.out.println("Riding a horse");
}
public void ride(WhiteHorse wh)
{
System.out.println("Riding a white horse");
}
public void ride(BlackHorse bh)
{
System.out.println("Riding a black horse");
}
public static void main(String[] args)
{
Horse wh = new WhiteHorse();
Horse bh = new BlackHorse();
Mozi mozi = new Mozi();
mozi.ride(wh);
mozi.ride(bh);
}
/**
* @directed
*/
private Horse lnkHorse;
}
打印了两次的“Riding a horse”。 墨子发现他骑的都是马。
两次对ride()方法的调用传入的是不同的参量,也就是wh和bh。它们虽然具有不同的真实类型,但是它们的静态类型都是一样的,均是Horse类型。
重载方法的分派是根据静态类型进行的。这个分派过程在编译时期就完成了。
动态分派
Java通过方法的置换(Overriding)支持动态分派。
String s1 = "ab";
Object o = s1 +"c";
String s = "abc";
boolean b = o.equals(s);
上述代码返回了true(基础多态就不多说了)。
2.分派的类型
一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参量统称做方法的宗量。
根据分派可以基于多少种宗量,可以将面向对象的语言划分为单分派语言和多分派语言。单元分派语言根据一个宗量的类型(真实类型)进行对方法的选择,多分派语言根据多于一个的宗量的类型对方法进行选择。
C++和Java以及Smaltalk都是单分派语言;多分派语言的例子包括CLOS和Cecil。按照这样的区分,C++和Java就是动态的单分派语言,因为这两种语言的动态分派仅仅会考虑到方法的接收者的类型,同时又是静态的多分派语言,因为这两种语言对重载方法的分派会考虑到方法的接收者的类型和方法所有参量的类型。
在一个支持动态单分派的语言里面,有两个条件决定了一个请求会调用哪一个操作:一是请求的名字,二是接收者的真实类型。单分派限制了方法的选择过程,使得只有一个宗量可以被考虑到,这个宗量通常就是方法的接收者。在JAVA语言里面,如果一个操作是作用于某个类型不明的对象上面的。那么这个对象的真实类型测试仅会发生一次。这个就是动态的单分派的特征。
一言以蔽之,JAVA语言支持静态的多分派和动态的单分派。
3.双重分派
一个方法根据两个宗量的类型来决定执行不同的代码,这就是“双分派”或者“多重分派”。Java不支持动态的多分派。但可以通过使用设计模式,在Java语言里面实现动态的双重分派(ps:就是“伪双重分派”是由两次的单分派组成)。
方案一:类型判断
在方法里使用instanceof判断真实类型,比如(java.awt.Component的源码):
protected void processEvent(AWTEvent e) {
if (e instanceof FocusEvent) {
processFocusEvent((FocusEvent)e);
} else if (e instanceof MouseEvent) {
switch(e.getID()) {
case MouseEvent.MOUSE_PRESSED:
case MouseEvent.MOUSE_RELEASED:
case MouseEvent.MOUSE_CLICKED:
case MouseEvent.MOUSE_ENTERED:
case MouseEvent.MOUSE_EXITED:
processMouseEvent((MouseEvent)e);
break;
case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_DRAGGED:
processMouseMotionEvent((MouseEvent)e);
break;
case MouseEvent.MOUSE_WHEEL:
processMouseWheelEvent((MouseWheelEvent)e);
break;
}
} else if (e instanceof KeyEvent) {
processKeyEvent((KeyEvent)e);
} else if (e instanceof ComponentEvent) {
processComponentEvent((ComponentEvent)e);
} else if (e instanceof InputMethodEvent) {
processInputMethodEvent((InputMethodEvent)e);
} else if (e instanceof HierarchyEvent) {
switch (e.getID()) {
case HierarchyEvent.HIERARCHY_CHANGED:
processHierarchyEvent((HierarchyEvent)e);
break;
case HierarchyEvent.ANCESTOR_MOVED:
case HierarchyEvent.ANCESTOR_RESIZED:
processHierarchyBoundsEvent((HierarchyEvent)e);
break;
}
}
}
这种方法实现的双重分派都格外的冗长、复杂和容易出错,也不符合“开闭原则”。
方案二:反转球
通过两次的调用来实现,比如下面剪刀石头布的游戏:
public enum Outcome { WIN, LOSE, DRAW } ///:~
interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
public Outcome compete(Item it) {
return it.eval(this);
}
public Outcome eval(Paper p) {
return DRAW;
}
public Outcome eval(Scissors s) {
return WIN;
}
public Outcome eval(Rock r) {
return LOSE;
}
public String toString() {
return "Paper";
}
}
class Scissors implements Item {
public Outcome compete(Item it) {
return it.eval(this);
}
public Outcome eval(Paper p) {
return LOSE;
}
public Outcome eval(Scissors s) {
return DRAW;
}
public Outcome eval(Rock r) {
return WIN;
}
public String toString() {
return "Scissors";
}
}
class Rock implements Item {
public Outcome compete(Item it) {
return it.eval(this);
}
public Outcome eval(Paper p) {
return WIN;
}
public Outcome eval(Scissors s) {
return LOSE;
}
public Outcome eval(Rock r) {
return DRAW;
}
public String toString() {
return "Rock";
}
}
public class RoShamBo1 {
static final int SIZE = 20;
private static Random rand = new Random(47);
public static Item newItem() {
switch (rand.nextInt(3)) {
default:
case 0:
return new Scissors();
case 1:
return new Paper();
case 2:
return new Rock();
}
}
public static void match(Item a, Item b) {
System.out.println(a + " vs. " + b + ": " + a.compete(b));
}
public static void main(String[] args) {
for (int i = 0; i < SIZE; i++)
match(newItem(), newItem());
}
}
RoshamBol.match()有2个item参数,通关过Item.compete()方法开始2路分发,要判定a的类型,分发机制会在a的实际类型的compete()内部起到分发作用。compete()方法通关过eval()来为另一个类型实现第二次分发,
将自身(this)作为参数调用eval(),能够调用重载过的eval()方法,这能够保留第一次分发的类型信息,第二次分发完成时,就能知道两个Item对象的具体类型了。
这种实现也就是“访问者模式”的精华。
这种的详细解释请看:
整理至《java与模式》
分享到:
相关推荐
java 任务分配样例 In this page we will walk through Quartz 2 scheduler passing parameters to Job with JobDataMap using @PersistJobDataAfterExecution and @DisallowConcurrentExecution example.
由三个类实现,写在了一个 Java 文件中:TaskDistributor 为任务分发器,Task 为待执行的任务,WorkThread 为自定的工作线程。代码中运用了命令模式,如若能配以监听器,用上观察者模式来控制 UI 显示就更绝妙不过了...
一个简单的代理类,它多路复用和分派协议方法到多个对象。 安装 AOMultiproxier 可通过。 要安装它,只需将以下行添加到您的 Podfile 中: pod "AOMultiproxier" 例子 在您的标题中: @property (nonatomic, ...
suro, Netflix数据管道的分发 Suro...聚集和分发大量应用程序事件( 包括日志数据)的数据管道服务。 它具有以下功能:它是分布的,可以水平缩放。它支持流数据流。大量连接和高吞吐量。它允许将事件动态地分派到不同位置
该存储库包含事件分发程序的简单实现。 它的设计灵感来自古老的,但在几个方面有所不同: 事件调度程序提供了一个公共接口来通知观察者。 因此,事件可以从外部触发。 客户不需要了解有关观察者的任何信息。 事件...
兼容的事件分派器提供了分派事件和侦听所分派事件的功能。 特征 兼容。 简单轻巧。 鼓励设计事件层次结构。 可以组合多个事件侦听器提供程序。 安装 该软件包可以与作曲家一起安装: composer require yiisoft/...
Arkimet介绍Arkimet是一组用于组织,存档和分发数据文件的工具。 目前,它支持GRIB,BUFR,HDF5和VM2格式的数据。 Arkimet管理着一组,每个都包含存储在中的同质数据。 它利用数据集中数据之间的共性来实现快速,...
采购作业内容是从收到“请购案件”开始进行分发采购案件,由采购经办人员先核对请购内容,查阅“厂商资料”、“采购记录”及其他有关资料后,开始办理询价,于报价后,整理报价资料,拟订议价方式及各种有利条件,...
nginx端口:80 根据域名分派 在conf/nginx.conf中的http中增加 代码如下: include www.jb51.net.conf 新建conf/www.jb51.net.conf,内容如下: 代码如下: server { listen 80; server_name www.jb51.net; ...
通过一个 URL 分发器模块进行 URL 分派,分发器使用正则表达式来匹配 URL,支持开发人员采用自定义 URL 方式,且没有框架的特定限定,使用起来非常灵活。 可以方便地生成各种表单模型,实现表单的有效性检验,且支持...
该库的工作方式类似于Objective-C和Swift NSNotificationCenter也类似于Android平台中的BroadcastReceiver 。 通知分派机制,可将信息广播到注册的观察者。 文章 阅读Medium网站上的。 在C#角网站上阅读。 阅读...
本文档主要对文档的规划、接收、登记、分发、借阅、任务分派、存档、取消等进行管理;实现了文档的系统化、规范化、自动化管理。 本软件主要分下几个模块:文档基础设置、文件登记管理、文件分发管理、文件借阅管理...
3. 报警信息分拣分派模块 2 3.1 ALARM_DISPATCH.RB 2 3.2系统中情况 3 4. 邮件报警模块 3 4.1 SEND_MAIL.RB 3 4.2系统中情况 3 5. 短信均衡分发模块 4 5.1 BALANCE.RB 4 5.2系统中情况 4 6. 短信报警模块 4 6.1 SEND...
本文简要介绍了有关在Azure Logic应用中使用ForEach循环来分派JSON文件的情况
行渲染,和进一步将其装配为最终图像,来跨越多台主机分派单桢图像。除了预视最终规格的渲染,您还可以通过在像4096x4096的 高分辨率下,将图像跨越分派至多台主机,以免去为打印大大小小的图像带来的长时间的等待。
Redux序列动作一个为Redux启用顺序操作分派的。 $ npm install --save redux-sequence-action为什么假设您有一个AddressPicker组件,它使用户可以选择传递地址。 它由2个select组成,其中包括州列表和城市列表。 ...
这个主线程(也称为UI线程)很重要,因为它负责把事件分派到相应的控件,其中就包括屏幕绘图事件,它同样是用户与Andriod控件交互的线程。比如,当你在屏幕上按下一个按钮后,UI线程会把这个事件分发给刚按得那个...
JS信号 受启发JavaScript自定义事件/消息系统。... 在分派多个信号后可能需要执行操作的情况下,此功能很有用。 由于并非总是需要此功能,因此它被拆分成自己的存储库,因此可以通过npm轻松分发。
Spring MVC分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行制定。是一个标准的MVC框架。SpringMVC框架图SpringMVC接口解释DispatcherServlet接口:Spring提供的前端控制器,所有的...
- 使用 Nginx 的 TCP 负载均衡功能,将客户端请求分派到多个服务器上,以提高并发处理能力 - 基于发布-订阅的服务器中间件redis消息队列,解决跨服务器通信难题 - 封装 MySQL 接口,将用户数据储存到磁盘中,实现...