設計模式 GRASP & GoF

借用公開課 Justice 中的話,瞭解設計模式不必定能讓咱們解決軟件設計與開發中的問題,但能讓咱們在遇到問題時,思考的方式不至魯莽與茫然。php

五大設計原則

面向對象軟件設計具備五大基本原則(首字母縮寫爲:SOLID):html

單一職責原則

單一職責原則(SRP:Single Responsibility Principle):不要讓一個類承擔過多的職責,就一個類而言,應該僅有一個引發它變化的緣由。git

開放閉合原則

開閉原則(OCP:Open-Close Priciple):保證類對擴展開放,而對修改封閉。github

李氏替換原則

里氏代換(LSP:The Liskov Substitution Principle):子類必須可以替換掉它們的父類。web

接口隔離原則

接口隔離原則(ISP:The Interface Segregation priciple):客戶端不該該依賴它不須要的接口;一個類對另外一個類的依賴應該創建在最小的接口上;不要讓類實現含有無用方法的接口。算法

依賴倒置原則

依賴倒轉(DIP:Dependency Inversion Principle):高層次的模塊不該該依賴於低層次的模塊,他們都應該依賴於抽象;抽象不該該依賴於具體,具體應該依賴於抽象。數據庫

如下對 GRASP & GoF 所表明的設計模式進行介紹,這些設計模式並不是徹底按照以上基本原則,有的甚至爲了解決某一類問題而破壞這些原則。
下述全部示例代碼能夠在 Github 上查看。編程

GRASP

通用責任鏈分配模式的核心思想是職責分配(Responsibility Assignment)。設計模式

專家模式(Expert Pattern)

介紹

專家模式要求將請求的處理交由信息的全部者。api

缺點

可能會致使信息專家對象承擔過多的職責。

示例

考慮登陸需求,在控制器中,將登陸驗證邏輯交由擁有登陸所需信息的類來進行,而不是控制器自己。

類圖

專家模式

核心代碼

public class Controller {
    public String login(){
        User user = new User("username", "password");
        boolean canLogin = user.login();
        if(canLogin){
            return "www.website.com/index";
        }else{
            return "www.website.com/error.401.php";
        }
    }
}

控制器模式(Controller Pattern)

介紹

控制器模式要求模塊可以接受請求,並將請求轉發到業務處理模塊,且按照處理模塊的處理結果反饋響應(MVC 模式)。

缺點

控制器承擔了過多職責,控制器每每與大量類存在耦合。

示例

MVC 模式中的控制器層。

類圖

控制器模式

注意,View 類也能夠直接調用 Model 類。

核心代碼

public class Controller {
    public Object dispatch(Object request){
        Model model = new Model();
        Object res = model.handle(request);
        return res;
    }
}

建立者模式(Creator Pattern)

介紹

建立者模式定義了一些標準,要求在如下狀況下,A 類對象須要是 B 類對象的建立者:

  1. A 類對象是 B 類對象的聚合體;

  2. A 類對象包含 B 類對象;

  3. A 類對象使用 B 類對象;

  4. A 類對象記錄 B 類對象狀態;

  5. A 類對象擁有建立 B 類對象的數據/信息。

缺點

因爲以上關係可能存在於大量類之間,致使 B 類的建立過程難以統一。

GoF

GoF:Gang of Four,指被稱爲設計模式先驅的四人:Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。他們提出瞭如下 23 種設計模式,且這些設計模式又可被分爲建立型模式、結構型模式,以及行爲型模式。

GoF - 建立型模式

單例模式(Singleton Pattern)

介紹

確保類中只有一個實例,試圖獲取這一類的邏輯代碼所獲得的對象均爲同一個。

缺點

在多線程編程中須要保證訪問的同步。

示例

如獲取配置文件的內容,因爲配置文件在項目運行後通常不會再修改,因此配置對象能夠採用單例模式實現。

類圖

單例模式

核心代碼

public class Config {
    private static Config config = null;
    // 私有化構造方法
    private Config() {}
    /**
     * 當對象爲空時,先建立再返回
     * 反之直接返回
     * @return
     */
    public static Config getConfig(){
        if(Config.config == null){
           Config.config = Config.createConfig();
           return Config.getConfig();
        }else{
            return Config.config;
        }
    }
    /**
     * 若是對象的建立過程比較複雜,能夠單獨抽離一個 createConfig 方法
     * 固然也能夠把建立過程直接寫在 getConfig 方法之中
     * @return
     */
    private static Config createConfig(){
        return new Config();
    }
}

