Java之觀察者模式(Observer)

Java 設計 ramostear 2019-07-12

1. 設計意圖

定義對象之間的一對多依賴關係,以便當一個對象更改狀態時,將自動通知和更新其所有依賴項。

Java之觀察者模式(Observer)

簡而言之

你別來找我,給我你的聯繫方式,有事我會主動聯繫你

2.案例演示

以當前最火熱的吃雞遊戲作為一個簡單的案例來演示觀察者模式,當玩家進入遊戲時,會收到遊戲服務器推送的提示消息,隨著遊戲的進行,如果某個玩家被Kill掉了,遊戲服務器會把此消息推送給房間裡的其他玩家。在本案例中,“遊戲” 是一個抽象的被觀察者,“吃雞遊戲” 是具體的被觀察者;“遊戲玩家”是一個抽象的觀察者(接口),而玩家A、玩家B等是具體的觀察者。案例的UML關係如下圖:

Java之觀察者模式(Observer)

3. 示例代碼

3.1 抽象的被觀察者類(Subject)

AbstractGame.java

package com.ramostear.pattern.observer;
import java.util.ArrayList;
/**
* @author ramostear
* @create-time 2019/1/5 0005-23:27
* @modify by :
* @info:[抽象的被觀測者類]
* @since:
*/
public abstract class AbstractGame {
/**
* 定義一個存放觀察者的容器
*/
public final ArrayList<Observer> obsList = new ArrayList<>();
/**
* 註冊觀察者
* @param obs 觀察者
* @param <T>
*/
public <T> void attach(Observer obs){
if (obs == null){
throw new NullPointerException("Observer is null.");
}else{
this.attachObs(obs);
}
}
/**
* 註冊觀察者
* @param obs
*/
private void attachObs(Observer obs){
if (obs == null){
throw new NullPointerException("class is null");
}else {
synchronized (obsList){
if(!obsList.contains(obs)){
obsList.add(obs);
}
}
}
}
/**
* 註銷觀察者
* @param obs 觀察者
* @param <T>
*/
public <T> void detach(Observer obs){
if(obs == null){
throw new NullPointerException("Observer is null");
}else {
this.detachObs(obs);
}
}
/**
* 註銷觀察者
* @param obs
*/
private void detachObs(Observer obs){
if(obs == null){
throw new NullPointerException("Class is null");
}else{
synchronized (obsList){
obsList.remove(obs);
}
}
}
/**
* 通知所有的觀察者
* @param messages
*/
public abstract void notifyAllObs(String...messages);
/**
* 通知某個觀察者
* @param obs
* @param messages
*/
public abstract void notifyObs(Observer obs,String...messages);
}

AbstractGame類中定義了添加、刪除和通知觀察者的方法,同時有一個List類型的容器,用於保存已註冊的觀察者,當需要通知觀察者時,從容器中取出觀察者信息。

說明:抽象的被觀察者可以定義成一個抽象類或者接口,本案例中採用的是抽象類

3.2 抽象的觀察者接口(Observer)

Observer.java

package com.ramostear.pattern.observer;
/**
* @author ramostear
* @create-time 2019/1/5 0005-23:26
* @modify by :
* @info:[觀察者接口]
* @since:
*/
public interface Observer {
/**
* 更新狀態
* @param messages
*/
void update(String... messages);
}

在該接口中定義了一個update() 方法,當被觀察者發出通知時,此方法會被調用。

3.3 具體被觀察者(ConcreteSubject)

ChikenGame繼承了AbstractGame類,並對通知方法進行了具體的實現。

ChikenGame.java

package com.ramostear.pattern.observer;
/**
* @author ramostear
* @create-time 2019/1/5 0005-23:55
* @modify by :
* @info:[吃雞遊戲類]
* @since:
*/
public class ChickenGame extends AbstractGame {
private String roomName;
public ChickenGame(String roomName) {
this.roomName = roomName;
}
public String getRoomName() {
return roomName;
}
public void setRoomName(String roomName) {
this.roomName = roomName;
}
@Override
public void notifyAllObs(String... messages) {
obsList.forEach(obs->{
this.notifyObs(obs,messages);
});
}
@Override
public void notifyObs(Observer obs, String... messages) {
if (obs == null){
throw new NullPointerException("Observer is null");
}else{
obs.update(messages);
}
}
}

3.4 具體觀察者(ConcreteObserver)

Gamer類實現了Observer接口,並對Observer的update方法進行了具體的實現;這裡為了演示,只是簡單的對消息進行輸出。

Gamer.java

package com.ramostear.pattern.observer;
/**
* @author ramostear
* @create-time 2019/1/6 0006-0:06
* @modify by :
* @info:[遊戲玩家]
* @since:
*/
public class Gamer implements Observer{
private String name;
public Gamer(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(String... messages) {
System.out.println("玩家:"+name);
for (String message:messages){
System.out.println("消息->"+message+"\n");
}
}
}

3.5 測試本次案例

創建一個吃雞遊戲叫“三國吃雞演義” ,將劉、關、張三個玩家註冊到吃雞遊戲中。遊戲發佈消息給三個玩家,劉、關、張同時收到遊戲發出的消息,當關羽掛掉後,只有劉、張兩個玩家收到消息;當張飛再掛掉後,只有劉備收到消息。為了演示觀察者模式,最後我們讓關羽滿血復活,此時劉、關二人收到遊戲發出的消息。

App.java

package com.ramostear.pattern.observer;
/**
* @author ramostear
* @create-time 2019/1/6 0006-0:08
* @modify by :
* @info:[測試類]
* @since:
*/
public class App {
public static void main(String[] args){
ChickenGame game = new ChickenGame("三國吃雞演義");
Gamer gamerLiu = new Gamer("劉備");
Gamer gamerZhang = new Gamer("張飛");
Gamer gamerGuan = new Gamer("關羽");
game.attach(gamerLiu);
game.attach(gamerGuan);
game.attach(gamerZhang);
game.notifyAllObs("歡迎進入"+game.getRoomName());
game.notifyAllObs(new String[]{"劉關張桃園三結義,開始三國吃雞演義..."});
game.detach(gamerGuan);
game.notifyAllObs("#關羽:"我去!被98K爆了,快來扶我一下!"");
game.notifyAllObs("#劉備:"我去,這貨肥得一批!"");
game.detach(gamerZhang);
game.notifyAllObs("#張飛:"我去,這比是掛!"");
game.notifyAllObs("#劉備:"我去!咋這麼多人,我涼了!"");
game.attach(gamerGuan);
game.notifyAllObs("關羽滿血復活");
game.notifyAllObs("#劉備:"苟住,苟住就能贏!"");
}
}

測試結果:

Java之觀察者模式(Observer)

4. 適用性

當滿足以下情況中的一種時使用觀察者模式

  • 當抽象有兩個Aspect時,一個依賴於另一個。 將這些Aspact封裝在單獨的對象中可讓您獨立地改變和重用它們.
  • 當一個對象的更改需要更改其他對象時,你不知道到底需要更改多少個關聯的對象
  • 當不希望多個對象之前發生緊耦合時

5. 真實案例

  • java.util.Observer
  • java.util.EventListener
  • javax.servlet.http.HttpSessionBindingListener

相關推薦

推薦中...