觀察者模式在MVP中的應用
一、要實現的效果
首先簡單介紹一下我在專案中為什麼需要使用觀察者模式加MVP模式
1、為什麼使用MVP模式:下圖是我所開發專案的一個主要功能介面(騎行介面),從圖中我們可以看到這個介面有很多控制元件,比如溫度、電量、當前速度、藍芽、鎖車、里程···在我重構之前所有的程式碼都放在了一個Activity裡面,UI更新與邏輯實現全部混合在一起,有2300多行,每次需要改動時都很麻煩,查詢很不方便。所以重構時決定採用MVP模式,將UI更新與邏輯實現進行解耦。
2、為什麼使用觀察者模式:使用MVP模式之後很好地實現了更新UI與邏輯實現的解耦,本來已經可以了。但是後來又增加了一個新需求:增加一個簡便操作介面,簡便操作介面如下圖所示:
可以看到簡便操作介面就是在前一個介面的基礎上,UI風格完全改變,功能相對於前一個介面有所減少,但是也增加了一些新功能。簡便操作介面跟騎行介面功能上很多都是重合的,因此很明顯我們需要重用邏輯程式碼,因為我們使用了mvp模式,所以我們的UI跟邏輯本身已經實現了分離,現在關鍵是當邏輯處理完之後,我們需要更新UI,那麼用哪種方式更新UI好呢,因為有2個介面的UI需要更新。這裡我使用了觀察者模式,為什麼使用觀察者模式?從觀察者模式的定義你大概可以看出使用它的原因了:++“有時被稱作釋出/訂閱模式,觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。”
一個邏輯處理完了之後,比如獲取溫度,我們需要更新兩個UI,這是一種一對多的關係,因此適合使用觀察者模式。
二、MVP模式簡要介紹
MVP把Activity中的UI邏輯抽象成View介面,把業務邏輯抽象成Presenter介面,Model類還是原來的Model。
MVP模式的作用 :
1、分離了檢視邏輯和業務邏輯,降低了耦合
2、Activity只處理生命週期的任務,程式碼變得更加簡潔
3、檢視邏輯和業務邏輯分別抽象到了View和Presenter的介面中去,提高程式碼的可閱讀性
4、Presenter被抽象成介面,可以有多種具體的實現,所以方便進行單元測試
5、把業務邏輯抽到Presenter中去,避免後臺執行緒引用著Activity導致Activity的資源無法被系統回收從而引起記憶體洩露和OOM
6、UI介面中定義了更新UI的方法,presenter介面中定義了業務邏輯方法,我們從這兩個介面檔案中就可以一目瞭然地看到我們有哪些邏輯操作,有哪些UI更新方法,功能一目瞭然。
三、觀察者模式簡要介紹:
- 概述:
有時被稱作釋出/訂閱模式,觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。 - 解決的問題:
將一個系統分割成一個一些類相互協作的類有一個不好的副作用,那就是需要維護相關物件間的一致性。我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護、擴充套件和重用都帶來不便。觀察者就是解決這類的耦合關係的。 - 模式中的角色:
3.1抽象主題(Subject):它把所有觀察者物件的引用儲存到一個聚集裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件。
3.2具體主題(ConcreteSubject):將有關狀態存入具體觀察者物件;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。
3.3 抽象觀察者(Observer):為所有的具體觀察者定義一個介面,在得到主題通知時更新自己。
3.4具體觀察者(ConcreteObserver):實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題狀態協調。 - 模式解讀:
4.1 觀察者模式的類圖
四、結合程式碼講解觀察者模式在MVP模式中的應用
為方便起見,我新建一個Java工程來模擬:ObseverMvp
專案檔案結構如下:
==com.zha==
Client.java[類]
==com.zha.logic==
CommLogicPresenter.java[抽象類]
ConcreteCommLogic.java[類]
IRidePresenter.java[介面]
ISimpleOperatorPresenter.java[介面]
==com.zha.ui==
CommUIView.java[抽象類]
IRideView.java[介面]
ISimpleOperatorView.java[介面]
RideActivity.java[類]
SimpleOperatorActivity.java[類]
該專案的類圖如下:
MVP:
從上面的目錄結構我們可以很清楚地可以看到三部分,Client.java部分、logic包下面的部分、ui包下面的部分,很好地對ui跟邏輯進行了分離。
logic包下以Presenter結尾的三個檔案是業務邏輯的抽象[MVP中的P]。為什麼分成三個檔案?首先CommLogicPresenter.java類中定義了兩個介面共有的業務邏輯,可以很好地實現邏輯複用;IRidePresenter.java類定義了騎行頁面有但是簡便操作介面沒有的業務邏輯;ISimpleOperatorPresenter.java類定義了簡便操作介面有但是騎行頁面沒有的業務邏輯。從這三個類中我們可以很清楚的看到它們有哪些共同的業務邏輯,騎行介面有哪些業務邏輯,簡便操作介面有哪些業務邏輯,功能一目瞭然。
ui包下以View結尾的三個檔案是檢視邏輯的抽象[MVP中的V]。為什麼分成三個檔案?首先CommUIView.java類中定義了兩個介面共有的檢視邏輯,好處是我們呼叫的時候可以使用同一段程式碼,但是不同的介面又可以對同一個介面有不同的實現,例如更新溫度,看下面程式碼:
@Override
public void noticyRefreshTemperature() {
System.out.println("事件---獲取溫度");
for (CommUIView commUIView : commUIViews) {
commUIView.refreshUI_temperature();
}
}
IRideView.java定義了騎行介面有但是簡便操作介面沒有的檢視邏輯,ISimpleOperatorView.java定義了簡便操作介面有但是騎行介面沒有的檢視邏輯。
觀察者模式
閱讀上面我們知道觀察者模式有四個角色,抽象主題、具體主題、抽象觀察者、具體觀察者。先來看抽象主題,CommLogicPresenter.java,它裡面定義了新增觀察者和刪除觀察者的介面:
/**
* 新增觀察者
* @param commUIView
*/
public abstract void attach(CommUIView commUIView);
/**
* 移除觀察者
* @param commUIView
*/
public abstract void detach(CommUIView commUIView);
然後再來看具體主題ConcreteCommLogic.java,1、首先它實現了抽象主題,2、然後它的另一個作用是當業務邏輯有改變時,通知所有的UI進行檢視更新,還是上面一段程式碼:
@Override
public void noticyRefreshTemperature() {
System.out.println("事件---獲取溫度");
for (CommUIView commUIView : commUIViews) {
commUIView.refreshUI_temperature();
}
}
從上面程式碼可以看到當我們獲取到溫度後,我們需要迴圈呼叫commUIView.refreshUI_temperature();方法來通知所有登記過(新增)的觀察者來更新檢視。
接著是抽象觀察者,CommUIView.java,它為所有的具體觀察者定義一個介面,在得到主題通知時更新自己。
最後是具體觀察者,RideActivity.java和SimpleOperatorActivity.java,它們實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題狀態協調,他們對同一個主題的通知可以有不同的實現方式。
下面我貼出所有的原始碼:
CommLogicPresenter它既是一個抽象的主題(抽象角色),1、他定義了新增和刪除觀察者的抽象方法;2、同時它又是一個抽象的業務邏輯(Presenter)。他定義了兩個介面共有的業務邏輯方法。
package com.zha.logic;
import com.zha.ui.CommUIView;
public abstract class CommLogicPresenter {
/**
* 新增觀察者
* @param commUIView
*/
public abstract void attach(CommUIView commUIView);
/**
* 移除觀察者
* @param commUIView
*/
public abstract void detach(CommUIView commUIView);
/**
* 重新整理溫度
*/
public abstract void noticyRefreshTemperature();
/**
* 啟動定時器
*/
public abstract void startTimer();
/**
* 停止定時器
*/
public abstract void stopTimer();
}
ConcreteCommLogic 它是一個具體的觀察者,也實現了具體的業務邏輯
package com.zha.logic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.zha.ui.CommUIView;
import com.zha.ui.IRideView;
import com.zha.ui.ISimpleOperatorView;
public class ConcreteCommLogic extends CommLogicPresenter implements IRidePresenter,ISimpleOperatorPresenter{
/**
* 觀察者列表
*/
private List<CommUIView> commUIViews= new ArrayList<CommUIView>();
private ScheduledExecutorService service = Executors
.newSingleThreadScheduledExecutor();
private Runnable runnable = new Runnable() {
public void run() {
noticyRefreshTemperature();
}
};
@Override
public void attach(CommUIView commUIView) {
commUIViews.add(commUIView);
}
@Override
public void detach(CommUIView commUIView) {
commUIViews.remove(commUIView);
}
@Override
public void noticyRefreshTemperature() {
System.out.println("事件---獲取溫度");
for (CommUIView commUIView : commUIViews) {
commUIView.refreshUI_temperature();
}
}
@Override
public void clickLock() {
System.out.println("事件---鎖定");
for (CommUIView commUI : commUIViews) {
if (commUI instanceof IRideView) {
((IRideView)commUI).refreshUI_lockStatus();
}
}
}
@Override
public void clickChangeAssistantMode() {
System.out.println("事件---切換助力模式");
for (CommUIView commUI : commUIViews) {
if (commUI instanceof ISimpleOperatorView) {
((ISimpleOperatorView)commUI).changeAssistantMode();
}
}
}
@Override
public void startTimer() {
System.out.println("事件---啟動定時器");
// 第二個引數為首次執行的延時時間,第三個引數為定時執行的間隔時間
service.scheduleAtFixedRate(runnable, 10, 8, TimeUnit.SECONDS);
}
@Override
public void stopTimer() {
}
}
IRidePresenter定義了騎行介面獨有的業務邏輯
package com.zha.logic;
public interface IRidePresenter {
/**
* 鎖定車輛
*/
void clickLock();
}
ISimpleOperatorPresenter定義了簡便操作介面獨有的邏輯
package com.zha.logic;
public interface ISimpleOperatorPresenter {
/**
* 切換助力模式
*/
void clickChangeAssistantMode();
}
CommUIView它既是一個抽象的主題;又是一個抽象的檢視邏輯(View)。他定義了介面所需要的檢視邏輯方法。
package com.zha.ui;
public abstract class CommUIView {
/**
* 更新溫度
*/
public abstract void refreshUI_temperature();
}
IRideView定義了騎行介面獨有的檢視邏輯
package com.zha.ui;
public interface IRideView {
/**
* 更新鎖定狀態
*/
void refreshUI_lockStatus();
}
定義了簡便操作介面獨有的檢視邏輯
package com.zha.ui;
public interface ISimpleOperatorView {
/**
* 切換助力模式
*/
void changeAssistantMode();
}
RideActivity它是具體的觀察者,同時也是具體的檢視邏輯
package com.zha.ui;
public class RideActivity extends CommUIView implements IRideView{
@Override
public void refreshUI_temperature() {
System.out.println("更新UI---騎行*更新溫度---");
}
@Override
public void refreshUI_lockStatus() {
System.out.println("更新UI---騎行*更新鎖定---");
}
}
SimpleOperatorActivity它是具體的觀察者,同時也是具體的檢視邏輯
package com.zha.ui;
public class SimpleOperatorActivity extends CommUIView implements ISimpleOperatorView{
@Override
public void refreshUI_temperature() {
System.out.println("更新UI---簡便操作*更新溫度---");
}
@Override
public void changeAssistantMode() {
System.out.println("更新UI---簡便操作*切換助力模式---");
}
}
最後是Client
package com.zha;
import com.zha.logic.ConcreteCommLogic;
import com.zha.ui.RideActivity;
import com.zha.ui.SimpleOperatorActivity;
public class Client {
public static void main(String[] args) {
ConcreteCommLogic concreteCommLogic = new ConcreteCommLogic();
RideActivity rideFragment = new RideActivity();
SimpleOperatorActivity simpleOperatorActivity = new SimpleOperatorActivity();
concreteCommLogic.attach(rideFragment);
concreteCommLogic.attach(simpleOperatorActivity);
concreteCommLogic.noticyRefreshTemperature();
concreteCommLogic.clickLock();
concreteCommLogic.clickChangeAssistantMode();
concreteCommLogic.startTimer();
}
}
執行結果:
事件—獲取溫度
更新UI—騎行_更新溫度
更新UI—簡便操作_更新溫度
事件—鎖定
更新UI—騎行_更新鎖定
事件—切換助力模式
更新UI—簡便操作_切換助力模式
事件—啟動定時器
事件—獲取溫度
更新UI—騎行_更新溫度
更新UI—簡便操作_更新溫度
事件—獲取溫度
更新UI—騎行_更新溫度
更新UI—簡便操作_更新溫度
原始碼下載地址:原始碼下載地址