注意:爲了讓外界不能再調用構造方法,應當將其私有化。此外,在其餘語言中,可能還須要將序列化和克隆方法均聲明爲私有。

原型模式(Prototype Pattern)

介紹

經過拷貝原型實例而非從新調用構造方法的方式獲得新的對象。

缺點

考慮原型對象的過程可能比較繁瑣(涉及深拷貝問題);且須要特別注意循環引用的問題(類的屬性中又包含該類)。

示例

對一類通知對象的構建。通知對象中含有大量的重複屬性,如通知頭部信息和通知尾部信息。

類圖

原型模式

核心代碼

public class Message implements Cloneable{
    // 通用頭部
    private Object commonHeader = null;
    // 通用尾部
    private Object commonFooter = null;
    // 定製主體
    private Object body;

    @Override
    protected Message clone() throws CloneNotSupportedException {
        Message message = (Message) super.clone();
        // 若是頭部和尾部的對象建立過程須要深拷貝,則須要特殊處理這段代碼
        message.commonHeader = new Object();
        message.commonFooter = new Object();
        return message;
    }
    public void setBody(Object body) {
        this.body = body;
    }
}

注意:在 Java 代碼中須要實現 Cloneable 接口,而在其餘語言中可能須要用其餘機制。如 PHP 中,須要重寫魔術方法 __clone(),並在須要調用時使用 $obj1 = clone $obj2

構造器模式(Builder Pattern)

介紹

使得複雜對象的構造過程和表示分離,以即可以複用相同的構造過程。

缺點

每一類產品都須要一個構造器,增長了代碼量。

示例

訂單可能須要轉換爲 XMLJson 格式,雖然轉換方法自己是不一樣的,但轉換的步驟(轉換訂單地址、轉換配送時間等)是相同的,於是可使用構造器模式實現。

類圖

構造器模式

注意,雖然 Client 依賴箭頭指向的是 OrderJSONBuilder,但其實際依賴的 OrderBuilder 接口。

核心代碼

public class Client {
    private void doSome(){
        OrderBuilder orderBuilder = new OrderJSONBuilder();
        Director director = new Director(orderBuilder);
        // 採用指導器用統一的過程指導構建
        director.build();
        // 從具體的構造類的得到構造結果
        Object res = orderBuilder.getRes();
    }
}

注意,用 Director 進行構造過程,而最終的結果是從具體的 OrderBuilder 中得到的,Director 中的代碼爲:

public class Director {

    private OrderBuilder orderBuilder;

    public Director(OrderBuilder orderBuilder) {
        this.orderBuilder = orderBuilder;
    }

    public void build(){
        this.orderBuilder.convertAddr();
        this.orderBuilder.convertFooter();
        this.orderBuilder.convertHeader();
        this.orderBuilder.convertTime();
    }
}

簡單工廠模式(Simple Factory Pattern)

介紹

爲了介紹後續的工廠類模式,這裏先對簡單工廠的實現進行介紹(該模式並不屬於 GoF 的 23 種設計模式之一)。簡單工廠模式將對象的構建過程統一到一塊兒,在業務代碼中須要使用到這一對象時,統一經過工廠類進行構建。這種模式使得對象的構造過程得以統一,讓對象構造過程的變化不至於致使大範圍的代碼修改。此外,當構造過程很是複雜時,採用簡單工廠也能夠減小代碼的冗餘。

缺點

構造工廠的引入增長了代碼量。

示例

訂單對象的構建過程一般須要對樣式、實際信息、附加信息等進行構造,相對複雜和麻煩,這時可使用訂單構造工廠來進行實現。

類圖

簡單工廠模式

核心代碼

public class OrderFactory {

    public static Order createOrder(){

        Object style = new Object();
        Object body = new Object();
        Object appendix = new Object();

        Order order = new Order(style, body, appendix);
        return order;
    }
}

工廠方法模式(Factroy Method Pattern)

介紹

對比簡單工廠模式,若是當前須要工廠來進行具體對象的構建,但工廠所要進行的建立過程如今還未可知時,咱們便須要考慮使用工廠方法模式進行實現。工廠方法模式將具體的工廠建立行爲交由子類實現。

缺點

工廠方法模式進一步聚合了一組構建對象和一組構建它們的工廠。當須要建立新的對象時,新的工廠也須要被定義。

