单分派、双分派及两种设计模式

什么是单分派和双分派

分派(dispatch) 是指按照对象的实际类型为其绑定对应方法体的过程。html

例若有X类及其两个子类X一、X2,它们都实现了实例方法m()——一般子类X一、X2的方法前应该加@Override,因此有3个m()。程序员

对于消息表达式a.m(b,c),按照一个对象的实际类型绑定对应方法体,称为单分派。固然,这个“一个对象”比较特殊,每个消息表达式a.m(b,c)只有一个消息接收者,这个“一个对象”就是指消息接收者,即a.m(b, c)中的a。因此,仅按照消息接收者的实际类型绑定实际类型提供的方法体,即单分派(singledispatch),就是面向对象中的动态绑定!编程

假设对于消息表达式a.m(b,c),若是可以按照a、b和c的实际类型为其绑定对应方法体,则称为三分派。简单起见,研究双分派(double dispatch)就够了。设计模式

所谓的双分派,则是但愿a.foo(b)可以 ①按照a的实际类型绑定其override的方法体,并且可以 ②按照b的实际类型绑定其重载的方法即foo(Y)、foo(Y1)、foo(Y2)中的适当方法体。 【相关概念,能够参考《设计模式.5.11访问者模式》p223】bash

遗憾的是,Java不支持双分派。对于foo(X)、foo(X1)和foo(X2)这些重载的方法,Java在编译时,就为foo(b)按照b的声明类型静态绑定了foo(X)这个的方法体,而不会去判断b的实际类型是X1仍是X2。 Java中可使用运行时类型识别(Run-Time TypeIdentification、RTTI)技术,即便用关键字instanceof判断实际类型。虽然声明类型为父类Y,程序中按照实际类型从新声明temp,并将参数向下造型。RTTI虽然代码简洁,但使用分支语句不够优雅。另外,①程序员还要注意,具体类型判断在前;②RTTI将占用较多的运行时间和空间。ide

《Java编程思想》中,有句话ui

Java中除了static方法和final方法(private方法属于final方法)以外,其余全部方法都是后期绑定,也就是运行时绑定,咱们没必要判断是否应该进行后期绑定-它会自动发生。this

这里提到的后期绑定,也只是针对参数的声明类型来选择具体的方法。spa

依赖设计模式实现双分派

既然Java支持a.m(b)时,按a的具体类型绑定相应的方法,那若是经过某种方式在a.m(b)的实现中,完成了b.m1()的调用,那不就实现“双分派”了吗?纵览GOF23,有两种设计模式完美地支持这一种,分别是命令模式访问者模式设计

命令模式实现双分派

源码

命令模式的UML图

先说个题外话,相信你们从UML图中能够看出些问题,为何Client既须要知道Invoker又须要知道Receiver呢,Invoker角色接受Client的命令并执行命令,而真正命令的实施者是Receiver,用《设计模式之禅》里的比方,Invoker是项目经理,Receiver就是干活的码农或美工等等。依照迪米特法则,那Client就不该该知道Receiver。这个问题咱们放在后面讨论。下面先看代码:

抽象receiver

public abstract class Receiver {
    abstract void doSth();
}
复制代码

ConcreteReceiver1

public class ConcreteCommand1 extends Command {
    private Receiver receiver;

    public ConcreteCommand1(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    void execute(Receiver receiver) {
        System.out.println("我是command1, 入参是Receiver");
        receiver.doSth();
    }

    @Override
    void execute(ConcreteReceiver1 receiver) {
        System.out.println("我是command1, 入参是ConcreteReceiver1");
        receiver.doSth();
    }

    @Override
    void execute(ConcreteReceiver2 receiver) {
        System.out.println("我是command1, 入参是ConcreteReceiver2");
        receiver.doSth();
    }
}

复制代码

抽象Command

public abstract class Command {
    private Receiver receiver;

    public Command(Receiver receiver) {
        this.receiver = receiver;
    }
    abstract void execute(Receiver receiver);

    abstract void execute(ConcreteReceiver1 receiver);

    abstract void execute(ConcreteReceiver2 receiver);

    public Receiver getReceiver() {
        return receiver;
    }
}

复制代码

ConcreteCommand1

public class ConcreteCommand1 extends Command {
    public ConcreteCommand1(Receiver receiver) {
        super(receiver);
    }

    @Override
    public Receiver getReceiver() {
        return super.getReceiver();
    }

    @Override
    void execute(Receiver receiver) {
        System.out.println("我是command1, 入参是Receiver");
        receiver.doSth();
    }

    @Override
    void execute(ConcreteReceiver1 receiver) {
        System.out.println("我是command1, 入参是ConcreteReceiver1");
        receiver.doSth();
    }

    @Override
    void execute(ConcreteReceiver2 receiver) {
        System.out.println("我是command1, 入参是ConcreteReceiver2");
        receiver.doSth();
    }
}
复制代码

ConcreteCommand2

public class ConcreteCommand2 extends Command {
    public ConcreteCommand2(Receiver receiver) {
        super(receiver);
    }

