設計模式(06)——設計原則(1)

設計原則

設計原則,是設計模式的內功心法,基本全部的設計模式都是基於設計原則進行的具體化,若是說設計模式是如何操做的話,那麼設計原則就是爲什麼這麼作的基石,所以,只要咱們能充分理解設計原則,那麼在此基礎上,對設計模式就能更好的理解,甚至能本身設計出一種設計模式來。
java

單一職責原則

定義

一個類或模塊,只須要完成一個範圍的功能,而不要搞得大而全。
程序員

場景

例如咱們設計一個社交網站,如今要存儲用戶信息,類設計以下:web

public class UserInfo {
    private String name;
    private String like;

    private String location;
}

如今,咱們想一想該類的設計是否符合單一職責原則?


答案是可能符合,也可能不符合。那麼判斷依據是什麼呢?


緣由就是類或模塊職責的判斷是根據業務來定的,並無一個廣泛認同的規則。例如,若是該須要要的網站還提供賣東西的功能,那麼用戶的地址信息,就是一個十分關鍵的信息,且該塊功能就須要抽離出來做爲一個單獨的模塊,此時,地址信息放在這裏就不合適了,違反了單一職責原則。


但若是地址信息,只是一個值對象,也就是說其只是一個展現屬性,那麼放在這裏就是合適的。


綜上所述,能夠看到單一職責原則並非設計出來就一成不變的,其須要結合業務發展的具體狀況來判斷。所以咱們在設計之初,能夠考慮一個大而全的類,但隨着業務的發展須要,須要持續不斷的進行優化(也就是持續重構的思想)。
算法

用處

單一職責,由於類的設計比較小而精,所以能夠極大提升代碼的可維護性和可讀性。


此外由於每一個類或模塊只涉及本身的功能部分,所以,也作到了高內聚。
編程

其餘

但類的設計也不是越單一越好,由於若是拆分的過細的話,可能上層一個類須要修改,會致使下層全部依賴其的類都要修改,又影響了代碼的可維護性,所以仍是要根據業務須要來合理評估,重點是感受要對。
設計模式

開閉原則

定義

字面意思,一個類的設計,應該要對拓展開放,對修改關閉。所以這裏的重點就是如下定義該如何判斷:api

  • 什麼樣的代碼修改是拓展;
  • 什麼樣的代碼修改是修改;
  • 修改代碼就必定是違反了該原則嗎

場景

public static void main(String[] args) {
        Demo demo = new Demo();
        demo.consume(1);
    }

    // 根據傳遞過來的級別來進行不一樣的會員邏輯判斷
    public void consume(int type) {
        if (type == 1) {
            Console.log("您好,1級會員!");
        }
        if (type == 2) {
            Console.log("您好,2級會員!");
        }
    }

如今,又提出一個新的需求,還須要根據對應的會員等級進行對應的金額扣除,若是是上述的設計方式,那麼修改的方式則是下面這樣:tomcat

public void consume(int type, int price) {
        if (type == 1) {
            Console.log("您好,1級會員,扣除金額{}", price);
        }
        if (type == 2) {
            Console.log("您好,2級會員,扣除金額{}", price);
        }
    }

很明顯,這樣的方式有問題,若是還要再傳遞一個字段,例如優惠比例,那麼依照該方案,則還須要修改接口定義,這就意味着調用方都須要修改,測試用例也須要對應的修改。框架

那麼若是按照開閉原則的話,該如何設計呢?


首先咱們對代碼進行下重構ide

// 將全部相關屬性封裝起來
public class Vip {
    private int type;
    private int price;
    private int radio;
}

針對每種處理方式,根據他們的公有行爲抽象出一個抽象層:

public interface VipHandler {
    void consume(Vip vip);
}


每種特殊處理方式實現對應的抽象:

public class FirstVipHandler implements VipHandler {
    @Override
    public void consume(Vip vip) {
        if (vip.getType() == 1) {
            Console.log("您好,1級會員,扣除金額{}", vip.getPrice() * vip.getRadio());
        }
    }
}