示例

訂單的導出過程結果能夠是 pdf 形式,也能夠是 html 形式,而訂單的構造過程爲轉換格式並導出,對於不一樣格式的訂單構造,只有轉換格式這一步是不一樣的,此時能夠考慮使用工廠方法模式。

類圖

工廠方法模式

核心代碼

abstract public class OrderFactory {

    protected Order order;

    public Order export(){
        this.convert();
        return this.order;
    }

    abstract protected void convert();

}

而後,能夠在 Client 中這樣使用:

public class Client {

    public void doSome(){

        OrderFactory factory = new PdfOrderFactory();
        Order order = factory.export();

    }
}

抽象工廠模式(Abstract Factory Pattern)

介紹

類比工廠方法模式,若是建立的對象分爲多類,每一類又含有相同的分類時,能夠考慮使用抽象工廠模式。該模式爲產品族羣對象或相互關聯對象提供了統一的接口。

缺點

應對產品類型增長這一變化時,抽象工廠模式相對困難。

示例

圖表能夠分爲餅狀圖、折線圖和柱形圖,而每一種圖表格又能夠分爲扁平風格和水晶風格。咱們能夠把這種類型的對象建立過程採用抽象工廠模式進行實現。

類圖

抽象工廠模式

核心代碼

Client 端使用的方式爲:

public class Client {

    public void doSome(){
        FlatChartFactory flatChartFactory = new FlatChartFactory();
        CrystalChartFactory crystalChartFactory = new CrystalChartFactory();

        BarChart barChart = flatChartFactory.createBarChart();
        LineChart lineChart = crystalChartFactory.createLineChart();
    }

}

ChartFactory 的代碼爲:

abstract public class ChartFactory {
    abstract public PieChart createPieChart();
    abstract public LineChart createLineChart();
    abstract public BarChart createBarChart();
}

抽象工廠實際上是將不一樣的分類方式巧妙的利用了起來。對於這一示例,若是須要增長 3D 類型的構造工廠,則只需從新實現一個工廠類便可。但若要新增一款產品(如雷達圖),則須要改動的代碼會至關多。

GoF - 結構型模式

適配器模式(Adapter Pattern)

介紹

經過將原接口轉換爲目標接口以實現不一樣對象之間的適配。

缺點

一個適配器只能適應一個目標需求。大量使用適配器可能會致使項目代碼層級愈來愈多。

示例

視圖層渲染視圖的時候,須要使用某一類型的對象,而業務代碼所能提供的是另外一種類型。此時可使用適配器模式來解決問題。

類圖

適配器模式

核心代碼

public class Controller {

    public void dispatch(){
        Viewer viewer = new Viewer();
        Data originData = new Data();
        DList data = new DataAdapter(originData);
        // 要求傳入的 DList 類型的對象,咱們藉助適配器進行了接口適配
        viewer.render(data);
    }

}

這種實現一方接口的適配器被稱爲對象適配器;若是該適配器同時實現或繼承調用者和被調用者雙方,則稱其爲類適配器(可能須要多繼承的語法支持)。

橋模式(Bridge Pattern)

介紹

橋模式旨在進行抽象層和實現層的分離。

缺點

增長了類設計的數量。

示例

數據庫日誌類中須要實現對數據庫的日誌操做,該日誌操做又分爲 XML 格式日誌和 JSON 格式日誌。此外,數據庫管理類須要依賴數據庫操做類實現數據操做,且數據庫管理類又存在 MySQL、SQLite 等多種實現。應對這一需求,可使用橋模式實現。

類圖

橋模式

核心代碼

public class Client {

   public void doSome(){
       DBOperator operator = new DBMySQLOperator();
       DBLogger logger = new DBXMLLogger();

       logger.setOperator(operator);
       logger.log();
   }

}

組合模式(Composite Pattern)

介紹

組合模式將聚合體及其內部的組成元素統一看成一種類型看待和操做。

缺點

組合模式要求把聚合體和元素都看做等同的類型,這在一些狀況下存在使用的侷限性。

示例

被渲染頁面中可能存在多個佈局模塊,模塊中的一些部分又能夠看成子佈局模塊,這其中也存在一些非聚合體組件。對於這種需求和狀況,能夠考慮使用組合模式。

類圖

組合模式

核心代碼

視圖層基於某一模塊進行渲染:

public class Viewer {

    public void render(){
        Renderable page = new Module();
        page.draw();
    }

}