    @Override
    void execute(Receiver receiver) {
        System.out.println("我是command2, 入参是Receiver");
        receiver.doSth();
    }

    @Override
    void execute(ConcreteReceiver1 receiver) {
        System.out.println("我是command2, 入参是ConcreteReceiver1");
        receiver.doSth();
    }

    @Override
    void execute(ConcreteReceiver2 receiver) {
        System.out.println("我是command2, 入参是ConcreteReceiver2");
        receiver.doSth();
    }

    @Override
    public Receiver getReceiver() {
        return super.getReceiver();
    }
}

复制代码

Invoker

public class Invoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void act() {
        this.command.execute(command.getReceiver());
    }
}

复制代码

client

public class Client {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();

        Receiver receiver1 = new ConcreteReceiver1();
        Receiver receiver2 = new ConcreteReceiver2();

        Command command1 = new ConcreteCommand1(receiver1);
        Command command2 = new ConcreteCommand2(receiver2);

        invoker.setCommand(command1);
        invoker.act();
    }
}
复制代码

执行结果

我是command1, 入参是Receiver
receiver1 处理命令1
复制代码

分析

为了凑a.m(b)这种格式,弄得很丑,你们见谅。Client中,Receiver和Command都是按接口声明的,当执行到invoker.setCommand(command1); invoker.act();时,程序走至

a即this.command,Java能够找到具体的实现类;而b的声明类型是Receiver,Java不会去识别,所以能够看做 ConcreteCommand1.execute(Receiver);,所以Java绑定了ConcreteCommand1中的第一个方法

咱们看到的打印输出就是 我是command1, 入参是Receiver

在这个方法中,receiver又成为了a.m(b)中的a,由于Java又能够根据其实际类型进行方法绑定,所以跑到ConcreteReceiver1中,为了避免要脸地硬捧a.m(b),这里又罗哩罗嗦地写了三个重载方法。这时b是ConcreteCommand1,所以找到重载方法

看到打印输出 receiver1 处理命令1

最佳实践

《设计模式之禅》也提到了我开篇的疑惑,Client为何要知晓Receiver的存在呢?事实上咱们在实际工做中,没有人真的那么干。引用一段书中的原文:

每个模式到实际应用的时候都有一些变形,命令模式的Receiver在实际应用中通常都会被封装掉(除非很是必要,例如撤销处理),那是由于在项目中:约定的优先级最高,每个命令是对一个或多个Receiver的封装,咱们能够在项目中经过有意义的类名或命令名处理命令角色和接收者角色的耦合关系(这就是约定),减小高层模块(Client类)对低层模块(Receiver角色类)的依赖关系,提升系统总体的稳定性。所以,建议你们在实际的项目开发时采用封闭Receiver的方式(固然了,仁者见仁,智者见智),减小Client对Reciver的依赖。

访问者模式

了解访问者模式的朋友看到这确定会说,这tm哪里是命令模式啊,明明是披着命令模式皮的访问者模式嘛!确实,为了千方百计说明双分派,已经把命令模式搞变态了,不妨好好看下访问者模式。举《设计模式之禅》的例子

演员演电影角色,一个演员能够扮演多个角色,咱们先定义一个影视中的两个角色:功夫主角和白痴配角

public interface Role { 
//演员要扮演的角色 
} 
public class KungFuRole implements Role { 
//武功天下第一的角色 
} 
public class IdiotRole implements Role { 
//一个弱智角色 
}
复制代码

角色有了,咱们再定义一个演员抽象类

public abstract class AbsActor {
    //演员都可以演一个角色 
    public void act(Role role){
        System.out.println("演员能够扮演任何角色");
    }
    //能够演功夫戏 
    public void act(KungFuRole role){
        System.out.println("演员均可以演功夫角色");
    }
}
复制代码

很简单,这里使用了Java的重载,咱们再来看青年演员和老年演员,采用覆写的方式来 细化抽象类的功能

public class YoungActor extends AbsActor {
    //年轻演员最喜欢演功夫戏 
    public void act(KungFuRole role){
        System.out.println("最喜欢演功夫角色");
    }
}
public class OldActor extends AbsActor {
    //不演功夫角色 
    public void act(KungFuRole role){
        System.out.println("年龄大了,不能演功夫角色");
    }
}
复制代码

覆写和重载都已经实现,咱们编写一个场景,

public class Client {
    public static void main(String[] args) {
//定义一个演员 
        AbsActor actor = new OldActor();
//定义一个角色 
        Role role = new KungFuRole();
//开始演戏 
        actor.act(role);
        actor.act(new KungFuRole());
    }
}
复制代码

获得输出结果

演员能够扮演任何角色
年龄大了,不能演功夫角色 
复制代码

使用上节提到的介绍的方法,能够很是轻松地分析出双分派的实现原理。

深层原理

你们能够发现,经过设计模式实现的双分派,实际上是“伪双分派”,至少深层的原理,须要阅读更多资料,等我读完《深刻理解Java虚拟机》后,会回来把这一节补上。

参考文档

www.iteye.com/topic/11307… www.voidcn.com/article/p-d… 《设计模式之禅》 《Java编程思想》

相关文章
相关标签/搜索