當一群相似結構的物件們,在執行相同方法時卻有著不同實作內容,那可以將方法封裝成獨立物件。當需要增加新的方法時,不用改變物件的結構,只需要增加封裝的方法就能使用。如此一來,讓方法的擴充、修改變得容易許多。
試想一個 RPG 的情境,建立一個職業物件,本身可以執行兩個方法:
class Warrior {
constructor() {
this.hp = 35
this.mp = 0
}
attack() {
// 執行 Warrior 專屬的 attack
}
defense() {
// 執行 Warrior 專屬的 attack
}
}
現在,要建立一個擁有相同方法的新職業,其方法內的實作細節不同:
class Thief {
constructor() {
this.hp = 30
this.mp = 0
}
attack() {
// 執行 Thief 專屬的 attack
}
defense() {
// 執行 Thief 專屬的 defense
}
}
假如又需要新增一個職業,同時三個職業還要新增一個方法呢?
class Warrior {
constructor() {
this.hp = 35
this.mp = 0
}
attack() {
// 執行 Warrior 專屬的 attack
}
defense() {
// 執行 Warrior 專屬的 defense
}
run() {
// 執行 Warrior 專屬的 run
}
}
class Thief {
constructor() {
this.hp = 30
this.mp = 0
}
attack() {
// 執行 Thief 專屬的 attack
}
defense() {
// 執行 Thief 專屬的 defense
}
run() {
// 執行 Thief 專屬的 run
}
}
class BlackMage {
constructor() {
this.hp = 25
this.mp = 10
}
attack() {
// 執行 BlackMage 專屬的 attack
}
defense() {
// 執行 BlackMage 專屬的 defense
}
run() {
// 執行 BlackMage 專屬的 run
}
}
隨著職業的增加、方法的增加,會變得越難管理。
仔細觀察,職業的結構相似,差異在方法,那有沒有一個模式,可以將方法封裝起來,同時使用方法時,能夠配合不同的職業而執行不同的內容呢?
這就是 Visitor 模式的由來。
作法是:
剛剛的 RPG 情境,發展到現在,可以製作成表格:
Warrior | Thief | BlackMage | |
---|---|---|---|
attack | AttackByWarrior | AttackByThief | AttackByBlackMage |
defense | DefenseByWarrior | DefenseByThief | DefenseByBlackMage |
run | RunByWarrior | RunByThief | RunByBlackMage |
magic | MagicByWarrior | MagicByThief | MagicByBlackMage |
以下範例以「模擬簡易 RPG」為核心,將製作:
建立方法的虛擬層親代物件:Action
public interface Action {
public abstract void executeWarriorAction(Warrior element);
public abstract void executeThiefAction(Thief element);
public abstract void executeBlackMageAction(BlackMage element);
}
建立職業物件的虛擬層親代:Character
public abstract class Character {
protected String name;
protected String job;
protected int hp;
protected int mp;
protected int level;
protected Character(String name, String job, int hp, int mp, int level) {
this.name = name;
this.job = job;
this.hp = hp;
this.mp = mp;
this.level = level;
System.out.println("The character " + this.name + " is created successfully");
}
public abstract void levelUp();
public abstract void showCharacterInformation();
public abstract void accept(Action visitor);
}
建立職業物件的子代:Warrior
、Thief
、BlackMage
(Element 物件)
public class Warrior extends Character {
public Warrior(String name) {
super(name, "Warrior", 30, 0, 1);
}
@Override
public void showCharacterInformation() {
System.out.println("The class is " + job + ", and the name is " + name);
System.out.println("The hp is " + hp + ", the mp is: " + mp + "and the level is " + level);
System.out.println("'I see, I come, I conquer' by Julius Caesar\n");
}
@Override
public void accept(Action visitor) {
visitor.executeWarriorAction(this);
}
@Override
public void levelUp() {
this.hp += 3;
this.level += 1;
}
}
public class Thief extends Character {
public Thief(String name) {
super(name, "Thief", 35, 0, 1);
}
@Override
public void showCharacterInformation() {
System.out.println("The class is " + job + ", and the name is " + name);
System.out.println("The hp is " + hp + ", the mp is: " + mp + "and the level is " + level);
System.out.println("'Everything is permitted, Nothing is true.' by Assassin's Creed\n");
}
@Override
public void accept(Action visitor) {
visitor.executeThiefAction(this);
}
@Override
public void levelUp() {
this.hp += 2;
this.level += 1;
}
}
public class BlackMage extends Character {
public BlackMage(String name) {
super(name, "Black Mage", 35, 0, 1);
}
@Override
public void showCharacterInformation() {
System.out.println("The class is " + job + ", and the name is " + name);
System.out.println("The hp is " + hp + ", the mp is: " + mp + "and the level is " + level);
System.out.println("'Knowledge is power, but using it wisely is the key.' by Khadgar\n");
}
@Override
public void accept(Action visitor) {
visitor.executeBlackMageAction(this);
}
@Override
public void levelUp() {
this.hp += 1;
this.mp += 3;
this.level += 1;
}
}
建立方法的子代物件:Attack
、Defense
、Run
、Magic
(Visitor 物件)
public class Attack implements Action {
@Override
public void executeWarriorAction(Warrior element) {
System.out.println("Use sword to attack enemy");
}
@Override
public void executeThiefAction(Thief element) {
System.out.println("Use dagger to stab enemy");
}
@Override
public void executeBlackMageAction(BlackMage element) {
System.out.println("It's is wrong decision");
}
}
public class Defense implements Action {
@Override
public void executeWarriorAction(Warrior element) {
System.out.println("Use shield to protect team members");
}
@Override
public void executeThiefAction(Thief element) {
System.out.println("Try to dodge this attack");
}
@Override
public void executeBlackMageAction(BlackMage element) {
System.out.println("Nothing to do");
}
}
public class Run implements Action {
@Override
public void executeWarriorAction(Warrior element) {
System.out.println("The last one to run");
}
@Override
public void executeThiefAction(Thief element) {
System.out.println("The fast one to run");
}
@Override
public void executeBlackMageAction(BlackMage element) {
System.out.println("The slow one to run");
}
}
public class Magic implements Action {
@Override
public void executeWarriorAction(Warrior element) {
System.out.println("There is no mana");
}
@Override
public void executeThiefAction(Thief element) {
System.out.println("The is no mana");
}
@Override
public void executeBlackMageAction(BlackMage element) {
System.out.println("Use fire ball!!!");
}
}
測試,模擬 RPG 的角色同時執行相同的命令:RPGVisitorSample
public class RPGVisitorSample {
public static void main(String[] args) {
System.out.println("建立角色");
List<Character> characters = new ArrayList<>();
characters.add(new Warrior("Zest"));
characters.add(new Thief("Sauber"));
characters.add(new BlackMage("Fritz"));
System.out.println("\n集體攻擊");
for (Character character : characters) {
character.accept(new Attack());
}
System.out.println("\n集體防禦");
for (Character character : characters) {
character.accept(new Defense());
}
System.out.println("\n集體使用魔法");
for (Character character : characters) {
character.accept(new Magic());
}
System.out.println("\n集體逃跑");
for (Character character : characters) {
character.accept(new Run());
}
}
}
建立方法的虛擬層親代物件:Action
/** @abstract */
class Character {
/**
* @param {string} name
* @param {string} job
* @param {int} hp
* @param {int} mp
* @param {int} level
*/
constructor(name, job, hp, mp, level) {
this.name = name;
this.job = job;
this.hp = hp;
this.mp = mp;
this.level = level;
console.log("The character " + this.name + " is created successfully");
}
/** @abstract */
levelUp() { return; }
/** @abstract */
showCharacterInformation() { return; }
/**
* @abstract
* @param {Action} visitor
*/
accept(visitor) { return; }
}
建立職業物件的虛擬層親代:Character
/** @abstract */
class Action {
/**
* @abstract
* @param {Warrior} element
*/
executeWarriorAction(element) { return; }
/**
* @abstract
* @param {Thief} element
*/
executeThiefAction(element) { return; }
/**
* @abstract
* @param {BlackMage} element
*/
executeBlackMageAction(element) { return; }
}
建立職業物件的子代:Warrior
、Thief
、BlackMage
(Element 物件)
class Warrior extends Character {
/** @param {string} name */
constructor(name) {
super(name, "Warrior", 30, 0, 1);
}
/** @override */
showCharacterInformation() {
console.log("The class is " + this.job + ", and the name is " + this.name);
console.log("The hp is " + this.hp + ", the mp is: " + this.mp + "and the level is " + this.level);
console.log("'I see, I come, I conquer' by Julius Caesar\n");
}
/**
* @override
* @param {Action} visitor
*/
accept(visitor) {
visitor.executeWarriorAction(this);
}
/** @override */
levelUp() {
this.hp += 3;
this.level += 1;
}
}
class Thief extends Character {
/** @param {string} name */
constructor(name) {
super(name, "Thief", 35, 0, 1);
}
/** @override */
showCharacterInformation() {
console.log("The class is " + this.job + ", and the name is " + this.name);
console.log("The hp is " + this.hp + ", the mp is: " + this.mp + "and the level is " + this.level);
console.log("'Everything is permitted, Nothing is true.' by Assassin's Creed\n");
}
/**
* @override
* @param {Action} visitor
*/
accept(visitor) {
visitor.executeThiefAction(this);
}
/** @override */
levelUp() {
this.hp += 2;
this.level += 1;
}
}
class BlackMage extends Character {
/** @param {string} name */
constructor(name) {
super(name, "Black Mage", 35, 0, 1);
}
/** @override */
showCharacterInformation() {
console.log("The class is " + this.job + ", and the name is " + this.name);
console.log("The hp is " + this.hp + ", the mp is: " + this.mp + "and the level is " + this.level);
console.log("'Knowledge is power, but using it wisely is the key.' by Khadgar\n");
}
/**
* @override
* @param {Action} visitor
*/
accept(visitor) {
visitor.executeBlackMageAction(this);
}
/** @override */
levelUp() {
this.hp += 1;
this.mp += 3;
this.level += 1;
}
}
建立方法的子代物件:Attack
、Defense
、Run
、Magic
(Visitor 物件)
class Attack extends Action {
/**
* @override
* @param {Warrior} element
*/
executeWarriorAction(element) {
console.log("Use sword to attack enemy");
}
/**
* @override
* @param {Thief} element
*/
executeThiefAction(element) {
console.log("Use dagger to stab enemy");
}
/**
* @override
* @param {BlackMage} element
*/
executeBlackMageAction(element) {
console.log("It's is wrong decision");
}
}
class Defense extends Action {
/**
* @override
* @param {Warrior} element
*/
executeWarriorAction(element) {
console.log("Use shield to protect team members");
}
/**
* @override
* @param {Thief} element
*/
executeThiefAction(element) {
console.log("Try to dodge this attack");
}
/**
* @override
* @param {BlackMage} element
*/
executeBlackMageAction(element) {
console.log("Nothing to do");
}
}
class Run extends Action {
/**
* @override
* @param {Warrior} element
*/
executeWarriorAction(element) {
console.log("The last one to run");
}
/**
* @override
* @param {Thief} element
*/
executeThiefAction(element) {
console.log("The fast one to run");
}
/**
* @override
* @param {BlackMage} element
*/
executeBlackMageAction(element) {
console.log("The slow one to run");
}
}
class Magic extends Action {
/**
* @override
* @param {Warrior} element
*/
executeWarriorAction(element) {
console.log("There is no mana");
}
/**
* @override
* @param {Thief} element
*/
executeThiefAction(element) {
console.log("The is no mana");
}
/**
* @override
* @param {BlackMage} element
*/
executeBlackMageAction(element) {
console.log("Use fire ball!!!");
}
}
測試,模擬 RPG 的角色同時執行相同的命令:rpgVisitorSample
const rpgVisitorSample = () => {
console.log("建立角色");
/** @type {Character[]} */
let characters = [];
characters.push(new Warrior("Zest"));
characters.push(new Thief("Sauber"));
characters.push(new BlackMage("Fritz"));
console.log("\n集體攻擊");
for (const character of characters) {
character.accept(new Attack());
}
console.log("\n集體防禦");
for (const character of characters) {
character.accept(new Defense());
}
console.log("\n集體使用魔法");
for (const character of characters) {
character.accept(new Magic());
}
console.log("\n集體逃跑");
for (const character of characters) {
character.accept(new Run());
}
};
rpgVisitorSample();
Visitor 模式的前提是「多個擁有相同資料結構的物件,在相同名稱的方法內執行不同內容的程式碼」,換句話說,必須在物件的資料結構以及方法都十分清楚時才能套用,這兩個條件缺乏任一都無法使用此方法。
也因為如此,能夠實作的場合不多,當程式碼因為擴充方法而開始混亂時才有登場的機會。
這邊簡單列出模式的優缺點:
最後補充一點,因為 Visitor 與 Element 互相依賴,技術上稱作 Double Dispatch
。
所以的模式都介紹完畢,明天將總結各個模式。