若模塊的某子部分爲普通的元素,則直接渲染,若仍爲模塊,則遞歸渲染:

public class Module implements Renderable {

    private List<Renderable> components = new ArrayList<>();

    @Override
    public void draw() {
        Iterator<Renderable> iterator = this.components.iterator();
        while (iterator.hasNext()){
            iterator.next().draw();
        }
    }
}

裝飾器模式(Decorator Pattern)

介紹

實現動態地向對象中添加功能。

缺點

動態地添加功能,則意味着實際調用對象的功能與原對象不一樣,致使調試上出現困難。

示例

每一種咖啡都有價格選項,而只有一部分咖啡能夠加糖或加牛奶。面對這種對象組織和需求,能夠考慮使用裝飾器模式。

類圖

裝飾器模式

核心代碼

public class Client {

    public void offer(){
        Coffee coffee = new SimpleCoffee();

        // 加糖
        Coffee coffeWithSuger = new CoffeeSugerDecorator(coffee);

        // 加牛奶
        Coffee coffeeWithMilkAndSuger = new CoffeeMilkDecorator(coffeWithSuger);

    }

}

門面模式(Facade Pattern)

介紹

爲了避免把複雜的操做過程通通暴露出去,能夠在類中僅定義一個公有方法給外界,而把複雜的過程隱藏在這個方法中。

缺點

暴露的方法的會受到內部實現方法變更的影響。

示例

一次 API 接口的調用可能須要通過不少步驟,若是這些方法通通暴露在外界的話,會增長外部使用這一對象的難度。於是咱們能夠只留下一個方法,而把複雜的步驟隱藏在這一方法中。

類圖

門面模式

核心代碼

在客戶端,咱們能夠只調用暴露的方法:

public class Client {

    public void callAPI(){
        APICaller apiCaller = new APICaller();
        Object res = apiCaller.call();
    }

}

而把具體的過程隱藏起來:

public class APICaller {
    public Object call(){
        Object res = getOriginData();
        res = encryptData(res);
        res = compressData(res);
        return res;
    }

    private Object getOriginData(){
        return new Object();
    }

    private Object encryptData(Object originData){
        // 加密處理
        return originData;
    }

    private Object compressData(Object originData){
        // 壓縮
        return originData;
    }
}

享元模式(Flyweight Pattern)

介紹

享元模式旨在採用共享方式有效使用數量巨大的細粒度對象。

缺點

在向享元管理類申請資源時須要伴隨必定的邏輯處理過程,會有資源消耗。

示例

把資源類對象的申請過程交由資源管理類進行,管理類擁有資源池,負責合理地分配和回收資源,以達到資源的最大利用率。

類圖

享元模式

核心代碼

public class Client {

    public void doSome(){
        ResourcePool pool = new ResourcePool();

        try {
            Object o = pool.apply(4);
        } catch (Exception e) {
            System.out.println("資源申請失敗");
            e.printStackTrace();
        }
    }

}

在資源管理類中,代碼爲:

public class ResourcePool {

    public ArrayList<Object> resources = new ArrayList<>();

    public Object apply(int index) throws Exception {
        if(resources.size() >= index + 1){

            if(resources.get(index) == null){
                Object flyweight = new Object();
                resources.set(index, flyweight);
            }

            return resources.get(index);

        }else{
            throw new Exception("Invalid request");
        }
    }

}

注意,這裏實現的是簡化的資源申請和管理策略。

代理模式(Proxy Pattern)

介紹

爲目標對象提供代理對象,從而能夠在真正的對象執行邏輯先後插入一些邏輯代碼,如日誌記錄等。

缺點

代理類的引入使得代碼的邏輯變得複雜。

示例

在向數據庫存取數據操做的先後進行操做類型和花費時間的記錄,此時能夠考慮使用代理模式。

類圖

代理模式

核心代碼

在客戶端使用相同的方式調用操做類:

public class Client {

    public void doSome(){
        DBOperator operator = new DBOperatorProxy();
        Object o = operator.getData();
    }

}

而實際的代碼執行過程先後被加入了另外的邏輯:

public class DBOperatorProxy implements DBOperator{

    private DBOperator dbOperator = new DBOperatorImpl();

    @Override
    public Object getData() {
        // 前置操做

        Object res = this.dbOperator.getData();

        // 後置操做

        return res;
    }
}

