定義:
封裝一些作用於某種資料結構中的各元素的操作,它可以在不改變這個資料結構的前提下定義作用於這些元素的新的操作。
存取者模式包含以下主要角色:
抽象存取者(Visitor)角色:定義了對每一個元素(Element)
存取的行為,它的引數就是可以存取的元素,它的方法個數理論上來講與元素類個數(Element的實現類個數)是一樣的,從這點不難看出,存取者模式要求元素類的個數不能改變。
具體存取者(ConcreteVisitor)角色:給出對每一個元素類存取時所產生的具體行為。
抽象元素(Element)角色:定義了一個接受存取者的方法(accept
),其意義是指,每一個元素都要可以被存取者存取。
具體元素(ConcreteElement)角色: 提供接受存取方法的具體實現,而這個具體的實現,通常情況下是使用存取者提供的存取該元素類的方法。
物件結構(Object Structure)角色:定義當中所提到的物件結構,物件結構是一個抽象表述,具體點可以理解為一個具有容器性質或者複合物件特性的類,它會含有一組元素(Element
),並且可以迭代這些元素,供存取者存取。
【例】給寵物餵食
現在養寵物的人特別多,我們就以這個為例,當然寵物還分為狗,貓等,要給寵物餵食的話,主人可以喂,其他人也可以餵食。
存取者角色:給寵物餵食的人
具體存取者角色:主人、其他人
抽象元素角色:動物抽象類
具體元素角色:寵物狗、寵物貓
結構物件角色:主人家
類圖如下:
程式碼如下:
建立抽象存取者介面
public interface Person {
void feed(Cat cat);
void feed(Dog dog);
}
建立不同的具體存取者角色(主人和其他人),都需要實現 Person
介面
public class Owner implements Person {
@Override
public void feed(Cat cat) {
System.out.println("主人餵食貓");
}
@Override
public void feed(Dog dog) {
System.out.println("主人餵食狗");
}
}
public class Someone implements Person {
@Override
public void feed(Cat cat) {
System.out.println("其他人餵食貓");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人餵食狗");
}
}
定義抽象節點 -- 寵物
public interface Animal {
void accept(Person person);
}
定義實現Animal
介面的 具體節點(元素)
public class Dog implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,汪汪汪!!!");
}
}
public class Cat implements Animal {
@Override
public void accept(Person person) {
person.feed(this);
System.out.println("好好吃,喵喵喵!!!");
}
}
定義物件結構,此案例中就是主人的家
public class Home {
private List<Animal> nodeList = new ArrayList<Animal>();
public void action(Person person) {
for (Animal node : nodeList) {
node.accept(person);
}
}
//新增操作
public void add(Animal animal) {
nodeList.add(animal);
}
}
測試類
public class Client {
public static void main(String[] args) {
Home home = new Home();
home.add(new Dog());
home.add(new Cat());
Owner owner = new Owner();
home.action(owner);
Someone someone = new Someone();
home.action(someone);
}
}
測試結果
1,優點:
擴充套件性好
在不修改物件結構中的元素的情況下,為物件結構中的元素新增新的功能。
複用性好
通過存取者來定義整個物件結構通用的功能,從而提高複用程度。
分離無關行為
通過存取者來分離無關的行為,把相關的行為封裝在一起,構成一個存取者,這樣每一個存取者的功能都比較單一。
2,缺點:
物件結構變化很困難
在存取者模式中,每增加一個新的元素類,都要在每一個具體存取者類中增加相應的具體操作,這違背了「開閉原則」。
違反了依賴倒置原則
存取者模式依賴了具體類,而沒有依賴抽象類。
物件結構相對穩定,但其操作演演算法經常變化的程式。
物件結構中的物件需要提供多種不同且不相關的操作,而且要避免讓這些操作的變化影響物件的結構。
存取者模式用到了一種雙分派的技術。
1,分派:
變數被宣告時的型別叫做變數的靜態型別,有些人又把靜態型別叫做明顯型別;而變數所參照的物件的真實型別又叫做變數的實際型別。比如 Map map = new HashMap()
,map變數的靜態型別是 Map
,實際型別是 HashMap
。根據物件的型別而對方法進行的選擇,就是分派(Dispatch),分派(Dispatch)又分為兩種,即靜態分派和動態分派。
靜態分派(Static Dispatch) 發生在編譯時期,分派根據靜態型別資訊發生。靜態分派對於我們來說並不陌生,方法過載就是靜態分派。
動態分派(Dynamic Dispatch) 發生在執行時期,動態分派動態地置換掉某個方法。Java通過方法的重寫支援動態分派。
2,動態分派:
通過方法的重寫支援動態分派。
public class Animal {
public void execute() {
System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override
public void execute() {
System.out.println("dog");
}
}
public class Cat extends Animal {
@Override
public void execute() {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();
}
}
上面程式碼的結果大家應該直接可以說出來,這不就是多型嗎!執行執行的是子類中的方法。
Java編譯器在編譯時期並不總是知道哪些程式碼會被執行,因為編譯器僅僅知道物件的靜態型別,而不知道物件的真實型別;而方法的呼叫則是根據物件的真實型別,而不是靜態型別。
3,靜態分派:
通過方法過載支援靜態分派。
public class Animal {
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
執行結果:
這個結果可能出乎一些人的意料了,為什麼呢?
過載方法的分派是根據靜態型別進行的,這個分派過程在編譯時期就完成了。
4,雙分派:
所謂雙分派技術就是在選擇一個方法的時候,不僅僅要根據訊息接收者(receiver)的執行時區別,還要根據引數的執行時區別。
public class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
在上面程式碼中,使用者端將Execute物件做為引數傳遞給Animal型別的變數呼叫的方法,這裡完成第一次分派,這裡是方法重寫,所以是動態分派,也就是執行實際型別中的方法,同時也將自己this作為引數傳遞進去,這裡就完成了第二次分派
,這裡的Execute類中有多個過載的方法,而傳遞進行的是this,就是具體的實際型別的物件。
說到這裡,我們已經明白雙分派是怎麼回事了,但是它有什麼效果呢?就是可以實現方法的動態繫結,我們可以對上面的程式進行修改。
執行結果如下:
雙分派實現動態繫結的本質,就是在過載方法委派的前面加上了繼承體系中覆蓋的環節,由於覆蓋是動態的,所以過載就是動態的了。
本文來自部落格園,作者:|舊市拾荒|,轉載請註明原文連結:https://www.cnblogs.com/xiaoyh/p/16563335.html