具體元素的接口與實現類
public interface Person {
void accept(Visitor visitor);
}
public class Woman implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Man implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
訪問者接口與實現類,分別代表男人與女人在不同的狀態下的表現
public interface Visitor {
public void visit(Man man);
public void visit(Woman girl);
}
//成功時Man與Woman的不同表現
public class Success implements Visitor{
public void visit(Man man) {
System.out.println("當男人成功時,背后多半有一個偉大的女人");
}
public void visit(Woman woman) {
System.out.println("當女人成功時,背后大多有一個不成功的男人");
}
}
//戀愛時Man與Woman的不同表現
public class Love implements Visitor{
public void visit(Man man) {
System.out.println("當男人戀愛時,凡事不懂也裝懂");
}
public void visit(Woman girl) {
System.out.println("當女人戀愛時,遇事懂也裝不懂");
}
}
ObjectStructure與客戶端測試代碼
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍歷各種具體元素并執行他們的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當成功時不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
//當戀愛時的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
}
}
需求的變化
假設現在需求要擴展數據結構,增加一種具體元素,男與女之外的一種不明物體,我們暫時把它稱為“怪獸”,在既有訪問者模式的架構下,應該怎樣?首先增加一個Bruce類,實現Person接口。最麻煩的是要修改訪問者接口及其所有具體訪問者!
因為Visit方法中沒有包含訪問Bruce對象的行為,因此我們被迫要去手工更改Visitor(包括抽象的,具體的),在其中添加有關Bruce對象的行為,這嚴重違反了“開放-封閉”原則。究其原因在于目前的結構下,被訪問對象與訪問對象互相依賴,自然不利于分離變化,必須去掉一層依賴關系。
我們嘗試把Visitor對Person(元素)的依賴關系去掉,抽象出對應每個具體元素的ElementVisitor接口 -->ManVisitor,WomanVisitor,然后把Visitor對Person的依賴關系轉移到ManVisitor與 WomanVisitor身上。
現在Visitor接口已經沒有任何抽象方法,只是一個空接口,每一個具體元素對應有一個ElementVisitor接口,每一個元素對應的ElementVisitor接口有訪問該元素的visit(),相當把原來在Visitor接口中聲明工作,交由各個具體ElementVisitor接口完成。
經過改造后的代碼:
原Visitor接口
public interface Visitor {
//退化到沒有任何抽象方法
}
新增加ManVisitor,WomanVisitor接口
public interface ManVisitor {
public void visit(Man man);
}
public interface WomanVisitor {
public void visit(Woman w);
}
具體Visitor實現類現在同時實現3個接口
//由實現Visitor接口擴展成實現Visitor,WomanVisitor,ManVisitor三個接口
public class Success implements Visitor,WomanVisitor,ManVisitor{
public void visit(Man man) {
System.out.println("當男人成功時,背后多半有一個偉大的女人");
}
public void visit(Woman girl) {
System.out.println("當女人成功時,背后大多有一個不成功的男人");
}
}
//由實現Visitor接口擴展成實現Visitor,WomanVisitor,ManVisitor三個接口
public class Love implements Visitor,WomanVisitor,ManVisitor{
public void visit(Man man) {
System.out.println("當男人戀愛時,凡事不懂也裝懂");
}
public void visit(Woman girl) {
System.out.println("當女人戀愛時,遇事懂也裝不懂");
}
}
Person接口沒有變化,依舊只依賴于Visitor接口
public interface Person {
void accept(Visitor visitor);
}
改造后的具體元素類Man與Woman
public class Man implements Person {
// 先對visitor進行類型轉換,再執行visit方法,因為Visitor接口已經沒有聲明任何抽象方法了
public void accept(Visitor visitor) {
if (visitor instanceof ManVisitor) {
ManVisitor mv = (ManVisitor) visitor;
mv.visit(this);
}
}
}
public class Woman implements Person {
// 先對visitor進行類型轉換,再執行visit方法,因為Visitor接口已經沒有聲明任何抽象方法了
public void accept(Visitor visitor) {
if (visitor instanceof WomanVisitor) {
WomanVisitor wv = (WomanVisitor) visitor;
wv.visit(this);
}
}
}
ObjectStructure與客戶端測試代碼沒有變化
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍歷各種具體元素并執行他們的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當成功時不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
//當戀愛時的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
}
}
至此改造完畢!我們執行客戶端測試代碼,結果顯示:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
此時,客戶端仍然只依賴于Visitor空接口與ObjectStructure類。可能一開始大家會認為空接口沒有什么用,現在就能體現出他的威力了,使客戶端與具體Visitor的高度解耦!也正是這種思維的核心在JavaAPI中也有類似的應用,這種空接口被稱為標識接口。比如java.io.Serializable與java.rmi.Remote等,標識接口里沒有任何方法和屬性,標識不對實現接口不對實現它的類有任何語義上的要求,它僅僅是表明實現它的類屬于一種特定的類型。
上面具體訪問者實現的多個接口被稱為混合類型。這個概念《Java與模式》中有提及過:當一個具體類處于一個類的等級結構之中的時候,為這個具體類定義一個混合類型是可以保證基于這個類型的可插入性的關鍵。
=================================無敵分界線====================================
講了這么長,現在我們測試下改造后的訪問者模式
首先增加一種行為(狀態),即原訪問者模式的優點
增加一個具體訪問者Fail,修改一下客戶端測試代碼
public class Fail implements Visitor,ManVisitor,WomanVisitor{
public void visit(Man man) {
System.out.println("當男人失敗時,悶頭喝酒,誰也不用勸");
}
public void visit(Woman woman) {
System.out.println("當女人失敗時,眼淚汪汪,誰也勸不了");
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當成功時不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
System.out.println();
//當戀愛時的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
System.out.println();
//新增加失敗時的不同反映
Visitor fail = new Fail();
o.display(fail);
}
}
結果顯示:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
當男人失敗時,悶頭喝酒,誰也不用勸
當女人失敗時,眼淚汪汪,誰也勸不了
增加新的行為(狀態)與原來一樣方便!只需要增加一個具體訪問者即可!
現在我們來增加一個具體元素(正是寫這篇文章的初衷)
首先增加一個具體元素Bruce
public class Bruce implements Person{
public void accept(Visitor visitor) {
if(visitor instanceof BruceVisitor){
BruceVisitor bv = (BruceVisitor) visitor;
bv.visit(this);
}
//這個else可寫可不寫
else{
String s = visitor.getClass().getName();
String state = s.substring(s.lastIndexOf(".")+1,s.length());
System.out.println("噢..原來怪獸在"+state+"的時候是沒有行為的!!");
}
}
}
//按照新的思維方式增加一個對應的ElementVisitor接口
public interface BruceVisitor {
public void visit(Bruce bruce);
}
我們讓Success這個具體訪問者多實現一個BruceVisitor訪問者接口,和修改一下客戶端代碼進行測試
public class Success implements Visitor,WomanVisitor,ManVisitor,BruceVisitor{
public void visit(Man man) {
System.out.println("當男人成功時,背后多半有一個偉大的女人");
}
public void visit(Woman girl) {
System.out.println("當女人成功時,背后大多有一個不成功的男人");
}
public void visit(Bruce bruce) {
System.out.println("當怪獸成功時.........無語..........");
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
o.attach(new Man());
o.attach(new Woman());
o.attach(new Bruce()); //新增一種具體元素Bruce
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
System.out.println();
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
System.out.println();
Visitor fail = new Fail();
o.display(fail);
}
}
顯示結果:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當怪獸成功時.........無語..........
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
噢..原來怪獸在Love的時候是沒有行為的!!
當男人失敗時,悶頭喝酒,誰也不用勸
當女人失敗時,眼淚汪汪,誰也勸不了
噢..原來怪獸在Fail的時候是沒有行為的!!
這個結果你滿意嗎?
雖然,這只是部分符合“開放-封閉”原則,我們不需要修改Visitor接口,但還是得去修改Success實現新的接口。但是修改具體類比修改接口的代價小得多,不需要重新編譯所有訪問接口和具體訪問者。使我們面對新的變化也容易得多。而且這還有一個好處,就是可以讓各種元素有選擇地讓別人訪問,如上述例子,這樣使訪問者模式的運用起來更加靈活。
public interface Person {
void accept(Visitor visitor);
}
public class Woman implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Man implements Person{
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
訪問者接口與實現類,分別代表男人與女人在不同的狀態下的表現
public interface Visitor {
public void visit(Man man);
public void visit(Woman girl);
}
//成功時Man與Woman的不同表現
public class Success implements Visitor{
public void visit(Man man) {
System.out.println("當男人成功時,背后多半有一個偉大的女人");
}
public void visit(Woman woman) {
System.out.println("當女人成功時,背后大多有一個不成功的男人");
}
}
//戀愛時Man與Woman的不同表現
public class Love implements Visitor{
public void visit(Man man) {
System.out.println("當男人戀愛時,凡事不懂也裝懂");
}
public void visit(Woman girl) {
System.out.println("當女人戀愛時,遇事懂也裝不懂");
}
}
ObjectStructure與客戶端測試代碼
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍歷各種具體元素并執行他們的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當成功時不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
//當戀愛時的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
}
}
需求的變化
假設現在需求要擴展數據結構,增加一種具體元素,男與女之外的一種不明物體,我們暫時把它稱為“怪獸”,在既有訪問者模式的架構下,應該怎樣?首先增加一個Bruce類,實現Person接口。最麻煩的是要修改訪問者接口及其所有具體訪問者!
因為Visit方法中沒有包含訪問Bruce對象的行為,因此我們被迫要去手工更改Visitor(包括抽象的,具體的),在其中添加有關Bruce對象的行為,這嚴重違反了“開放-封閉”原則。究其原因在于目前的結構下,被訪問對象與訪問對象互相依賴,自然不利于分離變化,必須去掉一層依賴關系。
我們嘗試把Visitor對Person(元素)的依賴關系去掉,抽象出對應每個具體元素的ElementVisitor接口 -->ManVisitor,WomanVisitor,然后把Visitor對Person的依賴關系轉移到ManVisitor與 WomanVisitor身上。
現在Visitor接口已經沒有任何抽象方法,只是一個空接口,每一個具體元素對應有一個ElementVisitor接口,每一個元素對應的ElementVisitor接口有訪問該元素的visit(),相當把原來在Visitor接口中聲明工作,交由各個具體ElementVisitor接口完成。
經過改造后的代碼:
原Visitor接口
public interface Visitor {
//退化到沒有任何抽象方法
}
新增加ManVisitor,WomanVisitor接口
public interface ManVisitor {
public void visit(Man man);
}
public interface WomanVisitor {
public void visit(Woman w);
}
具體Visitor實現類現在同時實現3個接口
//由實現Visitor接口擴展成實現Visitor,WomanVisitor,ManVisitor三個接口
public class Success implements Visitor,WomanVisitor,ManVisitor{
public void visit(Man man) {
System.out.println("當男人成功時,背后多半有一個偉大的女人");
}
public void visit(Woman girl) {
System.out.println("當女人成功時,背后大多有一個不成功的男人");
}
}
//由實現Visitor接口擴展成實現Visitor,WomanVisitor,ManVisitor三個接口
public class Love implements Visitor,WomanVisitor,ManVisitor{
public void visit(Man man) {
System.out.println("當男人戀愛時,凡事不懂也裝懂");
}
public void visit(Woman girl) {
System.out.println("當女人戀愛時,遇事懂也裝不懂");
}
}
Person接口沒有變化,依舊只依賴于Visitor接口
public interface Person {
void accept(Visitor visitor);
}
改造后的具體元素類Man與Woman
public class Man implements Person {
// 先對visitor進行類型轉換,再執行visit方法,因為Visitor接口已經沒有聲明任何抽象方法了
public void accept(Visitor visitor) {
if (visitor instanceof ManVisitor) {
ManVisitor mv = (ManVisitor) visitor;
mv.visit(this);
}
}
}
public class Woman implements Person {
// 先對visitor進行類型轉換,再執行visit方法,因為Visitor接口已經沒有聲明任何抽象方法了
public void accept(Visitor visitor) {
if (visitor instanceof WomanVisitor) {
WomanVisitor wv = (WomanVisitor) visitor;
wv.visit(this);
}
}
}
ObjectStructure與客戶端測試代碼沒有變化
import java.util.*;
public class ObjectStructure {
private List<Person> elements = new ArrayList<Person>();
public void attach(Person element){
elements.add(element);
}
public void detach(Person element){
elements.remove(elements);
}
//遍歷各種具體元素并執行他們的accept方法
public void display(Visitor visitor){
for(Person p:elements){
p.accept(visitor);
}
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當成功時不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
//當戀愛時的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
}
}
至此改造完畢!我們執行客戶端測試代碼,結果顯示:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
此時,客戶端仍然只依賴于Visitor空接口與ObjectStructure類。可能一開始大家會認為空接口沒有什么用,現在就能體現出他的威力了,使客戶端與具體Visitor的高度解耦!也正是這種思維的核心在JavaAPI中也有類似的應用,這種空接口被稱為標識接口。比如java.io.Serializable與java.rmi.Remote等,標識接口里沒有任何方法和屬性,標識不對實現接口不對實現它的類有任何語義上的要求,它僅僅是表明實現它的類屬于一種特定的類型。
上面具體訪問者實現的多個接口被稱為混合類型。這個概念《Java與模式》中有提及過:當一個具體類處于一個類的等級結構之中的時候,為這個具體類定義一個混合類型是可以保證基于這個類型的可插入性的關鍵。
=================================無敵分界線====================================
講了這么長,現在我們測試下改造后的訪問者模式
首先增加一種行為(狀態),即原訪問者模式的優點
增加一個具體訪問者Fail,修改一下客戶端測試代碼
public class Fail implements Visitor,ManVisitor,WomanVisitor{
public void visit(Man man) {
System.out.println("當男人失敗時,悶頭喝酒,誰也不用勸");
}
public void visit(Woman woman) {
System.out.println("當女人失敗時,眼淚汪汪,誰也勸不了");
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
//實例化具體元素
o.attach(new Man());
o.attach(new Woman());
//當成功時不同元素的不同反映
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
System.out.println();
//當戀愛時的不同反映
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
System.out.println();
//新增加失敗時的不同反映
Visitor fail = new Fail();
o.display(fail);
}
}
結果顯示:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
當男人失敗時,悶頭喝酒,誰也不用勸
當女人失敗時,眼淚汪汪,誰也勸不了
增加新的行為(狀態)與原來一樣方便!只需要增加一個具體訪問者即可!
現在我們來增加一個具體元素(正是寫這篇文章的初衷)
首先增加一個具體元素Bruce
public class Bruce implements Person{
public void accept(Visitor visitor) {
if(visitor instanceof BruceVisitor){
BruceVisitor bv = (BruceVisitor) visitor;
bv.visit(this);
}
//這個else可寫可不寫
else{
String s = visitor.getClass().getName();
String state = s.substring(s.lastIndexOf(".")+1,s.length());
System.out.println("噢..原來怪獸在"+state+"的時候是沒有行為的!!");
}
}
}
//按照新的思維方式增加一個對應的ElementVisitor接口
public interface BruceVisitor {
public void visit(Bruce bruce);
}
我們讓Success這個具體訪問者多實現一個BruceVisitor訪問者接口,和修改一下客戶端代碼進行測試
public class Success implements Visitor,WomanVisitor,ManVisitor,BruceVisitor{
public void visit(Man man) {
System.out.println("當男人成功時,背后多半有一個偉大的女人");
}
public void visit(Woman girl) {
System.out.println("當女人成功時,背后大多有一個不成功的男人");
}
public void visit(Bruce bruce) {
System.out.println("當怪獸成功時.........無語..........");
}
}
public class Client {
public static void main(String[] args) {
ObjectStructure o = new ObjectStructure(); //依賴于ObjectStructure
o.attach(new Man());
o.attach(new Woman());
o.attach(new Bruce()); //新增一種具體元素Bruce
Visitor success = new Success(); //依賴于抽象的Visitor接口
o.display(success);
System.out.println();
Visitor amativeness = new Love(); //依賴于抽象的Visitor接口
o.display(amativeness);
System.out.println();
Visitor fail = new Fail();
o.display(fail);
}
}
顯示結果:
當男人成功時,背后多半有一個偉大的女人
當女人成功時,背后大多有一個不成功的男人
當怪獸成功時.........無語..........
當男人戀愛時,凡事不懂也裝懂
當女人戀愛時,遇事懂也裝不懂
噢..原來怪獸在Love的時候是沒有行為的!!
當男人失敗時,悶頭喝酒,誰也不用勸
當女人失敗時,眼淚汪汪,誰也勸不了
噢..原來怪獸在Fail的時候是沒有行為的!!
這個結果你滿意嗎?
雖然,這只是部分符合“開放-封閉”原則,我們不需要修改Visitor接口,但還是得去修改Success實現新的接口。但是修改具體類比修改接口的代價小得多,不需要重新編譯所有訪問接口和具體訪問者。使我們面對新的變化也容易得多。而且這還有一個好處,就是可以讓各種元素有選擇地讓別人訪問,如上述例子,這樣使訪問者模式的運用起來更加靈活。