注意,這裏使用的是靜態代理模式,在不少語言中,還可使用反射機制實現動態代理甚至面向切面開發。

GoF - 行爲型模式

責任鏈模式(Chain of Responsibility Pattern)

介紹

當處理某一請求須要多個步驟,且這多個步驟之間能夠以某一順序依次進行時,可使用責任鏈模式。

缺點

責任鏈模式雖然串聯瞭解決問題的步驟,但並未保證問題必定能夠在鏈上被處理。

示例

訂單驗證功能可能須要多層驗證機制才能確保無誤,此時可使用責任鏈模式進行實現。

類圖

責任鏈模式

核心代碼

在客戶端中,只需調用首位驗證器便可:

public class Client {

    public void auth(){

        Object object = new Object();
        Validator validator = new LoginValidator();
        validator.validate(object);

    }

}

這以後的鏈式操做會由驗證器依次進行:

abstract public class Validator {
    protected Validator successor = null;

    public boolean validate(Object object){
        // 驗證過程
        boolean isValid = false;
        if(isValid){
            return true;
        }else{
            if(this.successor != null){
                return this.successor.validate(object);
            }else{
                return false;
            }
        }
    }

    public void setSuccessor(Validator successor) {
        this.successor = successor;
    }
}

固然,咱們也能夠經過 setter 方法動態的設置責任鏈上的驗證器後繼節點。

命令模式(Command Pattern)

介紹

命令模式將操做封裝爲類,使得對執行過程的撤銷變得易於實現。

缺點

因爲操做都被封裝爲了類,所以項目中的代碼量將會劇增。

示例

對數據庫的操做能夠分爲增刪改查,當進行具體操做時,爲了保證操做易於撤回,咱們能夠考慮使用命令模式進行實現。

類圖

命令模式

核心代碼

在客戶端,可使用 Invoker 執行命令:

public class Client {

    public void doSome(){
        Command commandA = new QueryCommand();
        Command commandB = new CreateCommand();
        Command commandC = new UpdateCommand();
        Command commandD = new DeleteCommand();

        Invoker invoker = new Invoker();
        invoker.invoke(commandA);
        invoker.invoke(commandB);
        invoker.undo();
        invoker.invoke(commandC);
        invoker.undo();
        invoker.invoke(commandD);
    }
}

Invoker 中的代碼爲:

public class Invoker {

    private History history = new History();

    public void invoke(Command command){
        this.history.add(command);
    }

    public void undo(){
        this.history.undo();
    }

}

History 中,對命令歷史進行保存:

public class History {

    private Stack<Command> history = new Stack<>();

    public void undo(){
        Command command = history.pop();
        command.undo();
    }

    public void add(Command command){
        command.redo();
        this.history.push(command);
    }

}

解釋器模式(Interpreter Pattern)

介紹

對於自定義表達式的運算,須要涉及到對錶達式的解析和執行過程。如此,能夠考慮使用解釋器模式。

缺點

難以處理複雜的解釋過程。

示例

如自定義的中文加法運算的執行過程(一 + 一 = 二),可使用該模式。

類圖

解釋器模式

核心代碼

將表達式分爲終結表達式和非終結表達式,在解釋過程當中,用相似遞歸的方式進行解釋:

public class AddExpression extends NonFinalExpression {
    @Override
    public int interpret(Context context) {
        int left = this.left.interpret(context);
        int right = this.right.interpret(context);
        return left + right;
    }
}

迭代器模式(Iterator Pattern)

介紹

當但願可以在不暴露內部實現細節的狀況下提供某一類的遍歷手段時,能夠考慮使用迭代器模式。

缺點

迭代過程返回內部的對象,會形成封裝性的破壞。

示例

好比要遍歷工資管理類中的所有工資清單,可使用這一模式。

類圖

迭代器模式

核心代碼

將一個類變爲可迭代的類(不一樣語言中實現的方式不一樣):

public class SalaryCollection implements Iterator<Salary>{

    private Salary salaries[] = {
            new Salary(10000),
            new Salary(20000),
            new Salary(15000),
            new Salary(30000),
            new Salary(5000)
    };

    private Integer index = 0;

    @Override
    public boolean hasNext() {
        return index >= salaries.length;
    }

    @Override
    public Salary next() {
        return salaries[index++];
    }

}

而後在客戶端對其進行遍歷:

public class Client {

    public void doSome(){
        SalaryCollection collection = new SalaryCollection();
        int sum = 0;
        for (; collection.hasNext(); ) {
            Salary salary = collection.next();
            sum += salary.getValue();
        }

    }

}