public class SecondVipHandler implements VipHandler {
    @Override
    public void consume(Vip vip) {
        if (vip.getType() == 2) {
            Console.log("您好,2級會員,扣除金額{}", vip.getPrice() * vip.getRadio());
        }
    }
}


經過這樣的處理方式,在每次接到新的任務後,就不須要從新修改原有的邏輯方法,能夠直接進行拓展便可:

// 根據傳遞過來的級別來進行不一樣的會員邏輯判斷
    public void consume(Vip vip, VipHandler vipHandler) {
        vipHandler.consume(vip);
    }

其餘

能夠看到即便是上述的方式來拓展代碼,仍舊會修改原有代碼,那麼這種方式是違反了開閉原則嗎?


在這裏,咱們判斷其並符合了開閉原則,由於咱們判斷是修改仍是拓展,並不能只是簡單的根據看是否修改了原有代碼,真正核心的關鍵問題應該是:

  • 改動沒有破壞原有代碼的正常運行;
  • 改動沒有破壞原有單元測試

**
在上述的修改後,咱們若是加一種特殊的狀況,並無修改到原先的處理邏輯類,這也就意味着原先的代碼不會引入一些可能的 bug,針對原始代碼的測試用例也仍是能夠照常的進行編寫,而不用再根據新的改動而進行改動。

用途

開閉原則的關鍵點是代碼的可拓展性,即如何快速的擁抱變化,當每次新的任務來後,沒必要修改原始代碼,而直接在原有的基礎上進行拓展便可。


關閉修改是保持原有代碼的穩定性

里氏替換原則

定義

子類對象能夠代替父類對象出現的任何地方,並保證原來程序邏輯行爲不被破壞。

由於要保證子類對象不能破壞原有程序邏輯行爲,所以該方式跟多態的區別是:
若是子類進行了重寫,並在重寫的邏輯中加入了跟父類對應方法不一樣的邏輯,那麼該方式能夠稱之爲多態,但就不符合里氏替換原則了。

用途

該原則最重要的做用是指導子類的設計,保證在替換父類的時候,不改變原有程序的邏輯行爲。


在這裏,重點是邏輯行爲的不改變,這就意味着,咱們能夠對實現的細節進行修改,只要保證業務含義不變,那麼就是符合里氏替換原則的。


所以,針對這種狀況,有一種用途是能夠 改進 原有的實現,例如原先採用的排序算法比較低效,那麼能夠設計一個子類,而後重寫對應排序算法,保證邏輯不發生變化。重點是,咱們作的是改進,無論如何改,排序的業務含義是不變的。


實現方式,則是按照協議進行編程,關鍵是子類重寫過程當中,要保證不破壞原有函數聲明的輸入、輸出、異常以及註釋中羅列的任何特殊狀況聲明。


接口隔離原則

定義

首先,咱們要對接口進行定義,明確其特殊含義,對於接口來講,咱們將其分爲兩種類型的表現形式。

  • 一種語法定義,其表明了一組方法的集合;
  • 向外暴露的單個API接口或函數實現


下面,咱們分別對其進行介紹。

場景:一組API聚合

public interface UserInfoService {
    boolean login();
    void getUserInfoByPhone();
    
    void deleteUserByPhone();
}

看上述這個接口定義是否符合接口隔離原則???


其實這跟單一職責原則同樣,也是要看業務發展的。例如,若是該接口是提供給後臺管理系統來使用的,那麼沒有問題,做爲一個後臺系統的 admin 權限人員固然能夠有不少操做的能力。


但若是該接口是給第三方用戶來使用的話,就不是很合適了。由於刪除操做是一個高權限能力,做爲用戶來講,通常是沒有權限作的,那麼在設計時,對應實現類就不該該實現所有接口內定義的方法,這就是接口隔離原則中所說的,不強制依賴接口中的全部方法。
**
而是,根據具體的定義,將其進行拆分,對應權限的實現類實現對應的權限行爲。

