What are single dispatch and double dispatch
Dispatch is the process of binding the corresponding method body of an object according to its actual type.
For example, there is class X and its two subclasses X1 and X2, both of which implement instance methods m() — usually subclasses X1 and X2 should be preceded by @override, so there are three m().
For the message expression a.m(b,c), the corresponding method body is bound according to the actual type of an object, called single dispatch. Of course, this “one object” is special. Each message expression a.m(b,c) has only one message receiver, and this “one object” refers to the message receiver, a. in a.m(b,c). So binding only the method body provided by the actual type of the message receiver, known as singleDispatch, is a dynamic binding in object-oriented terms!
Suppose that for the message expression A.m. (b, C), if the corresponding method body can be bound according to the actual types of A, B, and C, it is called triple dispatch. For simplicity, look at double dispatches.
Double dispatch expects A. Foo (b) to ① bind the body of its override method to the actual type of A, and ② bind the appropriate body of the overloaded methods in foo(Y), foo(Y1), and foo(Y2) to the actual type of B. [For related concepts, please refer to design Patterns.5.11 Visitor Patterns. P223]
Unfortunately, Java does not support double dispatch. For overloaded methods such as foo(X), foo(X1), and foo(X2), Java at compile time statically binds foo(b) to the body of foo(X) according to b’s declared type, without determining whether B’s actual type is X1 or X2. In Java, you can use run-time TypeIdentification (RTTI) techniques, which use the keyword instanceof to determine the actual type. Although the type is declared as parent Y, the program redeclares temp as the actual type and shapes the parameters downward. RTTI code is concise, but the use of branch statements is not elegant. In addition, ① programmers should also pay attention to the specific type of judgment before; ②RTTI takes up more running time and space.
There’s a quote in Java Programming Ideas
All methods in Java except static and final methods (private methods are final) are late bound, that is, run-time binding, and we don’t have to decide whether late binding should be done – it happens automatically.
The late binding mentioned here only selects the specific method for the declared type of the parameter.
Rely on design patterns for double dispatch
Since Java supports a.m. (b), which binds methods to the specific type of A, if b.m. (b) is somehow called in a.m. (b), then “double dispatch” will be implemented. Looking at GOF23, there are two design patterns that perfectly support this one: command and visitor patterns
The command mode implements double dispatch
The source code
UML diagram of command pattern
Abstract the receiver
public abstract class Receiver {
abstract void doSth();
}
Copy the code
ConcreteReceiver1
public class ConcreteCommand1 extends Command {
private Receiver receiver;
public ConcreteCommand1(Receiver receiver) {
this.receiver = receiver;
}
@Override
void execute(Receiver receiver) {
System.out.println("I'm command1, incoming parameter is Receiver");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver1 receiver) {
System.out.println("I'm command1, the input parameter is ConcreteReceiver1");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver2 receiver) {
System.out.println("I'm command1, the input parameter is ConcreteReceiver2"); receiver.doSth(); }}Copy the code
Abstract the 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() {
returnreceiver; }}Copy the code
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("I'm command1, incoming parameter is Receiver");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver1 receiver) {
System.out.println("I'm command1, the input parameter is ConcreteReceiver1");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver2 receiver) {
System.out.println("I'm command1, the input parameter is ConcreteReceiver2"); receiver.doSth(); }}Copy the code
ConcreteCommand2
public class ConcreteCommand2 extends Command {
public ConcreteCommand2(Receiver receiver) {
super(receiver);
}
@Override
void execute(Receiver receiver) {
System.out.println("I'm command2, and the incoming parameter is Receiver.");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver1 receiver) {
System.out.println("I'm command2, and the input parameter is ConcreteReceiver1");
receiver.doSth();
}
@Override
void execute(ConcreteReceiver2 receiver) {
System.out.println("I'm command2, and the input parameter is ConcreteReceiver2");
receiver.doSth();
}
@Override
public Receiver getReceiver() {
returnsuper.getReceiver(); }}Copy the code
Invoker
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void act() { this.command.execute(command.getReceiver()); }}Copy the code
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(); }}Copy the code
The execution result
I am acommand1. The input parameter is Receiver Receiver1 Processing command 1Copy the code
Analysis of the
In order to make up the a.m. (B) format, it’s very ugly, forgive me. In the Client, Receiver and Command are declared by interface, when executing invoker.setCommand(command1); invoker.act(); , the program goes to
ConcreteCommand1.execute(Receiver);
I'm command1 and the incoming parameter is Receiver
In this method, receiver becomes the A in A.M. (b), because Java can bind methods to their actual types, so in ConcreteReceiver1, three overloaded methods are written to inasmuch promote A.M. (b). Now b is ConcreteCommand1, so find overloaded methods
Receiver1 Processing command 1
Best practices
Zen of Design Patterns also addresses the question I started with, why should the Client know about the existence of the Receiver? In fact, in our actual work, no one actually does that. To quote a passage from the book:
Each mode has some distortion when applied in practice. The command mode Receiver is generally encapsulated in practice (unless it is absolutely necessary, such as undo processing) because in the project: The convention has the highest priority. Each command is the encapsulation of one or more receivers. We can deal with the coupling relationship between command role and Receiver role (this is the convention) through meaningful class name or command name in the project, and reduce the dependency between the high-level module (Client class) and the low-level module (Receiver role class). Improve the overall stability of the system. Therefore, it is recommended to use closed Receiver in actual project development (of course, it depends) to reduce Client’s dependence on Reciver.
Visitor pattern
Those of you who are familiar with the visitor pattern will say, “What the hell is this command mode? It’s the visitor pattern in command mode.” Indeed, the command mode has been perverted in an attempt to explain double dispatch, so take a good look at the visitor mode. Take zen of Design Patterns as an example
Actors play movie roles. An actor can play more than one role. Let’s first define two roles in a movie: kung fu hero and idiot supporting actor
} public class KungFuRole implements Role {// KungFuRole implements Role} public class IdiotRole Implements Role {// a retarded Role}Copy the code
With the roles, let’s define an actor abstract class
Public void act(Role Role){system.out.println ("An actor can play any role."); } // KungFuRole public void act(KungFuRole role){system.out.println ("Actors can play kung fu roles."); }}Copy the code
Very simple, using Java overloads, let’s look at young actors and old actors, using overrides to refine the functionality of abstract classes
Public class YoungActor extends AbsActor {public void act(KungFuRole role){system.out.println ("Favorite kung fu role."); }} public class OldActor extends AbsActor {public void act(KungFuRole role){system.out.println ("Too old for kung Fu roles."); }}Copy the code
Overwrite and overload are already implemented, so we write a scenario,
Public class Client {public static void main(String[] args) {AbsActor actor = new OldActor(); // Define a Role Role Role = new KungFuRole(); // start acting actor. Act (role); actor.act(new KungFuRole()); }}Copy the code
Get the output
Actors can play any role. They are too old to play kung fu rolesCopy the code
Using the methods described in the previous section, it is very easy to analyze how double dispatch works.
Deep principle
As you can see, double dispatch via design pattern is actually “pseudo-double dispatch.” At least the underlying principles require more reading, and I’ll come back to cover this section after I finish understanding the Java Virtual Machine.
Reference documentation
www.iteye.com/topic/11307… www.voidcn.com/article/p-d… Zen of Design Patterns Java Programming Ideas