設計模式之一:六大設計原則

這裏寫圖片描述

這裏有篇文章關於各個原則結合代碼進行較爲清晰的闡述:http://www.uml.org.cn/sjms/201211023.asp#4

個人覺得其實六大原則中,單一職責原則、開放-封閉原則其實是模塊化編程時代的思想順延,這兩個原則顯然是經過經驗積累得到的代碼可維護性的保證。而關於面向對象編程的三個重要思想則是面向接口編程(中間抽象層)、合成/聚合原則(減少不必要的強耦合)以及迪米特原則(增加中間代理對象以減少模塊間的耦合)。下面進行單獨介紹。

核心原則之一:依賴倒轉原則
這裏寫圖片描述

問題由來:客戶端client直接依賴類A,假如client要改爲依賴類B,則必須通過修改client的代碼來達成。類A和類B是低層模塊,負責基本的原子操作; client一般負責具體的業務邏輯,本就負擔繁重,如果底層不能保持統一,任何一次底層模塊變動都要驚擾client,這顯然是不合適的。

解決方案:添加中間接口層I,將client修改爲依賴接口I,類A和類B各自實現接口I,這樣client通過接口I間接與類A或者類B發生聯繫,則會大大降低直接修改client的機率。

核心原則之二:迪米特原則
“高內聚,低耦合”可以說是現在模式設計中的無上心法,而類與類之間彼此要了解的越多,則兩者間的耦合關係越強,這樣其中一方發生變化,必然會對另一方造成不必要的衝擊影響。所以類與類之間的瞭解在滿足需求的前提下,所需知識應該儘可能地少。

迪米特法則還有一個更簡單的定義:只與直接的朋友通信。首先來解釋一下什麼是直接的朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係,我們就說這兩個對象之間是朋友關係。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們稱出現成員變量、方法參數、方法返回值中的類爲直接的朋友,而出現在局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要作爲局部變量的形式出現在類的內部。

給出一段Java的示例代碼進行分析

//總公司員工
class Employee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

//分公司員工
class SubEmployee{
    private String id;
    public void setId(String id){
        this.id = id;
    }
    public String getId(){
        return id;
    }
}

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //爲分公司人員按順序分配一個ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }
}

class CompanyManager{

    public List<Employee> getAllEmployee(){
        List<Employee> list = new ArrayList<Employee>();
        for(int i=0; i<30; i++){
            Employee emp = new Employee();
            //爲總公司人員按順序分配一個ID
            emp.setId("總公司"+i);
            list.add(emp);
        }
        return list;
    }

    public void printAllEmployee(SubCompanyManager sub){
        List<SubEmployee> list1 = sub.getAllEmployee();
         //作爲總經理,其實不應該知道分佈員工的信息的,甚至連它們被叫做SubEmployee這個名稱都不應該知道
        for(SubEmployee e:list1){
            System.out.println(e.getId());
        }

        List<Employee> list2 = this.getAllEmployee();
        for(Employee e:list2){
            System.out.println(e.getId());
        }
    }
}

public class Client{
    public static void main(String[] args){
        CompanyManager e = new CompanyManager();
        e.printAllEmployee(new SubCompanyManager());
    }
} 

所以改進的邏輯應該如下
這裏寫圖片描述

即應該講分佈員工的信息枚舉功能交給分公司經理來完成,在分公司經理的類中添加一個枚舉函數。

class SubCompanyManager{
    public List<SubEmployee> getAllEmployee(){
        List<SubEmployee> list = new ArrayList<SubEmployee>();
        for(int i=0; i<100; i++){
            SubEmployee emp = new SubEmployee();
            //爲分公司人員按順序分配一個ID
            emp.setId("分公司"+i);
            list.add(emp);
        }
        return list;
    }

    public void printEmployee(){
        List<SubEmployee> list = this.getAllEmployee();
        for(SubEmployee e:list){
            System.out.println(e.getId());
        }
    }
}

/*改寫了權限之後,這樣總經理的函數操作將會簡單並且輕鬆很多*/
class CompanyManager{
    public List<Employee> getAllEmployee() {...}

    public void printAllEmployee(SubCompanyManager sub) {
        sub.printEmployee();
        //to-do ...
    }
}

當然這裏要注意的迪米特原則又可以叫做中間人代理,但是這裏的代理是順手之舉,如這裏的分公司經理枚舉分公司的員工名單,這是順手做的,而不能爲了代理專門設置一個專門的代理類,這是代理模式,但是不應該過分使用,否則會導致存在大量的中介傳遞類,這種頻繁創建對象用於傳遞信息,無疑也會導致信息傳遞鏈條過長。

核心原則之三:合成/聚合原則
在面向對象設計中,合成/聚合關係的耦合強度是明顯低於繼承關係的,根據”高內聚,低耦合“心法,顯然不能盲目使用繼承關係,這會導致UML繼承樹深度和規模急速擴展,故而應該儘可能選擇低耦合的關係,如依賴、關聯、組合、聚合、委託等,其中合成/聚合原則(CARP:Composition and Aggregation Reuse Principle)。
單一採用繼承關係設計的UML圖如下


採用CARP原則之後,得到的UML圖如下

採用CARP原則之後,得到的UML圖如下
兩者對比可以明顯發現CARP原則的好處。

除了上述六大原則,還有一些其他領域的原則也可以拿來借鑑,如最小規模接口原則。
最小接口原則定義:客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。

問題由來:類A通過接口I依賴類B,類C通過接口I依賴類D,如果接口I對於類A和類B來說不是最小接口,則類B和類D必須去實現他們不需要的方法。

解決方案:將臃腫的接口I拆分爲獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關係。也就是採用接口分割最小化原則。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章