行爲型模式java
行爲型模式關注的是各個類之間的相互做用,將職責劃分清楚,使得咱們的代碼更加地清晰。程序員
策略模式太經常使用了,因此把它放到最前面進行介紹。它比較簡單,我就不廢話,直接用代碼說事吧。設計模式
下面設計的場景是,咱們須要畫一個圖形,可選的策略就是用紅色筆來畫,仍是綠色筆來畫,或者藍色筆來畫。微信
首先,先定義一個策略接口:網絡
public interface Strategy { public void draw(int radius, int x, int y); }
而後咱們定義具體的幾個策略:多線程
public class RedPen implements Strategy { @Override public void draw(int radius, int x, int y) { System.out.println("用紅色筆畫圖,radius:" + radius + ", x:" + x + ", y:" + y); } } public class GreenPen implements Strategy { @Override public void draw(int radius, int x, int y) { System.out.println("用綠色筆畫圖,radius:" + radius + ", x:" + x + ", y:" + y); } } public class BluePen implements Strategy { @Override public void draw(int radius, int x, int y) { System.out.println("用藍色筆畫圖,radius:" + radius + ", x:" + x + ", y:" + y); } }
使用策略的類:app
public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeDraw(int radius, int x, int y){ return strategy.draw(radius, x, y); } }
客戶端演示:異步
public static void main(String[] args) { Context context = new Context(new BluePen()); // 使用綠色筆來畫 context.executeDraw(10, 0, 0); }
放到一張圖上,讓你們看得清晰些:分佈式
這個時候,你們有沒有聯想到結構型模式中的橋樑模式,它們其實很是類似,我把橋樑模式的圖拿過來你們對比下:ide
要我說的話,它們很是類似,橋樑模式在左側加了一層抽象而已。橋樑模式的耦合更低,結構更復雜一些。
觀察者模式對於咱們來講,真是再簡單不過了。無外乎兩個操做,觀察者訂閱本身關心的主題和主題有數據變化後通知觀察者們。
首先,須要定義主題,每一個主題須要持有觀察者列表的引用,用於在數據變動的時候通知各個觀察者:
public class Subject { private List<Observer> observers = new ArrayList<Observer>(); private int state; public int getState() { return state; } public void setState(int state) { this.state = state; // 數據已變動,通知觀察者們 notifyAllObservers(); } public void attach(Observer observer){ observers.add(observer); } // 通知觀察者們 public void notifyAllObservers(){ for (Observer observer : observers) { observer.update(); } } }
定義觀察者接口:
public abstract class Observer { protected Subject subject; public abstract void update(); }
其實若是隻有一個觀察者類的話,接口都不用定義了,不過,一般場景下,既然用到了觀察者模式,咱們就是但願一個事件出來了,會有多個不一樣的類須要處理相應的信息。好比,訂單修改爲功事件,咱們但願發短信的類獲得通知、發郵件的類獲得通知、處理物流信息的類獲得通知等。
咱們來定義具體的幾個觀察者類:
public class BinaryObserver extends Observer { // 在構造方法中進行訂閱主題 public BinaryObserver(Subject subject) { this.subject = subject; // 一般在構造方法中將 this 發佈出去的操做必定要當心 this.subject.attach(this); } // 該方法由主題類在數據變動的時候進行調用 @Override public void update() { String result = Integer.toBinaryString(subject.getState()); System.out.println("訂閱的數據發生變化,新的數據處理爲二進制值爲:" + result); } } public class HexaObserver extends Observer { public HexaObserver(Subject subject) { this.subject = subject; this.subject.attach(this); } @Override public void update() { String result = Integer.toHexString(subject.getState()).toUpperCase(); System.out.println("訂閱的數據發生變化,新的數據處理爲十六進制值爲:" + result); } }
客戶端使用也很是簡單:
public static void main(String[] args) { // 先定義一個主題 Subject subject1 = new Subject(); // 定義觀察者 new BinaryObserver(subject1); new HexaObserver(subject1); // 模擬數據變動,這個時候,觀察者們的 update 方法將會被調用 subject.setState(11); }
output:
訂閱的數據發生變化,新的數據處理爲二進制值爲:1011 訂閱的數據發生變化,新的數據處理爲十六進制值爲:B
固然,jdk 也提供了類似的支持,具體的你們能夠參考 java.util.Observable 和 java.util.Observer 這兩個類。
實際生產過程當中,觀察者模式每每用消息中間件來實現,若是要實現單機觀察者模式,筆者建議讀者使用 Guava 中的 EventBus,它有同步實現也有異步實現,本文主要介紹設計模式,就不展開說了。
責任鏈一般須要先創建一個單向鏈表,而後調用方只須要調用頭部節點就能夠了,後面會自動流轉下去。好比流程審批就是一個很好的例子,只要終端用戶提交申請,根據申請的內容信息,自動創建一條責任鏈,而後就能夠開始流轉了。
有這麼一個場景,用戶參加一個活動能夠領取獎品,可是活動須要進行不少的規則校驗而後才能放行,好比首先須要校驗用戶是不是新用戶、今日參與人數是否有限額、全場參與人數是否有限額等等。設定的規則都經過後,才能讓用戶領走獎品。
若是產品給你這個需求的話,我想大部分人一開始確定想的就是,用一個 List 來存放全部的規則,而後 foreach 執行一下每一個規則就行了。不過,讀者也先別急,看看責任鏈模式和咱們說的這個有什麼不同?
首先,咱們要定義流程上節點的基類:
public abstract class RuleHandler { // 後繼節點 protected RuleHandler successor; public abstract void apply(Context context); public void setSuccessor(RuleHandler successor) { this.successor = successor; } public RuleHandler getSuccessor() { return successor; } }
接下來,咱們須要定義具體的每一個節點了。
校驗用戶是不是新用戶:
public class NewUserRuleHandler extends RuleHandler { public void apply(Context context) { if (context.isNewUser()) { // 若是有後繼節點的話,傳遞下去 if (this.getSuccessor() != null) { this.getSuccessor().apply(context); } } else { throw new RuntimeException("該活動僅限新用戶參與"); } } }
校驗用戶所在地區是否能夠參與:
public class LocationRuleHandler extends RuleHandler { public void apply(Context context) { boolean allowed = activityService.isSupportedLocation(context.getLocation); if (allowed) { if (this.getSuccessor() != null) { this.getSuccessor().apply(context); } } else { throw new RuntimeException("很是抱歉,您所在的地區沒法參與本次活動"); } } }
校驗獎品是否已領完:
public class LimitRuleHandler extends RuleHandler { public void apply(Context context) { int remainedTimes = activityService.queryRemainedTimes(context); // 查詢剩餘獎品 if (remainedTimes > 0) { if (this.getSuccessor() != null) { this.getSuccessor().apply(userInfo); } } else { throw new RuntimeException("您來得太晚了,獎品被領完了"); } } }
客戶端:
public static void main(String[] args) { RuleHandler newUserHandler = new NewUserRuleHandler(); RuleHandler locationHandler = new LocationRuleHandler(); RuleHandler limitHandler = new LimitRuleHandler(); // 假設本次活動僅校驗地區和獎品數量,不校驗新老用戶 locationHandler.setSuccessor(limitHandler); locationHandler.apply(context); }
代碼其實很簡單,就是先定義好一個鏈表,而後在經過任意一節點後,若是此節點有後繼節點,那麼傳遞下去。
至於它和咱們前面說的用一個 List 存放須要執行的規則的作法有什麼異同,留給讀者本身琢磨吧。
在含有繼承結構的代碼中,模板方法模式是很是經常使用的,這也是在開源代碼中大量被使用的。
一般會有一個抽象類:
public abstract class AbstractTemplate { // 這就是模板方法 public void templateMethod(){ init(); apply(); // 這個是重點 end(); // 能夠做爲鉤子方法 } protected void init() { System.out.println("init 抽象層已經實現,子類也能夠選擇覆寫"); } // 留給子類實現 protected abstract void apply(); protected void end() { } }
模板方法中調用了 3 個方法,其中 apply() 是抽象方法,子類必須實現它,其實模板方法中有幾個抽象方法徹底是自由的,咱們也能夠將三個方法都設置爲抽象方法,讓子類來實現。也就是說,模板方法只負責定義第一步應該要作什麼,第二步應該作什麼,第三步應該作什麼,至於怎麼作,由子類來實現。
咱們寫一個實現類:
public class ConcreteTemplate extends AbstractTemplate { public void apply() { System.out.println("子類實現抽象方法 apply"); } public void end() { System.out.println("咱們能夠把 method3 當作鉤子方法來使用,須要的時候覆寫就能夠了"); } }
客戶端調用演示:
public static void main(String[] args) { AbstractTemplate t = new ConcreteTemplate(); // 調用模板方法 t.templateMethod(); }
代碼其實很簡單,基本上看到就懂了,關鍵是要學會用到本身的代碼中。
update: 2017-10-19
廢話我就不說了,咱們說一個簡單的例子。商品庫存中心有個最基本的需求是減庫存和補庫存,咱們看看怎麼用狀態模式來寫。
核心在於,咱們的關注點再也不是 Context 是該進行哪一種操做,而是關注在這個 Context 會有哪些操做。
定義狀態接口:
public interface State { public void doAction(Context context); }
定義減庫存的狀態:
public class DeductState implements State { public void doAction(Context context) { System.out.println("商品賣出,準備減庫存"); context.setState(this); //... 執行減庫存的具體操做 } public String toString(){ return "Deduct State"; } }
定義補庫存狀態:
public class RevertState implements State { public void doAction(Context context) { System.out.println("給此商品補庫存"); context.setState(this); //... 執行加庫存的具體操做 } public String toString() { return "Revert State"; } }
前面用到了 context.setState(this),咱們來看看怎麼定義 Context 類:
public class Context { private State state; private String name; public Context(String name) { this.name = name; } public void setState(State state) { this.state = state; } public void getState() { return this.state; } }
咱們來看下客戶端調用,你們就一清二楚了:
public static void main(String[] args) { // 咱們須要操做的是 iPhone X Context context = new Context("iPhone X"); // 看看怎麼進行補庫存操做 State revertState = new RevertState(); revertState.doAction(context); // 一樣的,減庫存操做也很是簡單 State deductState = new DeductState(); deductState.doAction(context); // 若是須要咱們能夠獲取當前的狀態 // context.getState().toString(); }
讀者可能會發現,在上面這個例子中,若是咱們不關心當前 context 處於什麼狀態,那麼 Context 就能夠不用維護 state 屬性了,那樣代碼會簡單不少。
不過,商品庫存這個例子畢竟只是個例,咱們還有不少實例是須要知道當前 context 處於什麼狀態的。
行爲型模式部分介紹了策略模式、觀察者模式、責任鏈模式、模板方法模式和狀態模式,其實,經典的行爲型模式還包括備忘錄模式、命令模式等,可是它們的使用場景比較有限,並且本文篇幅也挺大了,我就不進行介紹了。
學習設計模式的目的是爲了讓咱們的代碼更加的優雅、易維護、易擴展。此次整理這篇文章,讓我從新審視了一下各個設計模式,對我本身而言收穫仍是挺大的。我想,文章的最大收益者通常都是做者本人,爲了寫一篇文章,須要鞏固本身的知識,須要尋找各類資料,並且,本身寫過的才最容易記住,也算是我給讀者的建議吧。
(全文完)
轉自https://javadoop.com/post/des...
更多內容請關注微信公衆號【Java技術江湖】
這是一位阿里 Java 工程師的技術小站,做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!(關注公衆號後回覆」資料「便可領取 3T 免費技術學習資源以及我我原創的程序員校招指南、Java學習指南等資源)