初探Java設計模式4:JDK中的設計模式

行爲型模式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);
}

放到一張圖上,讓你們看得清晰些:分佈式

strategy-1

這個時候,你們有沒有聯想到結構型模式中的橋樑模式,它們其實很是類似,我把橋樑模式的圖拿過來你們對比下:ide

bridge-1

要我說的話,它們很是類似,橋樑模式在左側加了一層抽象而已。橋樑模式的耦合更低,結構更復雜一些。

觀察者模式

觀察者模式對於咱們來講,真是再簡單不過了。無外乎兩個操做,觀察者訂閱本身關心的主題和主題有數據變化後通知觀察者們。

首先,須要定義主題,每一個主題須要持有觀察者列表的引用,用於在數據變動的時候通知各個觀察者:

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學習指南等資源)

相關文章
相關標籤/搜索