一、要實現的效果
首先簡單介紹一下我在項目中爲什麼需要使用觀察者模式加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—簡便操作_更新溫度
源碼下載地址:源碼下載地址