@jlacar/

VisitorPatternDoubleDispatch

Java

Example of Visitor double dispatch pattern. Based on this discussion: https://coderanch.com/t/699911/java/object-communicate-object

fork
loading
Files
  • Main.java
Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import java.util.*;
 
class Main {
  public static void main(String[] args) {
    equip(new Wizard(), 
      new Weapon("Anything but a Staff"),
      new Sword("Blackfyre"), 
      new Staff("The Runestaff"), 
      new Staff("Kaladanda"), 
      new Staff("Ru Yi Jing Gu Bang"),
      new Gun("Musket"),
      new Knife("Tanto")
    );

    equip(new Warrior(), 
      new Sword("Blackfyre"), 
      new Staff("The Runestaff"), 
      new Sword("Excalibur"),
      new Gun("Rifle"),
      new Knife("Dagger")
    );

    equip(new Batman(),
      new Weapon("His Mind"),
      new Sword("Katana"),
      new Staff("Bo Staff"),
      new Gun("M1911"),
      new Knife("Balisong")
    );

    equip(new Soldier(), 
      new Weapon("Blunt Force Object"),
      new Sword("Blackfyre"), 
      new Staff("The Runestaff"), 
      new Sword("Excalibur"),
      new Gun("M16"),
      new Knife("Ka-Bar")
    );
  }

  private static void equip(Character character, Weapon... weapons) {
    String charType = character.getClass().getName();
    System.out.printf("Weapons check for %s:%n", charType);

    character.check(weapons);

    System.out.printf("%nWeapons accepted by %s: %s%n%n", 
      charType, character.weaponsListing());
  }
}

interface WeaponRule {

// DEVELOPER NOTE:
// When a new Character type is defined, you must add a
// method to this interface to define the default rule 
// for that character. 

// The default rules below say that a Wizard and Warrior
// cannot use a weapon by default. Weapon subclasses that
// they are allowed to use should override the canBeUsedBy()
// method for the specific Character type.
// 
// The default rules also say that Batman and Soldier 
// can use any weapon by default. Weapon subclasses can
// override this behavior to prevent their use by Batman
// and/or Soldier.

  default boolean canBeUsedBy(Wizard wiz) { return false; }
  default boolean canBeUsedBy(Warrior war) { return false; }
  default boolean canBeUsedBy(Batman bruce) { return true; }
  default boolean canBeUsedBy(Soldier grunt) { return true; }
}

class Weapon implements WeaponRule {

  private String name;

  public Weapon() {
    this.name = this.getClass().getName();
  }

  public Weapon(String name) {
    this.name = name;
  }

  @Override 
  public String toString() {
    return this.name;
  }
}

class Knife extends Weapon {
  public Knife() {}

  public Knife(String name) {
    super(name);
  }
}

class Gun extends Weapon {
  public Gun() {}

  public Gun(String name) {
    super(name);
  }

  @Override
  public boolean canBeUsedBy(Batman bruce) { 
    // Just on principle, Batman won't use a Gun
    return false; 
  }
}

class Sword extends Weapon {
  public Sword() {}

  public Sword(String name) {
    super(name);
  }

  @Override 
  public boolean canBeUsedBy(Warrior war) { return true; }

  @Override
  public boolean canBeUsedBy(Soldier grunt) { return false; }
}

class Staff extends Weapon {
  public Staff() {}

  public Staff(String name) {
    super(name);
  }

  @Override
  public boolean canBeUsedBy(Wizard wiz) { 
    // A Wizard can only use a Staff
    return true; 
  }

}

abstract class Character {

  private List<Weapon> weapons = new ArrayList<>();

  // Declaring this method as final ensures that the 
  // double dispatch mechanism doesn't get inadvertently 
  // overridden and broken by anyone in the future. This
  // is the Template Method design pattern.
  public final boolean accept(Weapon weapon) {
    if (isLegal(weapon)) {
      return weapons.add(weapon);
    }
    return false;
  }

  // The call to this method is the first dispatch.
  // Subclasses then call Weapon#canBeUsedBy(this) to
  // implement a proper double dispatch and leverage
  // the type system and polymorphism. This is the 
  // variable part of the accept() Template Method.
  protected abstract boolean isLegal(Weapon w);

  public String weaponsListing() {
    return Arrays.toString(weapons.toArray());
  }

  public void check(Weapon... weapons) {
    for (Weapon w : weapons) {
      String weaponType = w.getClass().getName();
      if (accept(w)) {
        System.out.printf("%s (%s) accepted%n", w, weaponType);
      } else {
        System.out.printf("Cannot accept %s (%s)%n", w, weaponType);
      }
    }
  }
}

// Because of the way 'this' is bound (at compile time),
// each subclass of Character needs to provide its 
// own implementation of the isLegal() method so that
// double dispatch polymorphism can be leveraged. 
// 
// Caveat: Attempting to DRY this up by moving it 
// up to the superclass ** WON'T WORK **

class Wizard extends Character {
  @Override 
  protected boolean isLegal(Weapon w) {
    return w.canBeUsedBy(this); // 2nd dispatch
  }
}

class Warrior extends Character {
  @Override 
  protected boolean isLegal(Weapon w) {
    return w.canBeUsedBy(this); // 2nd dispatch
  } 
}

class Batman extends Character {
  @Override
  protected boolean isLegal(Weapon w) {
    return w.canBeUsedBy(this); // 2nd dispatch
  }
}

class Soldier extends Warrior {
  @Override
  protected boolean isLegal(Weapon w) {
    return w.canBeUsedBy(this); // 2nd dispatch
  } 
}