当一群相似结构的物件们,在执行相同方法时却有着不同实作内容,那可以将方法封装成独立物件。当需要增加新的方法时,不用改变物件的结构,只需要增加封装的方法就能使用。如此一来,让方法的扩充、修改变得容易许多。
试想一个 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
。
所以的模式都介绍完毕,明天将总结各个模式。
<<: Day 29:653. Two Sum IV - Input is a BST
对于 Powershell 脚本的参数,我们可以通过一些属性来限制参数。 今天我们就来看看,怎么通过...
前言 在这个蔬菜是有机的、水果都会甜、衣服耐洗不缩水、满街百年创始老店、车子很省油、房…的社会当中,...
终於来到 interface,觉得这个算是颇重要的一趴,让我们看下去。这大概是我最认真做笔记的一篇...
前言 在上一个范例中,是写死回传的内容,显然在现实生活中应该是不会有公司让你可以这样做的,而当我们的...
写 CSS 的时候常常会有些设定是重复出现的,SASS(SCSS)是一个方便的预处理器,提供了变数、...