RE|GoF的23種設計模式-1

1.設計模式的分類

整體來講設計模式分爲三大類:java

建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式算法

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式編程

行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式設計模式

2.設計模式簡介

單例(Singleton)模式:某個類只能生成一個實例,該類提供了一個全局訪問點供外部獲取該實例,其拓展是有限多例模式。架構

原型(Prototype)模式:將一個對象做爲原型,經過對其進行復制而克隆出多個和原型相似的新實例。框架

工廠方法(Factory Method)模式:定義一個用於建立產品的接口,由子類決定生產什麼產品。ide

抽象工廠(AbstractFactory)模式:提供一個建立產品族的接口,其每一個子類能夠生產一系列相關的產品。函數

建造者(Builder)模式:將一個複雜對象分解成多個相對簡單的部分,而後根據不一樣須要分別建立它們,最後構建成該複雜對象。學習

代理(Proxy)模式:爲某對象提供一種代理以控制對該對象的訪問。即客戶端經過代理間接地訪問該對象,從而限制、加強或修改該對象的一些特性。ui

適配器(Adapter)模式:將一個類的接口轉換成客戶但願的另一個接口,使得本來因爲接口不兼容而不能一塊兒工做的那些類能一塊兒工做。

橋接(Bridge)模式:將抽象與實現分離,使它們能夠獨立變化。它是用組合關係代替繼承關係來實現,從而下降了抽象和實現這兩個可變維度的耦合度。

裝飾(Decorator)模式:動態的給對象增長一些職責,即增長其額外的功能。

外觀(Facade)模式:爲多個複雜的子系統提供一個一致的接口,使這些子系統更加容易被訪問。

享元(Flyweight)模式:運用共享技術來有效地支持大量細粒度對象的複用。

組合(Composite)模式:將對象組合成樹狀層次結構,使用戶對單個對象和組合對象具備一致的訪問性。

模板方法(TemplateMethod)模式:定義一個操做中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類能夠不改變該算法結構的狀況下重定義該算法的某些特定步驟。

策略(Strategy)模式:定義了一系列算法,並將每一個算法封裝起來,使它們能夠相互替換,且算法的改變不會影響使用算法的客戶。

命令(Command)模式:將一個請求封裝爲一個對象,使發出請求的責任和執行請求的責任分割開。

職責鏈(Chain of Responsibility)模式:把請求從鏈中的一個對象傳到下一個對象,直到請求被響應爲止。經過這種方式去除對象之間的耦合。

狀態(State)模式:容許一個對象在其內部狀態發生改變時改變其行爲能力。

觀察者(Observer)模式:多個對象間存在一對多關係,當一個對象發生改變時,把這種改變通知給其餘多個對象,從而影響其餘對象的行爲。

中介者(Mediator)模式:定義一箇中介對象來簡化原有對象之間的交互關係,下降系統中對象間的耦合度,使原有對象之間沒必要相互瞭解。

迭代器(Iterator)模式:提供一種方法來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。

訪問者(Visitor)模式:在不改變集合元素的前提下,爲一個集合中的每一個元素提供多種訪問方式,即每一個元素有多個訪問者對象訪問。

備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取並保存一個對象的內部狀態,以便之後恢復它。

解釋器(Interpreter)模式:提供如何定義語言的文法,以及對語言句子的解釋方法,即解釋器。

3.設計模式的7大原則

1) 開閉原則

定義:軟件實體應當對擴展開放,對修改關閉,這就是開閉原則的經典定義。是指一個軟件實體如類、模塊和函數應該對 擴展開放,對修改關閉。所謂的開閉,也正是對擴展和修改兩個行爲的一個原則。強調的是用抽象構建框架,用實現擴展細節。能夠提升軟件系統的可複用性及可維護性。開閉原則,是面向對象設計中最基礎的設計原則。

舉個例子

咱們銷售手機,每一個手機都有它的屬性好比:類型、價格、名稱,定義了一個接口

public interface IPhone {
    String getType(); // 手機類型
    Double getPrice(); // 手機價錢
    String getName(); // 手機名稱
}
複製代碼

如今咱們使用蘋果手機對他進行實現

public static class ApplePhone implements IPhone {
    private String type;
    private String name;
    private Double price;

    ApplePhone(String type, Double price, String name) {
        this.type = type;
        this.price = price;
        this.name = name;
    }

    @Override
    public String getType() {
        return type;
    }

    @Override
    public Double getPrice() {
        return price;
    }

    @Override
    public String getName() {
        return name;
    }
}
複製代碼

如今咱們進行活動對手機進行打折售出

public static class ActivityApplePhone extends ApplePhone {

    ActivityApplePhone(String type, Double price, String name) {
        super(type, price, name);
    }

    @Override
    public Double getPrice() { // 違反了里氏替換原則
        return super.getPrice() * 0.8;
    }

    @Override
    public String getName() { // 違反了里氏替換原則
        return "[優惠~]" + super.getName();
    }

}
複製代碼

