Одинарная отправка, двойная отправка и два шаблона проектирования

Java

Что такое одинарная отправка и двойная отправка

отправлятьв зависимости от объектафактический типПроцесс, к которому привязано соответствующее тело метода.

Например, есть класс X и два его подкласса X1 и X2, которые реализуют метод экземпляра m() — обычно @Override следует добавлять перед методами подклассов X1 и X2, поэтому m() 3.

Для выражения сообщения a.m(b,c) соответствующее тело метода связывается в соответствии с фактическим типом объекта, который называется одиночной отправкой. Конечно, этот «один объект» особенный, каждое выражение сообщения a.m(b,c) имеет только одного получателя сообщения, и этот «один объект» относится кполучатель сообщения, то есть в a.m(b, c)a. так,Привязывайте только тело метода, предоставленное фактическим типом, в соответствии с фактическим типом получателя сообщения, то есть с одиночной отправкой (singledispatch)., это динамическая привязка в объектно-ориентированном!

Предположим, что для выражения сообщения a.m(b,c), если к нему может быть привязано соответствующее тело метода в соответствии с фактическими типами a, b и c, это называется тройной отправкой. Для простоты достаточно изучить двойную диспетчеризацию.

Так называемыйдвойная отправка, есть надежда, что a.foo(b) сможет ① Связать тело метода его переопределения в соответствии с фактическим типом a и может ② Свяжите его перегруженные методы в соответствии с фактическим типом b, то есть с соответствующими телами методов в foo(Y), foo(Y1) и foo(Y2). [Что касается связанных концепций, см. «Шаблон проектирования. 5.11 Шаблон посетителя» на стр. 223.]

К сожалению, Java не поддерживает двойную отправку. Для перегруженных методов foo(X), foo(X1) и foo(X2) Java компилирует foo(b) в соответствии с правилами b.тип объявленияТело метода foo(X) статически связано, без оценки того, является ли фактический тип b X1 или X2. Технология идентификации типа во время выполнения (Run-Time TypeIdentification, RTTI) может использоваться в Java, то есть фактический тип оценивается с использованием ключевого слова instanceof. Хотя объявленный тип является родительским классом Y, программа повторно объявляет temp в соответствии с фактическим типом и приводит параметр вниз. Хотя RTTI имеет лаконичный код, он недостаточно элегантен, чтобы использовать операторы ветвления. Кроме того, ① программист также должен сначала обратить внимание на определение конкретного типа; ② RTTI займет больше времени и места.

В "Java Programming Thought" есть предложение

За исключением статических методов и конечных методов в Java (приватные методы являются окончательными методами), все остальные методы являются поздним связыванием, то есть связыванием во время выполнения, и нам не нужно судить, следует ли выполнять позднее связывание — это происходит автоматически.

Упомянутая здесь поздняя привязка выбирает только конкретный метод для объявленного типа параметра.

Использование шаблонов проектирования для достижения двойной отправки

Так как Java поддерживает am(b), соответствующий метод связывается в соответствии с конкретным типом a, то если вызов b.m1() каким-то образом завершится в реализации am(b), то он не будет реализована "двойная отправка"? Глядя на GOF23, есть два шаблона проектирования, которые идеально поддерживают этот, а именнокомандный режима такжешаблон посетителя

Шаблон команды реализует двойную отправку

исходный код

UML-диаграмма шаблона команды

Во-первых, не по теме, я думаю, вы можете увидеть некоторые проблемы из диаграммы UML.Почему Клиент должен знать и Invoker, и Receiver?Роль Invoker принимает команду клиента и выполняет команду, а реальный исполнитель команды Получатель, используя Например, в «Дзен шаблонов проектирования», Инвокер — менеджер проекта, а Получатель — фермер или художник рабочего кода и т. д. По Закону Деметры Клиент не должен знать Получателя. Мы обсудим этот вопрос позже. Давайте сначала посмотрим на код:

абстрактный приемник

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();
    }
}

АннотацияКоманда

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), это некрасиво, простите меня все. В клиенте Receiver и Command объявляются в соответствии с интерфейсом.invoker.setCommand(command1); invoker.act();, программа переходит к

a is this.command, Java может найти конкретный класс реализации, а объявленный тип b — Receiver, который Java не распознает, поэтому его можно рассматривать какConcreteCommand1.execute(Receiver);, поэтому Java связывает первый метод в ConcreteCommand1

Распечатка, которую мы видим,我是command1, 入参是Receiver

В этом методе приемник становитсяa.m(b)В a, поскольку Java может выполнять привязку методов в соответствии с его фактическим типом, он выполнялся в ConcreteReceiver 1. Чтобы беззастенчиво удерживать a.m(b), я написал здесь три перегруженных метода. В настоящее время b — это ConcreteCommand1, поэтому найдите перегруженный метод.

посмотреть распечаткуreceiver1 处理命令1

Лучшие практики

«Дзен шаблонов проектирования» также упомянул в начале мои сомнения, почему Клиент должен знать о существовании Receiver? На самом деле, в нашей реальной работе никто этим особо не занимается. Цитирую отрывок из книги:

Каждый режим имеет некоторые деформации, когда он применяется к реальному приложению.Приемник командного режима обычно инкапсулируется в фактическое приложение (если это не очень необходимо, например, обработка отзыва), потому что в проекте: соглашение имеет имеет наивысший приоритет, а каждая команда A является инкапсуляцией одного или нескольких приемников. Мы можем обрабатывать отношения связи между ролью команды и ролью получателя с помощью осмысленных имен классов или имен команд в проекте (это соглашение), уменьшая пары модулей высокого уровня (класса клиента) Зависимости модулей низкого уровня (классы ролей получателя) улучшают общую стабильность системы. Поэтому рекомендуется использовать метод закрытого Получателя в реальной разработке проекта (конечно, доброжелательный видит благожелательность, а мудрый видит мудрость) для снижения зависимости Клиента от Получателя.

шаблон посетителя

Друзья, знающие режим посетителя, обязательно скажут, а где тут командный режим в этой тм, это явно режим посетителя в скине командного режима! Действительно, чтобы найти способы объяснить двойную диспетчеризацию, командный режим был извращен, так что давайте внимательно рассмотрим режим посетителя. Возьмем пример «Дзен шаблонов проектирования».

Актеры играют роли в кино. Актер может играть несколько ролей. Давайте сначала определим две роли в кино и на телевидении: главный герой кунг-фу и второстепенная роль идиота.

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», я вернусь в этот раздел Заполнить.

Справочная документация

Woohoo.ITeye.com/topic/11307… Woohoo.void cn.com/article/afraid-of…Дзен шаблонов проектирования «Идеи программирования на Java»