仲裁者模式(Mediator Pattern)

介紹

當多個對象之間均有交互請求,且交互邏輯較爲複雜時,可使用仲裁者模式造成對象間交互的中間層(仲裁者),用以解耦對象之間的顯式依賴。

缺點

顯然,解耦的依賴轉移到了仲裁者對象自己,使得當被仲裁對象發生變化時,仲裁者自己也必須響應該變化並進行修改。

示例

系統中存在顧客、餐廳員工,以及配餐員三個角色,其中,餐廳員工會通知配餐員送餐;配餐員會告知顧客餐正在配送中;而顧客也能夠向配送員或餐廳員工詢問餐的進展。對於這種多個對象之間存在多種耦合的行爲,能夠考慮採用仲裁者模式實現。

類圖

仲裁者模式

核心代碼

在仲裁者模式中,請求均經過仲裁者對象進行,從而對各個對象進行了解耦:

public class Mediator {

    private Patron patron = new Patron();
    private Deliverer deliverer = new Deliverer();
    private Staff staff = new Staff();

    public void askForDeliverer(){
        this.deliverer.receiveAsk();
    }

    public void askForStaff(){
        this.staff.receiveAsk();
    }

    public void sendConfirm(){
        this.patron.getConfirm();
    }

    public void requestToDeliver(){
        this.deliverer.receiveRequest();
    }
}

備忘錄模式(Memento Pattern)

介紹

在不破壞封裝性的狀況下,對對象的內部信息進行保存。

缺點

用於記錄內部信息快照的備忘錄對象自己是耗費資源的。

示例

如下演示對於訂單狀態的備忘保存實現。

類圖

備忘錄模式

核心代碼

OrderHistory 中負責記錄訂單的歷史信息,存儲的是 Memento 對象:

public class OrderHistory {

    private ArrayList<Memento> mementos = new ArrayList<>();

    public void store(Order order){
        Memento memento = order.createMemento();
        this.mementos.add(memento);
    }

    public void restore(Order order, int index){
        Memento memento = this.mementos.get(index);
        order.restore(memento);
    }

}

而訂單類負責產生備忘錄對象:

public class Order {

    private State state;

    public Memento createMemento(){
        Memento memento = new Memento();
        memento.setState(this.state);
        return memento;
    }

    public void restore(Memento memento){
        this.state = memento.getState();
    }
}

注意,不能把歷史信息直接存儲在 Order 類中,由於一旦 Order 類生命週期結束,歷史信息也會消失,這樣是不合理的。

觀察者模式(Observer Pattern)

介紹

觀察者模式是很是經常使用的一種設計模式。若是當某一事物發生改變時,這種行爲會觸發一系列的對象產生變化,此時就可使用觀察者模式來解耦這種一對多的對象關聯關係。

缺點

可能會致使觀察者的級聯更新。

示例

不一樣的用戶使用不一樣的方式(郵件、短信)註冊了某一通知服務,當有新通知到達時,系統會基於不一樣的註冊方式向註冊用戶發送通知。應對這一需求,可使用觀察者模式。

類圖

觀察者模式

核心代碼

public class Publisher {

    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer){
        this.observers.add(observer);
    }

    public void publish(Message message){
        Iterator<Observer> iterator = this.observers.iterator();
        while (iterator.hasNext()){
            Observer observer = iterator.next();
            observer.update(message);
        }
    }

    public void removeObserver(Observer observer){
        this.observers.remove(observer);
    }

}

狀態模式(State Pattern)

介紹

容許一個對象的具體行爲隨着內部狀態的改變而改變。

缺點

各個子狀態間存在先後繼的耦合關係。

示例

對一個訂單而言,其存在三種狀態:準備、完成、配送。當處於準備狀態時,訂單須要進行加工操做;當處於完成狀態時,須要執行通知以實現配送;當處於配送狀態時,須要實時發佈物流消息。對於這一需求,能夠經過狀態模式實現。

類圖

狀態模式

核心代碼

客戶端調用的方式爲:

public class Client {

    public void doSome(){
        // 初始化,進入準備狀態
        Order order = new Order();
        // 進入完成狀態
        order.action();
        // 進入配送狀態
        order.action();
        // 結束
    }

}

準備狀態代碼:

