[TOC]
行為型模式是對在不同的對象之間劃分責任和算法的抽象化,重點關注類(或者對象)之間的相互作用。
行為型模式分為類行為型模式和對象行為型模式兩種。包含
責任鏈模式、命令模式、解釋器模式、迭代器模式、中介者模式、備忘錄模式、觀察者模式、狀態模式、策略模式、模版方法模式及訪問者模式。
# 1 觀察者模式
觀察者模式又稱發布-訂閱模式、模型-視圖模式、源-監聽器模式、從屬者模式
## 1.1 模式定義
定義了對象間的一種一對多的依賴關系。
一個對象(觀察目標)發生改變時,將自動通知其他對象(觀察者),其他對象做出相應的反應。
## 1.2 模式結構
包含如下角色:
* Subject:抽象目標(又稱主題)
把所有觀察者對象的引用保存在一個集合中,并且提供接口可以添加、刪除觀察者
* ConcreteSubject:具體目標
抽象目標類的子類,通常包含有經常發生改變的數據或狀態
* Observer:抽象觀察者
定義一個更新接口,在得到目標通知時更新自己
* ConcreteObserver:具體觀察者
存儲與目標狀態一致的狀態,如果需要還可以持有一個指向具體目標對象的引用
## 1.3 代碼示例
抽象目標:
```java
public abstract class Nexus6P {
// 所有觀察者對象保存在集合中
private List<Customer> mCustomerList = new ArrayList<>();
// 添加觀察者
public void attach(Customer customer) {
if (customer == null) {
throw new NullPointerException();
}
if (!mCustomerList.contains(customer)) {
mCustomerList.add(customer);
}
}
// 刪除觀察者
public void detach(Customer customer) {
mCustomerList.remove(customer);
}
// 通知所有注冊的觀察者
public void notifyCustomers(float nowPrice) {
for (Customer customer : mCustomerList) {
customer.update(nowPrice);
}
}
}
```
具體目標:
```java
public class ConcreteNexus6P extends Nexus6P {
// 包含經常發生改變的數據或狀態
private float mNowPrice;
public float getNowPrice() {
return mNowPrice;
}
// 改變數據或狀態
public void changePrice(float nowPrice) {
mNowPrice = nowPrice;
// 狀態改變,通知各個觀察者
this.notifyCustomers(mNowPrice);
}
}
```
抽象觀察者:
```java
public interface Customer {
//狀態更新接口
void update(float nowPrice);
}
```
具體觀察者:
```java
public class ConcreteCustomer implements Customer {
private static final String TAG = "ConcreteCustomer";
// 存儲與目標一致的狀態
private float mNowPrice;
@Override
public void update(float nowPrice) {
// 更新觀察者狀態,使其與觀察目標狀態保持一致
mNowPrice = nowPrice;
if (mNowPrice < 3000) {
buyIt();
} else {
Log.i(TAG, "Too expensive!");
}
}
private void buyIt() {
// buy it
}
}
```
客戶端類:
```java
public class JingDongStore {
public static void main(String[] args) {
ConcreteNexus6P nexus6P = new ConcreteNexus6P();
Customer Mark = new ConcreteCustomer();
nexus6P.attach(Mark);
// nexus6P.attach(Tom);
// ...
nexus6P.changePrice(3200);
nexus6P.changePrice(2800);
}
}
```
觀察者模式又分為推模型和拉模型兩種,以上例子屬于推模型。
* 推模型:由觀察目標通過更新方法向觀察者推送全部或部分數據
* 拉模型:由觀察者主動從觀察目標中拉數據,一般在更新方法中觀察目標會把自身傳遞過去,觀察者持有觀察目標引用
拉模型的例子如下:
```java
// 抽象目標
public abstract class Nexus6P {
private List<Customer> mCustomerList = new ArrayList<>();
public void attach(Customer customer) {
if (customer == null) {
throw new NullPointerException();
}
if (!mCustomerList.contains(customer)) {
mCustomerList.add(customer);
}
}
public void detach(Customer customer) {
mCustomerList.remove(customer);
}
public void notifyCustomers() {
for (Customer customer : mCustomerList) {
customer.update(this);
}
}
}
// 具體目標
public class ConcreteNexus6P extends Nexus6P {
private float mNowPrice;
public float getNowPrice() {
return mNowPrice;
}
public void changePrice(float nowPrice) {
mNowPrice = nowPrice;
this.notifyCustomers();
}
}
// 抽象觀察者
public interface Customer {
void update(Nexus6P nexus6P);
}
// 具體觀察者
public class ConcreteCustomer implements Customer {
private static final String TAG = "ConcreteCustomer";
// 可以持有具體觀察目標的引用
private ConcreteNexus6P mConcreteNexus6P;
private float mNowPrice;
@Override
public void update(Nexus6P nexus6P) {
// 更新觀察者狀態,使其與觀察目標狀態保持一致
mConcreteNexus6P = (ConcreteNexus6P) nexus6P;
mNowPrice = mConcreteNexus6P.getNowPrice();
if (mNowPrice < 3000) {
buyIt();
} else {
Log.i(TAG, "Too expensive!");
}
}
private void buyIt() {
// buy it
}
}
```
## 1.4 總結
* 優點在于可以實現表示層和數據邏輯層的分離,在觀察目標和觀察者之間建立一個抽象的耦合
* 缺點在于一個觀察目標有很多直接或間接的觀察者時,將所有的觀察者全部通知到會花費很多時間;且觀察者和觀察目標之間有循環依賴時,可能會導致崩潰
# 2 命令模式
又稱動作(Action)模式、事物(Transaction)模式
## 2.1 模式定義
命令模式把一個請求或者操作封裝到一個對象中。
## 2.2 模式結構
* 接收者
負責具體實施和執行請求。
* 抽象命令類
聲明了給所有具體命令類的接口方法(execute 方法等),通常叫做執行方法。
* 具體命令類
實現 execute 方法,負責調用接收者的相應操作。
* 請求者
負責調用命令對象(的 execute 方法)執行請求,相關的方法稱為行動方法。
* 客戶端
負責創建具體命令對象,并確定其接收者。
## 2.3 代碼示例
接收者:
```java
public class Nexus6P {
// 真正執行命令的相應操作
public void playMusic() {
// play music
}
public void playVideo() {
// play video
}
public void playGames() {
// play games
}
}
```
抽象命令類:
```java
public interface Command {
// 執行方法
void execute();
}
```
具體命令類:
```java
public class PlayMusicCommand implements Command {
// 接收者
private Nexus6P mNexus6P;
public PlayMusicCommand(Nexus6P nexus6P) {
mNexus6P = nexus6P;
}
// 執行方法
@Override
public void execute() {
mNexus6P.playMusic();
}
}
public class PlayVideoCommand implements Command {
// 接收者
private Nexus6P mNexus6P;
public PlayVideoCommand(Nexus6P nexus6P) {
mNexus6P = nexus6P;
}
// 執行方法
@Override
public void execute() {
mNexus6P.playVideo();
}
}
public class PlayGamesCommand implements Command {
// 接收者
private Nexus6P mNexus6P;
public PlayGamesCommand(Nexus6P nexus6P) {
mNexus6P = nexus6P;
}
// 執行方法
@Override
public void execute() {
mNexus6P.playGames();
}
}
```
請求者:
```java
public class PhoneScreen {
// 具體命令對象
private Command mPlayMusicCommand;
private Command mPlayVideoCommand;
private Command mPlayGamesCommand;
public void setPlayMusicCommand(Command playMusicCommand) {
mPlayMusicCommand = playMusicCommand;
}
public void setPlayVideoCommand(Command playVideoCommand) {
mPlayVideoCommand = playVideoCommand;
}
public void setPlayGamesCommand(Command playGamesCommand) {
mPlayGamesCommand = playGamesCommand;
}
// 行動方法
public void playMusic() {
mPlayMusicCommand.execute();
}
// 行動方法
public void playVideo() {
mPlayVideoCommand.execute();
}
// 行動方法
public void playGames() {
mPlayGamesCommand.execute();
}
}
```
客戶端:
```java
public class CustomerMark {
public static void main(String[] args) {
// 創建接收者
Nexus6P nexus6P = new Nexus6P();
// 創建具體命令對象
Command playMusicCommand = new PlayMusicCommand(nexus6P);
Command playVideoCommand = new PlayVideoCommand(nexus6P);
Command playGamesCommand = new PlayGamesCommand(nexus6P);
// 創建請求者
PhoneScreen phoneScreen = new PhoneScreen();
phoneScreen.setPlayMusicCommand(playMusicCommand);
phoneScreen.setPlayVideoCommand(playVideoCommand);
phoneScreen.setPlayGamesCommand(playGamesCommand);
// 測試
phoneScreen.playMusic();
phoneScreen.playVideo();
phoneScreen.playGames();
}
}
```
## 2.4 總結
* 命令模式使請求本身成為一個對象,這個對象和其他對象一樣可以被存儲和傳遞。
* 優點在于降低系統的耦合度,增加新的命令很方便,而且可以比較容易地設計一個命令隊列和宏命令,并方便地實現對請求的撤銷和恢復
* 缺點在于可能會導致某些系統有過多的具體命令類
* 命令模式適用情況:
* 需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互;
* 需要在不同的時間指定請求、將請求排隊和執行請求;
* 需要支持命令的撤銷操作和恢復操作,需要將一組操作組合在一起,即支持宏命令。
# 3 策略模式
## 3.1 模式定義
策略模式是對算法的包裝,針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中,從而使得它們可以相互替換。客戶端類可自由選擇使用哪一種策略(算法)。
## 3.2 模式結構
* 抽象策略角色(Strategy)
給出所有具體策略類所需要實現的接口
* 具體策略角色(ConcreteStrategy)
包裝了相關的算法或行為
* 環境角色(Context)
環境類在解決某個問題時可以采用多種策略,在環境類中維護一個對抽象策略類的引用實例
## 3.3 代碼示例
抽象策略:
```java
public interface ConsumerStrategy {
double calculatePrice(double originalPrice);
}
```
具體策略類:
```java
public class GoldMedalConsumerStrategy implements ConsumerStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * 0.8;
}
}
public class SilverMedalConsumerStrategy implements ConsumerStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * 0.9;
}
}
public class BronzeMedalConsumerStartegy implements ConsumerStrategy {
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * 0.95;
}
}
```
環境類:
```java
public class Price {
private ConsumerStrategy mStrategy;
public Price(ConsumerStrategy strategy) {
mStrategy = strategy;
}
public double calculate(double phonePrice) {
return mStrategy.calculatePrice(phonePrice);
}
}
```
客戶端類:
```java
public class JingDongstore {
public static void main(String[] args) {
ConsumerStrategy silverMedalConsumerStrategy = new SilverMedalConsumerStrategy();
Price price = new Price(silverMedalConsumerStrategy);
double dealPrice = price.calculate(5000);
System.out.print("手機成交價格為" + dealPrice);
}
}
```
## 3.4 總結
* 策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活,具有更好的維護性和擴展性。
* 對于一系列具體的策略算法,大家的地位是完全一樣的,正因為這個平等性,才能實現算法之間可以相互替換。所有的策略算法在實現上也是相互獨立的,相互之間是沒有依賴的。
* 策略模式在每一個時刻只能使用一個具體的策略實現對象,雖然可以動態地在不同的策略實現中切換,但是同時只能使用一個。
* 在策略模式中,應當由客戶端自己決定在什么情況下使用什么具體策略角色。
* 策略模式適用情況包括:
* 在一個系統里面有許多類,它們之間的區別僅在于它們的行為,使用策略模式可以動態地讓一個對象在許多行為中選擇一種行為;
* 一個系統需要動態地在幾種算法中選擇一種;
* 避免使用難以維護的多重條件選擇語句;
* 希望在具體策略類中封裝算法和與相關的數據結構。
# 4 狀態模式
又稱狀態對象模式
## 4.1 模式定義
狀態模式允許一個對象在其內部狀態改變的時候改變其行為,這個對象看上去就像改變了它的類一樣
狀態模式把所研究的對象(環境角色)的行為包裝在不同的狀態對象里,每一個狀態對象都屬于一個抽象狀態類的一個子類。
## 4.2 模式結構
* 環境角色
定義客戶端感興趣的接口,并持有一個具體狀態類的實例
* 抽象狀態類
定義一個接口,用以封裝環境(Context)對象的一個特定的狀態所對應的行為
* 具體狀態類
每一個具體狀態類都實現了環境(Context)的一個狀態所對應的行為
## 4.3 代碼示例
抽象狀態類:
```java
public interface State {
// 特定狀態所對應的行為
void handle(String sampleParameter);
}
```
具體狀態類:
```java
public class ConcreteStateA implements State {
private static final String TAG = "ConcreteStateA";
@Override
public void handle(String sampleParameter) {
Log.e(TAG, "handle: " + sampleParameter);
}
}
public class ConcreteStateB implements State {
private static final String TAG = "ConcreteStateB";
@Override
public void handle(String sampleParameter) {
Log.e(TAG, "handle: " + sampleParameter);
}
}
```
環境角色類:
```java
public class Context {
// 持有一個具體狀態類的實例
private State mState;
public void setState(State state) {
mState = state;
}
// 定義客戶端感興趣的接口
public void request(String sampleParameter) {
mState.handle(sampleParameter);
}
}
```
客戶端:
```java
public class Client {
public static void main(String[] args) {
Context context = new Context();
State state = new ConcreteStateA();
context.setState(state);
context.request("test");
}
}
```
## 4.4 總結
* 在具體的狀態處理類中經常需要獲取環境 (Context) 自身的數據,甚至在必要的時候會回調環境 (Context) 的方法,因此,通常將環境 (Context) 自身當作一個參數傳遞給具體的狀態處理類。
* 客戶端一般只和環境 (Context) 交互。客戶端可以用狀態對象來配置一個環境(Context),一旦配置完畢,就不再需要和狀態對象打交道了。
* 策略模式與狀態模式的區別
* 可以通過環境類狀態的個數來決定是使用策略模式還是狀態模式。
* 策略模式的環境類自己選擇一個具體策略類,具體策略類無須關心環境類;而狀態模式的環境類由于外在因素需要放進一個具體狀態中,以便通過其方法實現狀態的切換,因此環境類和狀態類之間存在一種雙向的關聯關系。
* 使用策略模式時,客戶端需要知道所選的具體策略是哪一個,而使用狀態模式時,客戶端無須關心具體狀態,環境類的狀態會根據用戶的操作自動轉換。
* 如果系統中某個類的對象存在多種狀態,不同狀態下行為有差異,而且這些狀態之間可以發生轉換時使用狀態模式;如果系統中某個類的某一行為存在多種實現方式,而且這些實現方式可以互換時使用策略模式。
- 導讀
- Java知識
- Java基本程序設計結構
- 【基礎知識】Java基礎
- 【源碼分析】Okio
- 【源碼分析】深入理解i++和++i
- 【專題分析】JVM與GC
- 【面試清單】Java基本程序設計結構
- 對象與類
- 【基礎知識】對象與類
- 【專題分析】Java類加載過程
- 【面試清單】對象與類
- 泛型
- 【基礎知識】泛型
- 【面試清單】泛型
- 集合
- 【基礎知識】集合
- 【源碼分析】SparseArray
- 【面試清單】集合
- 多線程
- 【基礎知識】多線程
- 【源碼分析】ThreadPoolExecutor源碼分析
- 【專題分析】volatile關鍵字
- 【面試清單】多線程
- Java新特性
- 【專題分析】Lambda表達式
- 【專題分析】注解
- 【面試清單】Java新特性
- Effective Java筆記
- Android知識
- Activity
- 【基礎知識】Activity
- 【專題分析】運行時權限
- 【專題分析】使用Intent打開三方應用
- 【源碼分析】Activity的工作過程
- 【面試清單】Activity
- 架構組件
- 【專題分析】MVC、MVP與MVVM
- 【專題分析】數據綁定
- 【面試清單】架構組件
- 界面
- 【專題分析】自定義View
- 【專題分析】ImageView的ScaleType屬性
- 【專題分析】ConstraintLayout 使用
- 【專題分析】搞懂點九圖
- 【專題分析】Adapter
- 【源碼分析】LayoutInflater
- 【源碼分析】ViewStub
- 【源碼分析】View三大流程
- 【源碼分析】觸摸事件分發機制
- 【源碼分析】按鍵事件分發機制
- 【源碼分析】Android窗口機制
- 【面試清單】界面
- 動畫和過渡
- 【基礎知識】動畫和過渡
- 【面試清單】動畫和過渡
- 圖片和圖形
- 【專題分析】圖片加載
- 【面試清單】圖片和圖形
- 后臺任務
- 應用數據和文件
- 基于網絡的內容
- 多線程與多進程
- 【基礎知識】多線程與多進程
- 【源碼分析】Handler
- 【源碼分析】AsyncTask
- 【專題分析】Service
- 【源碼分析】Parcelable
- 【專題分析】Binder
- 【源碼分析】Messenger
- 【面試清單】多線程與多進程
- 應用優化
- 【專題分析】布局優化
- 【專題分析】繪制優化
- 【專題分析】內存優化
- 【專題分析】啟動優化
- 【專題分析】電池優化
- 【專題分析】包大小優化
- 【面試清單】應用優化
- Android新特性
- 【專題分析】狀態欄、ActionBar和導航欄
- 【專題分析】應用圖標、通知欄適配
- 【專題分析】Android新版本重要變更
- 【專題分析】唯一標識符的最佳做法
- 開源庫源碼分析
- 【源碼分析】BaseRecyclerViewAdapterHelper
- 【源碼分析】ButterKnife
- 【源碼分析】Dagger2
- 【源碼分析】EventBus3(一)
- 【源碼分析】EventBus3(二)
- 【源碼分析】Glide
- 【源碼分析】OkHttp
- 【源碼分析】Retrofit
- 其他知識
- Flutter
- 原生開發與跨平臺開發
- 整體歸納
- 狀態及狀態管理
- 零碎知識點
- 添加Flutter到現有應用
- Git知識
- Git命令
- .gitignore文件
- 設計模式
- 創建型模式
- 結構型模式
- 行為型模式
- RxJava
- 基礎
- Linux知識
- 環境變量
- Linux命令
- ADB命令
- 算法
- 常見數據結構及實現
- 數組
- 排序算法
- 鏈表
- 二叉樹
- 棧和隊列
- 算法時間復雜度
- 常見算法思想
- 其他技術
- 正則表達式
- 編碼格式
- HTTP與HTTPS
- 【面試清單】其他知識
- 開發歸納
- Android零碎問題
- 其他零碎問題
- 開發思路