觀察者模式
定義了一對多的依賴關系,讓多個觀察者對象同時監聽某一個對象主題。這個主題對象在狀態變化時,會通知所有的觀察者對象,讓他們能夠自動更新自己。
【主要角色
】
- 抽象主題角色:把所有觀察者對象保存在一個集合里,每個主題都可以有任意數量的觀察者,抽象主題提供一個接口,可以添加和刪除觀察者對象
- 具體主題:該角色將有關狀態存入具體觀察者對象,在具體主題的內部狀態發生改變時,給所有注冊過的觀察者發送通知
- 抽象觀察者:是觀察者的抽象類,定義了一個更新接口,在主題更改時通知更新自己
- 具體觀察者:實現抽象觀察者定義的更新接口,以便在得到主題更改時通知更新自身的狀態
案例:微信公眾號
【需求
】:當這個微信公眾號的專欄發生變更后,所有訂閱了這個微信公眾的用戶都會收到通知。
抽象的觀察者:
public interface Observer {void update(String message);
}
訂閱公眾號的人(具體的觀察者):
@AllArgsConstructor
public class WeiXinUser implements Observer {private String name;@Overridepublic void update(String message) {System.out.println(name + "-" + message);}
}
抽象主題角色:
public interface Subject {// 添加訂閱者(觀察者對象)void attach(Observer observer);// 刪除訂閱者void detach(Observer observer);// 通知訂閱者更新消息void notify(String message);
}
公眾號類(具體主題類):
public class SubscriptionSubject implements Subject {// 定義一個集合,存儲多個觀察者對象private List<Observer> weixinUserList = new ArrayList<>();@Overridepublic void attach(Observer observer) {weixinUserList.add(observer);}@Overridepublic void detach(Observer observer) {weixinUserList.remove(observer);}@Overridepublic void notify(String message) {// 通知所有的觀察者對象去調用觀察者對象的update()方法weixinUserList.forEach(observer -> observer.update(message));}
}
測試類:
public class Client {public static void main(String[] args) {// 1. 創建公眾號對象SubscriptionSubject subject = new SubscriptionSubject();// 2. 訂閱公眾號subject.attach(new WeiXinUser("孫悟空"));subject.attach(new WeiXinUser("豬八戒"));subject.attach(new WeiXinUser("沙僧"));// 3. 公眾號更新,發消息給訂閱者(觀察者對象)subject.notify("專欄更新了");}
}
適用場景
- 對象間存在一對多的狀態,一個對象的狀態改變會影響其他對象
- 一個抽象模型有兩個方面,其中一個方面依賴于另一個方面
JDK中提供的實現
通過Observable類和Observer接口定義了觀察者模式,只要實現他們的子類就可以編寫觀察者模式實例。
Observable類:抽象主題類
void addObserver(Observer var1)
方法:將新的觀察者對象添加到集合中void notifyObservers(Object var1)
方法:調用集合中所有觀察者對象的update()方法,通知他們數據發生變化。(越晚加入集合的越先通知,因為遍歷時從后往前的)void setChanged()
方法:用來設置一個boolean的內部標志,表明目標對象發生變化,當他為true時,notifyObservers()才會通知觀察者
Observer接口:抽象觀察者
監視目標對象的變化,當目標對象發生變化時,觀察者得到通知,并調用update()
方法,進行相應的工作。
案例:警察抓小偷
警察是觀察者對象,小偷是被觀察者對象
小偷(被觀察者,需要繼承Observable類):
@Data
@AllArgsConstructor
public class Thief extends Observable {private String name;public void steal() {System.out.println("小偷:我偷東西了");super.setChanged(); // changed = truesuper.notifyObservers(); // 只有把changed改成true,這個方法才會被執行}
}
警察(觀察者類,需要繼承Observer接口):
@Data
@AllArgsConstructor
public class Policeman implements Observer {private String name;@Overridepublic void update(Observable o, Object arg) {System.out.println("警察:" + ((Thief)o).getName() + "抓到你了");}
}
測試類:
public class Client {public static void main(String[] args) {// 創建小偷對象Thief t = new Thief("老王");// 創建警察對象Policeman p = new Policeman("小林");// 讓警察盯著小偷t.addObserver(p); // 訂閱主題(把警察添加到小偷的集合中,小偷偷東西就會通知警察)// 小偷偷東西t.steal();}
}
中介者模式
同事之間的關系是錯綜復雜的(網狀結構)
但是如果引入中介者,就是呈現一種星形結構,所有的對象只需要和中介者有關聯即可。
中介者模式:定義一個中介角色來封裝一系列對象之間的交互,使原有對象之間的耦合沖散,且可以獨立改變他們之間的交互
【主要角色
】
- 抽象中介者角色:中介者的接口(提供同時對象注冊與轉發同時對象信息的抽象方法)
- 具體中介者角色:實現中介者接口,定義一個List來管理同事對象,協調各個同時角色時間的交互關系
- 抽象同事類角色:定義同事類的接口,保存中介者對象,提供同時對象交互的抽象方法,實現所有相互影響的同事類的公共功能
- 具體同事類角色:是抽象同事類的實現者,當需要與其他同事交互時,由中介者對象負責后續的交互
案例:租房
房屋中介(抽象中介者類):
public abstract class Mediator {public abstract void constact(String message, Person person);
}
抽象 租客 or 房主類:
@AllArgsConstructor
public abstract class Person {// 租房者 or 房主的姓名protected String name;// 中介protected Mediator mediator;// 和中介聯系abstract void constact(String message);// 獲取信息abstract void getMessage(String message);
}
具體的租客類:
public class Tenant extends Person {public Tenant(String name, Mediator mediator) {super(name, mediator);}@Overridepublic void constact(String message) {mediator.constact(message, this);}@Overridepublic void getMessage(String message) {System.out.println("租房者" + name + "獲取到的信息:" + message);}
}
具體的房主類:
public class HouseOwner extends Person {public HouseOwner(String name, Mediator mediator) {super(name, mediator);}@Overridevoid constact(String message) {mediator.constact(message, this);}@Overridevoid getMessage(String message) {System.out.println("房主" + name + "獲取到的信息:" + message);}
}
具體的中介者角色:
@Data
public class MediatorStructure extends Mediator {// 房主private HouseOwner houseOwner;// 租房者對象private Tenant tenant;@Overridepublic void constact(String message, Person person) {if(person == houseOwner) { // 房主tenant.getMessage(message);}else{ // 租房者houseOwner.getMessage(message);}}
}
測試類:
public class Client {public static void main(String[] args) {// 1. 創建中介者對象MediatorStructure mediator = new MediatorStructure();// 2. 創建租房者對象Tenant tenant = new Tenant("李四", mediator);// 3. 創建房主對象HouseOwner houseOwner = new HouseOwner("張三", mediator);// 4.中介者知道房主和租房者對象mediator.setTenant(tenant);mediator.setHouseOwner(houseOwner);// 5. 租房者和中介溝通tenant.constact("我要租房"); // 中介者會把這個信息發給房主// 6. 房主回應消息houseOwner.constact("我有房子");}
}
適用場景
- 系統中對象之間存在復雜的引用關系
- 想創建一個運行于多個類之間的對象(中介者角色),又不想生成新的子類
迭代器模式
提供一個對象來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
【主要角色
】
- 抽象聚合角色:定義存儲、添加、刪除聚合元素以及創建迭代器對象的接口
- 具體聚合角色:實現抽象聚合角色,返回具體迭代器實例
- 抽象迭代器角色:定義訪問和遍歷聚合元素的接口
- 具體迭代器角色:定義抽象迭代器接口中所定義的方法,完成對聚合對象的遍歷,記錄遍歷的當前位置
案例:學生容器
學生類:
@Data
@AllArgsConstructor
public class Student {private String name; // 姓名private String number; // 學號
}
抽象迭代器角色接口:
public interface StudentIterator {// 判斷是否還有元素boolean hasNext();// 獲取下一個元素Student next();
}
具體迭代器角色類:
public class StudentIteratorImpl implements StudentIterator {private List<Student> list;private int position = 0; // 用來記錄遍歷時的位置public StudentIteratorImpl(List<Student> list) {this.list = list;}@Overridepublic boolean hasNext() {return position < list.size();}@Overridepublic Student next() {return list.get(position++);}
}
抽象聚合角色類:
public interface StudentAggregate {// 添加學生void addStudent(Student student);// 刪除學生void removeStudent(Student student);// 獲取迭代器對象StudentIterator getStudentIterator();
}
具體聚合角色類:
public class StudentAggregateImpl implements StudentAggregate {private List<Student> list = new ArrayList<>();@Overridepublic void addStudent(Student student) {list.add(student);}@Overridepublic void removeStudent(Student student) {list.remove(student);}@Overridepublic StudentIterator getStudentIterator() {return new StudentIteratorImpl(list);}
}
測試類:
public class Client {public static void main(String[] args) {// 1. 創建聚合對象StudentAggregateImpl aggregate = new StudentAggregateImpl();// 2.添加元素aggregate.addStudent(new Student("小林", "001"));aggregate.addStudent(new Student("xiaolin", "002"));aggregate.addStudent(new Student("03", "003"));// 3. 遍歷聚合對象StudentIterator iterator = aggregate.getStudentIterator(); // 獲取迭代器對象while(iterator.hasNext()) { // 判斷是否有元素System.out.println(iterator.next()); // 輸出}}
}
適用場景
- 需要為聚合對象提供多種遍歷方式
- 需要為遍歷不同的聚合結構提供一個統一的接口
- 當訪問一個聚合對象的內容而無需暴露內部細節的表示
JDK源碼解析:Iterator類
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while(itertaor.hasNext()) {System.out.println(iterator.next());
}
在開發中,如果想使用迭代器模式,只需要讓我們自己實現的類實現
java.util.Iterable
,并實現其中的iterator()方法,使其返回一個java.util.Iterable
的實現類就可以了
訪問者模式
封裝一些作用于某種數據結構中的個元素的操作,可以在不改變數據結構的前提下,定義作用于這些元素的新操作。
【主要角色
】:
- 抽象訪問者角色:定義了對每一個元素的訪問行為(訪問者模式要求元素的個數不能改變)
- 具體訪問者角色:給出對每個元素類訪問時所產生的具體行為
- 抽象元素角色:定義了一個接受訪問者的方法(accept),每一個元素都要可以被訪問者訪問
- 具體元素角色:提供接收訪問角色的具體方法
- 對象結構角色:定義了對象結構,會包含一組元素,可以迭代這些元素,供訪問者使用
案例:給寵物喂食
【需求
】:要給寵物(狗、貓)喂食的時候,主人可以喂,其他人也可以喂
- 訪問者角色:給寵物喂食的人
- 具體訪問者角色:主人、其他人
- 抽象元素角色:動物抽象類
- 具體元素角色:寵物狗、寵物貓
- 結構對象角色:主人家
人(訪問者角色類):
public interface Person {// 給寵物貓喂食void feed(Cat cat);// 給寵物貓喂食void feed(Dog dog);
}
主人、其他人(具體訪問者角色類):
public class Owner implements Person {@Overridepublic void feed(Cat cat) {System.out.println("主人喂食貓");}@Overridepublic void feed(Dog dog) {System.out.println("主人喂食狗");}
}public class Someone implements Person {@Overridepublic void feed(Cat cat) {System.out.println("其他人喂食貓");}@Overridepublic void feed(Dog dog) {System.out.println("其他人喂食狗");}
}
動物類(抽象元素角色類):
public interface Animal {// 接受訪問者訪問的功能void accept(Person person);
}
貓、狗類(具體元素角色類):
public class Cat implements Animal {@Overridepublic void accept(Person person) {person.feed(this);System.out.println("喵喵喵");}
}public class Dog implements Animal {@Overridepublic void accept(Person person) {person.feed(this);System.out.println("汪汪汪");}
}
家(對象結構類):
public class Home {// 存儲元素對象private List<Animal> nodeList = new ArrayList<>();// 添加元素public void add(Animal animal) {nodeList.add(animal);}//public void action(Person person) {// 遍歷集合,獲取每一個元素,讓訪問者訪問每一個元素nodeList.forEach(animal -> animal.accept(person));}
}
測試類:
public class Client {public static void main(String[] args) {// 創建Home對象Home home = new Home();home.add(new Dog());home.add(new Cat());// 創建主人對象Owner owner = new Owner();// 讓主人喂食所有的寵物home.action(owner);}
}
適用場景
對象結構相對穩定,操作算法經常變化
擴展:雙分派技術
分派技術
分派:變量被聲明的類型叫變量的靜態類型,根據對象的類型對方法進行的選擇就是分派。
- 動態分派:發生在運行十七,動態分派動態的置換某個方法(方法重寫)
class Animal {public void eat() {}
}
class Dog extends Animal {public void eat() {System.out.println("dog");}
}
class Cat extends Animal {public void eat() {System.out.println("cat");}
}
public class Demo01 {public static void main(String[] args) {Animal a = new Dog();a.eat();a = new Cat();a.eat();}
}
- 靜態分派:發生在編譯時期,分派根據靜態類型信息發生(方法重載)
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
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 Demo02 {public static void main(String[] args) {Animal a = new Animal();Animal d = new Dog();Animal c = new Cat();Execute e = new Execute();e.execute(a);// animale.execute(d);// animale.execute(c);// animal}
}
重載方法的分派是在編譯時期進行的,這個分派過程在編譯時期就完成了
雙分派技術
在選擇一個方法時,不僅僅要根據消息接收者的運行時區別,還要根據參數的運行時區別。
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");}
}
class Animal {public void accept(Execute e) {e.execute(this);}
}
class Dog extends Animal {public void accept(Execute e) {e.execute(this);}
}
class Cat extends Animal {public void accept(Execute e) {e.execute(this);}
}public class Demo03 {public static void main(String[] args) {Animal a = new Animal();Animal d = new Dog();Animal c = new Cat();Execute e = new Execute();a.accept(e); // animald.accept(e); // dogc.accept(e); // cat}
}
上邊代碼經歷了兩次分派
- 第一次分派(方法重寫、靜態分派):客戶端將Execute對象作為參數傳遞給Animal類型的變量調用方法
- 第二次分派(方法重載、動態分派):每個類將自己this作為參數傳遞,這里的Execute類中有多個重載的方法,而傳遞的this是具體的實際類型對象
雙分派實現動態綁定的本質
:在重載方法委派的前面加上了繼承體系中的覆蓋環節,因為覆蓋是動態的,所以重載就是動態的了。
備忘錄模式
又叫快照模式,在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,以便以后當需要時能將該對象恢復到原先保存的狀態。
【主要角色
】
- 發起人角色:記錄當前時刻的內部狀態信息,提供創建、恢復備忘錄的功能
- 備忘錄角色:負責存儲發起人的內部狀態,在需要的時候提供這些內部狀態給發起人
- 管理者角色:對備忘錄進行管理,提供保存、獲取備忘錄的功能,不能對備忘錄的內容進行訪問和修改。
案例:游戲挑戰boss
【需求
】:游戲角色有生命力、攻擊力、防御力等數據,在打boss前后都不一樣,我們允許玩家如果感覺和boss決斗的效果不理想可以讓游戲恢復到快斗之前的狀態
白箱備忘錄模式
備忘錄角色對任何對象都一個一個接口(寬接口),備忘錄角色內部所存儲的對象就對所有對象公開。
游戲角色類(發起人角色):
@Data
public class GameRole {private int vit; // 生命力private int atk; // 攻擊力private int def; // 防御力// 初始化內部狀態public void initState() {this.vit = 100;this.atk = 100;this.def = 100;}// 戰斗public void fight() {this.vit = 0;this.atk = 0;this.def = 0;}// 保存角色狀態功能public RoleStateMemento saveState() {return new RoleStateMemento(vit, atk, def);}// 恢復角色狀態(將備忘錄對象中存儲的狀態賦值給當前成員)public void recoverState(RoleStateMemento memento) {this.vit = memento.getVit();this.atk = memento.getAtk();this.def = memento.getDef();}// 展示狀態public void stateDisplay() {System.out.println("角色生命力" + vit);System.out.println("角色攻擊力" + atk);System.out.println("角色防御力" + def);}}
備忘錄角色類:
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RoleStateMemento {private int vit; // 生命力private int atk; // 攻擊力private int def; // 防御力
}
備忘錄管理對象:
@Data
public class RoleStateCaretaker {// 備忘錄private RoleStateMemento roleStateMemento;
}
測試類:
public class Client {public static void main(String[] args) {// 創建游戲角色對象GameRole gameRole = new GameRole();gameRole.initState(); // 初始數據gameRole.stateDisplay();// 備份內部狀態RoleStateCaretaker caretaker = new RoleStateCaretaker();caretaker.setRoleStateMemento(gameRole.saveState());gameRole.fight(); // 戰斗gameRole.stateDisplay();// 恢復備忘錄gameRole.recoverState(caretaker.getRoleStateMemento());gameRole.stateDisplay();}
}
上邊之所以是白箱備忘錄模式,是因為管理者、客戶端其實都可以拿到備忘錄對象,進而對備忘錄對象里的內容進行操作。
黑箱備忘錄模式
雙重接口:備忘錄角色對發起人對象提供寬接口,對其他對象提供窄接口(將備忘錄類設置成發起人類的成員內部類)
備忘錄接口(對外提供窄接口):
public interface Memento {
}
備忘錄管理對象:
@Data
public class RoleStateCaretaker {// 備忘錄private Memento memento; // 面向接口(返回的是接口,所以在外界是訪問不到里邊的成員的)
}
游戲角色類(發起人角色):
@Data
public class GameRole {private int vit; // 生命力private int atk; // 攻擊力private int def; // 防御力// 初始化內部狀態public void initState() {this.vit = 100;this.atk = 100;this.def = 100;}// 戰斗public void fight() {this.vit = 0;this.atk = 0;this.def = 0;}// 保存角色狀態功能public RoleStateMemento saveState() {return new RoleStateMemento(vit, atk, def);}// 恢復角色狀態(將備忘錄對象中存儲的狀態賦值給當前成員)public void recoverState(Memento memento) {RoleStateMemento roleStateMemento = (RoleStateMemento) memento;this.vit = roleStateMemento.vit;this.atk = roleStateMemento.atk;this.def = roleStateMemento.def;}// 展示狀態public void stateDisplay() {System.out.println("角色生命力" + vit);System.out.println("角色攻擊力" + atk);System.out.println("角色防御力" + def);}// 成員內部類,必須實現Memento接口// 把備忘錄角色類定義在了發起者角色類中,并將它私有@Data@AllArgsConstructor@NoArgsConstructorclass RoleStateMemento implements Memento {private int vit; // 生命力private int atk; // 攻擊力private int def; // 防御力}
}
測試類:
public class Client {public static void main(String[] args) {// 創建游戲角色對象GameRole gameRole = new GameRole();gameRole.initState(); // 初始數據gameRole.stateDisplay();// 備份內部狀態RoleStateCaretaker caretaker = new RoleStateCaretaker();caretaker.setMemento(gameRole.saveState());gameRole.fight(); // 戰斗gameRole.stateDisplay();// 恢復備忘錄gameRole.recoverState(caretaker.getMemento());gameRole.stateDisplay();}
}
把備忘錄角色類定義在了發起者角色類中,并將它私有,這樣外界就不能訪問了
適用場景
- 需要保存和恢復數據的場景(玩游戲時的中間結果存檔功能)
- 需要提供一個可回滾操作的場景
- word、記事本按ctrl + z
- 數據庫中事務操作
解釋器模式
定義一個語言,定義它的文法表示,并定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子。
【主要角色
】
- 抽象表達式角色:約定解釋器的解釋操作(主要包含解釋方法interpret())
- 終結符表達式角色:抽象表達式的子類,用來標識文法中與終結符相關的操作
- 非終結符表達式角色:抽象表達式的子類,用來表示文法中與非終結符相關的操作
- 環境角色:包含各個解釋器需要的數據或公共的功能
- 客戶端:將需要分析的句子或表達式轉換成使用解釋器對象描述的抽象語法樹
案例:實現加減法的軟件
抽象表達式類:
public abstract class AbstractExpression {public abstract int interpret(Context context);
}
環境角色類:
public class Context {// 定義map集合,存儲變量及對應的值private Map<Variable, Integer> map = new HashMap<>();// 添加變量public void assign(Variable var, Integer value) {map.put(var, value);}// 根據變量獲取對應值public int getValue(Variable var) {return map.get(var);}
}
封裝變量的類:
@Data
@AllArgsConstructor
public class Variable extends AbstractExpression {// 聲明存儲【變量名】的成員變量private String name;@Overridepublic int interpret(Context context) {// 返回變量的值return context.getValue(this);}public String toString() {return name;}
}
減法、加法表達式類
@AllArgsConstructor
@Data
public class Minus extends AbstractExpression {// 加號左邊的表達式private AbstractExpression left;// 加號右邊的表達式private AbstractExpression right;@Overridepublic int interpret(Context context) {// 將左邊表達式的結果和右邊表達式的結果相減return left.interpret(context) - right.interpret(context);}public String toString() {return "(" + left.toString() + " - " + right.toString() + ")";}
}@AllArgsConstructor
@Data
public class Plus extends AbstractExpression {// 加號左邊的表達式private AbstractExpression left;// 加號右邊的表達式private AbstractExpression right;@Overridepublic int interpret(Context context) {// 將左邊表達式的結果和右邊表達式的結果相加return left.interpret(context) + right.interpret(context);}public String toString() {return "(" + left.toString() + " + " + right.toString() + ")";}
}
測試類:
public class Client {public static void main(String[] args) {// 創建環境對象Context context = new Context();// 創建多個變量對象Variable a = new Variable("a");Variable b = new Variable("b");Variable c = new Variable("c");Variable d = new Variable("d");// 將變量存儲到環境中context.assign(a, 1);context.assign(b, 2);context.assign(c, 3);context.assign(d, 4);// 構建抽象語法樹AbstractExpression expression = new Minus(a, new Plus(new Minus(b, c), d));// 解釋(計算)int result = expression.interpret(context);System.out.println(expression + " = " + result);}
}
每條規則都需要定義一個類
適用場景
- 問題重復出現,可以用一種簡單的語言來進行表達
- 一個語言需要解釋執行,并且語言中的句子可以表示為一個抽象的語法樹