這樣咱們能夠不修改原來的代碼上擴展了活動中蘋果的類,還能夠經過實現 IPhone 類來添加更多類型的手機的屬性。

2) 里氏替換原則

定義:繼承必須確保超類所擁有的性質在子類中仍然成立。里氏替換原則主要闡述了有關繼承的一些原則,也就是何時應該使用繼承,何時不該該使用繼承,以及其中蘊含的原理。里氏替換原是繼承複用的基礎,它反映了基類與子類之間的關係,是對開閉原則的補充,是對實現抽象化的具體步驟的規範。

補充:里氏替換原則裏

  1. 子類能夠擴展父類的功能,但不能改變父類原有的功能。
  2. 子類能夠實現父類的抽象方法,但不能覆蓋父類的非抽象方法。
  3. 子類中能夠增長本身特有的方法。
  4. 當子類的方法重載父類的方法時,方法的前置條件(即方法的輸入/入參)要比父類方法的輸入參數更寬鬆。
  5. 當子類的方法實現父類的方法時(重寫/重載或實現抽象方法),方法的後置條件(即 方法的輸出/返回值)要比父類更嚴格或相等。

舉個例子:

仍是上面的例子,咱們從里氏替換原則出發現問題 子類能夠擴展父類的功能,但不能改變父類原有的功能,咱們的上面的例子很簡單發現違反了這個原則。

修改:

public static class ActivityApplePhone extends ApplePhone {

    ActivityApplePhone(String type, Double price, String name) {
        super(type, price, name);
    }

    public Double getActivityPrice() {
        return super.getPrice() * 0.8;
    }

    public String getActivityName() {
        return "[優惠~]" + super.getName();
    }

}

複製代碼

3) 依賴倒置原則

定義:要面向接口編程,不要面向實現編程。

因爲在軟件設計中,細節具備多變性,而抽象層則相對穩定,所以以抽象爲基礎搭建起來的架構要比以細節爲基礎搭建起來的架構要穩定得多。這裏的抽象指的是接口或者抽象類,而細節是指具體的實現類。使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操做,把展示細節的任務交給它們的實現類去完成。

舉個例子:

用戶購物,若是用戶想購買物品 C 則沒法實現,由於裏面沒有購買 物品 C 的動做。若是隨意改變代碼很容出現問題。

public static class User {

    public void shoppingGoodsA(){
        System.out.println("購買商品 -- A");
    }

    public void shoppingGoodsB() {
        System.out.println("購買商品 -- B");
    }

}

複製代碼

咱們改變一下,以下...

public static class User {
    public void shoppingGoods(IGoods goods){
        goods.shopping();
    }
}

interface IGoods {
    void shopping();
}

public static class GoodsA implements IGoods {

    @Override
    public void shopping() {
        System.out.println("購買商品 -- A");
    }
}

public static class GoodsB implements IGoods {

    @Override
    public void shopping() {
        System.out.println("購買商品 -- B");
    }
}

複製代碼

用戶不管怎麼購物均可以,知足用戶的需求,並且不須要對原來的代碼修改..

下面使用 構造注入 和 set 注入購買的商品

// 構造注入
public static class User {
    private IGoods goods;

    User(IGoods goods) { // 構造注入
        this.goods = goods;
    }

    public void shoppingGoods() {
        if (goods != null) goods.shopping();
    }
    
}

// 運行
User userA = new User(new GoodsA());
userA.shoppingGoods();

User userB = new User(new GoodsB());
userB.shoppingGoods();

複製代碼
// set 注入
public static class User {
    private IGoods goods;
    
    public void setGoods(IGoods goods) {
        this.goods = goods;
    }

    public void shoppingGoods() {
        if (goods != null) goods.shopping();
    }
    
}

// 運行
User u = new User();
u.setGoods(new GoodsA());
u.shoppingGoods();
u.setGoods(new GoodsB());
u.shoppingGoods();

複製代碼

4) 單一職責原則

定義:單一職責原則又稱單一功能原則,這裏的職責是指類變化的緣由,單一職責原則規定一個類應該有且僅有一個引發它變化的緣由,不然類應該被拆分。該原則提出對象不該該承擔太多職責,若是一個對象承擔了太多的職責,至少存在如下兩個缺點:[1] 一個職責的變化可能會削弱或者抑制這個類實現其餘職責的能力;[2] 當客戶端須要該對象的某一個職責時,不得不將其餘不須要的職責全都包含進來,從而形成冗餘代碼或代碼的浪費。

舉個例子

咱們購買商品或者退款商品,以及查看商品信息均可以屬於和商品的操做,可是根據單一職責原則應該分爲 商品信息 和 商品操做 兩個接口, 商品信息接口能夠擴展吧商品的類型 顏色 狀態 等描述清楚,而商品的操做類,只用關注商品的購買和退款兩個操做。

public interface IGoods {

    void getInfo();

    void purchase();

    void refund();

}

複製代碼

改變爲

public interface IGoodsInfo {

    void getInfo();

}

public interface IGoodsOperation {

    void purchase();

    void refund();

}

複製代碼