public class PrepareState implements State {
    @Override
    public void handle(Order order) {
        // 執行加工操做業務代碼
        // ...
        // 轉換狀態
        order.setState(new FinishedState());

    }
}

結束狀態代碼:

public class FinishedState implements State {
    @Override
    public void handle(Order order) {
        // 執行通知操做業務代碼
        // ...
        // 轉換狀態
        order.setState(new DeliveringState());
    }
}

配送狀態代碼:

public class DeliveringState implements State {
    @Override
    public void handle(Order order) {
        // 執行發佈操做業務代碼
        // ...
        // 轉換狀態
        order.setState(null);
    }
}

策略模式(Strategy Pattern)

介紹

業務的具體算法能夠在運行時確認和變更。即業務過程的策略能夠進行設置和調整。

缺點

調用方須要手動指定策略。引入策略會破壞封裝性。

示例

在要展現的列表數據處理邏輯中,對列表的排序操做存在多種策略,如按照姓名排序、按照年齡排序、按照薪資排序等。對於這種形式的需求,能夠考慮採用策略模式進行實現。

類圖

策略模式

核心代碼

在使用方,手動指定策略:

public class Client {

    public void doSome(){
        DataViewer dataViewer = new DataViewer();
        SortStrategy strategyA = new SortByNameStrategy();
        SortStrategy strategyB = new SortByAgeStrategy();
        SortStrategy strategyC = new SortBySalaryStrategy();
        dataViewer.setStrategy(strategyA);
        // or
        dataViewer.setStrategy(strategyB);
        // or
        dataViewer.setStrategy(strategyC);

        dataViewer.render();
    }

}

模板方法模式(Template Method Pattern)

介紹

將算法或邏輯的不變行爲抽離出來,而將可變部分放在子類中實現。

缺點

會產生較多的類。

示例

在結帳這一業務中,確認帳單、得到帳單這一過程是相同的,而選擇支付方式是不一樣的。咱們能夠把這一需求採用模板方法模式進行實現。

類圖

模板方法模式

核心代碼

在客戶端的調用方法以下:

public class Client {

    public void doSome(){
        PayOrder payOrderA = new PayOrderByCard();
        // or
        PayOrder payOrderB = new PayOrderByCash();
        payOrderA.check();
    }

}

PayOrder 中的實現爲:

abstract public class PayOrder {

    final public void check(){
        this.confirm();
        this.pay();
        this.getBill();
    }

    // 確認
    private void confirm(){

    }

    // 得到帳單
    private void getBill(){

    }

    // 子類中須要實現的方法
    abstract protected void pay();

}

訪問者模式(Visitor Pattern)

介紹

在不改變聚合對象元素行爲的狀況下,定義施加在聚合對象元素上的行爲。

缺點

聚合對象須要相對穩定。

示例

訂單分爲即時訂單和預購訂單兩類,當須要對訂單的進行檢查時,檢查行爲須要分別對這兩類進行操做。此時,能夠考慮使用訪問者模式。

類圖

訪問者模式

核心代碼

Manager 中,須要先設定訪問者類型,而後對擁有訂單的顧客 Patron 進行訂單檢查:

public class Manager {

    public void doSome(){
        Patron patron = new Patron();
        Visitor visitorA = new VisitorImplA();
        // or
        Visitor visitorB = new VisitorImplB();

        patron.setVisitor(visitorA);
        patron.check();
    }

}

Patron 的代碼爲:

public class Patron {

    private List<Order> orders = new ArrayList<>();

    private Visitor visitor;

    public void setVisitor(Visitor visitor) {
        this.visitor = visitor;
    }

    public void check(){
        Iterator<Order> iterator = this.orders.iterator();
        while (iterator.hasNext()){
            Order order = iterator.next();
            order.accept(this.visitor);
        }
    }

}

Visitor 實現類須要針對不一樣的 Element 進行對應的操做,代碼爲:

public class VisitorImplA implements Visitor {
    @Override
    public void visitOrder(Order order) {
        order.order();
    }

    @Override
    public void visitSubOrder(SubOrder subOrder) {
        subOrder.subOrder();
    }
}

參考

  1. 軟件設計模式 - 學堂在線

  2. design-patterns-for-humans - github

  3. 瞭解軟件設計模式概念 - 簡書

  4. 面向對象的七種設計原則 - 博客園

  5. 設計模式五大原則(SOLID) - CSDN

  6. GRASP通用職責分配軟件模式

相關文章
相關標籤/搜索