場景:單個API接口或函數實現

public class Statistics {
    private int max;
    private int min;
    private int sum;
    //......

    public Statistics count(List<Integer> data){
        Statistics statistics = new Statistics();
        // 計算 Statistics 中的每一個值
        return statistics;
    }
}

首先,咱們來看上述方法定義是否符合接口隔離原則???


在這裏,咱們仍是要結合具體的業務場景才能作出結論,若是使用該函數的調用者,在大部分場景下都須要用到其中的大部分字段,那麼該設計就是能夠的。


可是若是每次只用到其中的幾個,那麼該設計就不合理了,其會浪費大量的無效計算能力,影響性能。在該場景下,就須要進行拆分。

public int max(List<Integer> data) {
        return data.stream().max(Statistics::compare).get();
    }

    public int min(List<Integer> data) {
        return data.stream().min(Statistics::compare).get();
    }

依賴翻轉原則

對於依賴翻轉原則來講,有不少看着很像的定義,咱們分別對其進行介紹,看看其都是什麼含義,他們之間又有什麼關聯。

控制翻轉(IOC)

控制翻轉,針對是在原有的程序設計流程中,整個程序的運行流程是直接交由程序員來控制的,可是若是使用控制翻轉的思想,則是在一個架子中,已經定義好了執行的流程,而只是預先定義好了拓展點,後續程序員所能修改的只有拓展點,開發人員在拓展點裏添加相關業務邏輯便可。

public abstract class VipProcess {
    public abstract boolean isVip();

    public void consume() {
        if (isVip()) {
            Console.log("vip hello");
        } else {
            Console.log("get out");
        }
    }
}


public class Vip extends VipProcess {
    @Override
    public boolean isVip() {
        return true;
    }

    public static void main(String[] args) {
        VipProcess vip = new Vip();
        vip.consume();
    }
}


public class CommonPeople extends VipProcess {
    @Override
    public boolean isVip() {
        return false;
    }

    public static void main(String[] args) {
        VipProcess vip = new CommonPeople();
        vip.consume();
    }
}

上述方式,就是經過模板方法來實現的控制翻轉,提供一個拓展的 isVip() 邏輯來交給程序員來實現,而框架根據實際實現的方法返回來決定下面的程序流轉。

依賴注入(DI)

依賴注入更好理解,一句話歸納:不用程序員顯式經過 new 來建立對象,而是經過構造函數,函數傳遞的方式來傳遞對象。


即A類若是須要依賴B類,不是經過在 A 中 new 一個 B 出來,而是在外面建立好 B 後,傳遞給 A。經過這樣的方式,能夠在需求改變中,靈活的替換傳遞參數(B 實現 C 接口的話)。


而更進一步,如今一些框架都提供了 DI 的功能,只須要簡單的配置一下相關類對象,所需參數等,框架就會自動接管對象的建立流程已經生命週期等。(AutoWired)

依賴翻轉(DIP)

定義:高層模塊不依賴於底層模塊,而是經過一個抽象層來解耦。 該定義其實在咱們的日常業務開發中,不怎麼會用到,由於咱們日常就是高層依賴着底層,例如 Controller 依賴 Service,Service 依賴 Repository,該原則的重點仍是指導框架層面的開發。 例如 Tomcat ,咱們知道 Tomcat 的運行是咱們將程序寫完後,打成 war 包扔到對應目錄就能夠啓動了,而 Tomcat 和應用程序就是經過一個共同的抽象 **Servlet **來關聯的。 Tomcat 不直接依賴於底層實現:Web 程序,而是跟 Web 都依賴於 Servlet,而 Servlet 不依賴於具體的Tomcat 實現的 Web 的具體細節。 經過該實現方式,在編碼中,能夠靈活的進行替換,好比咱們仍是一個 web 程序,可是運行的容器不使用 tomcat 了,也能夠無縫的進行切換,只要保證要替換的容器,仍是依賴於 Servlet 規範便可。

相關文章
相關標籤/搜索