5) 接口隔離原則

定義: 客戶端不該該被迫依賴於它不使用的方法。該原則還有另一個定義:一個類對另外一個類的依賴應該創建在最小的接口上。以上兩個定義的含義是:要爲各個類創建它們須要的專用接口,而不要試圖去創建一個很龐大的接口供全部依賴它的類去調用。接口隔離原則和單一職責都是爲了提升類的內聚性、下降它們之間的耦合性,體現了封裝的思想,但二者是不一樣的: 單一職責原則注重的是職責,而接口隔離原則注重的是對接口依賴的隔離。單一職責原則主要是約束類,它針對的是程序中的實現和細節;接口隔離原則主要約束接口,主要針對抽象和程序總體框架的構建。

舉個例子

若是對購買/退貨商品分析,發現商品的購買的動做實際上是 庫存減小,銷售額增長。退貨同理~

public interface IGoodsOperation {

    void purchase();

    void refund();

}



interface IDepot {

    void increase();

    void reduce();
    
}

interface IMoney {

    void increase();

    void reduce();

}

複製代碼

6) 迪米特法則

定義:迪米特法則又叫做最少知識原則,若是兩個軟件實體無須直接通訊,那麼就不該當發生直接的相互調用,能夠經過第三方轉發該調用。其目的是下降類之間的耦合度,提升模塊的相對獨立性。迪米特法則中的「第三方」是指:當前對象自己、當前對象的成員對象、當前對象所建立的對象、當前對象的方法參數等,這些對象同當前對象存在關聯、聚合或組合關係,能夠直接訪問這些對象的方法。

舉個例子:

在購買完物品後去付款,你去掃碼支付的時候,掃碼支付關係的多少錢,而不是你買了什麼,因此對於支付來講物品他不關心。因此經過購物清單獲得購物的最後價格。

public static class GoodModel {
    private String name;
    private Double price;
    private Integer num;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }
}

public static class GoodsList {

    List<GoodModel> list = new ArrayList<>();

    Double getCountPrice () {
        double countPrice = 0.0;
        for (GoodModel goodModel : list) {
            countPrice += goodModel.getPrice() * goodModel.getNum();
        }
        return countPrice;
    }

}

public static class Payment {

    private GoodsList goodsList;

    public Payment() {
    }

    public Payment(GoodsList goodsList) {
        this.goodsList = goodsList;
    }


    public void run() {
        System.out.println("支付 --- " + goodsList.getCountPrice());
    }
}


複製代碼

7) 合成複用原則

定義:又叫組合/聚合複用原則。它要求在軟件複用時,要儘可能先使用組合或者聚合等關聯關係來實現,其次才考慮使用繼承關係來實現。若是要使用繼承關係,則必須嚴格遵循里氏替換原則。合成複用原則同里氏替換原則相輔相成的,二者都是開閉原則的具體實現規範。

舉個例子:

購買筆記本,如今有筆記本種類 得力和晨光 兩種。 每種裏面都有 規格A 規格B 規格C,若是是繼承者違背了 里氏替換原則。經過注入想要的類型,從而複用公共的部分。

public static abstract class Book {

    private BookType type;

    public Book(BookType type) {
        this.type = type;
    }

    public abstract String brand();

    public final void purchase() {
        System.out.println("購買--品牌:" + this.brand() + "類型:" + type.toType());
    }

}

public interface BookType {
    String toType();
}

public class BookTypeA implements BookType {

    @Override
    public String toType() {
        return "Type_A";
    }
}

public static class BookTypeB implements BookType {

    @Override
    public String toType() {
        return "Type_B";
    }
}

public static class BookTypeC implements BookType {

    @Override
    public String toType() {
        return "Type_C";
    }
}


public static class DeliBook extends Book {

    public DeliBook(BookType type) {
        super(type);
    }

    @Override
    public String brand() {
        return "Deli";
    }
}

public class ChenGuangBook extends Book {

    public ChenGuangBook(BookType type) {
        super(type);
    }

    @Override
    public String brand() {
        return "ChenGuang";
    }


}


複製代碼

設計模式總結

這 7 種設計原則是軟件設計模式必須儘可能遵循的原則,各類原則要求的側重點不一樣。其中,開閉原則是總綱,它告訴咱們要對擴展開放,對修改關閉;里氏替換原則告訴咱們不要破壞繼承體系;依賴倒置原則告訴咱們要面向接口編程;單一職責原則告訴咱們實現類要職責單一;接口隔離原則告訴咱們在設計接口的時候要精簡單一;迪米特法則告訴咱們要下降耦合度;合成複用原則告訴咱們要優先使用組合或者聚合關係複用,少用繼承關係複用。

學習設計原則,學習設計模式的基礎。在實際開發過程當中,並非必定要求全部代碼都遵循設計原則,咱們要考慮人力、時間、成本、質量,不是刻意追求完美,要在適當的場景遵循設計原則,體現的是一種平衡取捨,幫助咱們設計出更加優雅的代碼結構。

相關文章
相關標籤/搜索