軟件開發的六大設計原則

概述

剛開始進行軟件開發的時候,只要會功能開發就好了,不太要考慮性能問題,項目的可讀性,可維護性,但隨着時間的推移,這些東西也是你面向高級工程師的必經之路,爲了提升項目的可維護性和可複用性,增長系統的可擴展性和靈活性,咱們要儘可能根據後面要將的六大設計原則來進行程序開發,從而提升軟件的開發效率,節約開發成本和維護成本,只有將這些原則爛熟於心,在日常的開發過程當中才能作到靈活應用java

原件開發過程當中的六大設計原則有編程

  • 單一職責原則
  • 里氏替換原則
  • 依賴倒置原則
  • 接口隔離原則
  • 迪米特法則
  • 開閉原則

下面咱們就具體看下這六大原則bash

六大設計原則(1):單一職責原則

單一職責原則 (Single Responsibility Principle - SRP)架構

There should never be more than one reason for a class to change, 致使一個類變化的緣由(事情)不能多於一個 ,通俗講就是一個類只負責一件事情或一項職責ide

問題由來:假如一個類T負責職責P1和P2 ,假如因爲p1職責發生變化修改類T時,此時可能會使本來正常的P2職責發生變化函數

解決方案:把原來的T類查分紅T1和T2 ,T1負責P1 ,T2負責P2 ,這樣當P1發生變化修改T1時,它自己對P2是隔離的,不會對P2形成任何影響性能

看一個例子 :一個學校的學生工做,分爲生活輔導和學習輔導,假如由一個老師所有負責的話有點過重了,正確的作法是生活輔導有生活老師進行輔導,學習輔導由學習老師進行輔導學習

單一職責原則一樣適用於方法,一個方法負責一項職責,若是一個方法負責的職責太多,那麼它的顆粒度就會變粗,不利用重用,各個職責間的耦合度也會增大測試

備註ui

  • 在實際的開發過程當中職責拆分的顆粒度可有由具體的業務場景進行拆分,沒有必要爲了拆分而拆分,特別簡單的功能也把顆粒度弄的特別細,只要總體符合單一職責原則就OK
  • 在實際的開發過程當中沒有什麼事一成不變的,隨着業務的變化有可能會發生職責擴散,所謂職責擴散,就是隨着業務變化P職責劃分爲P1和P2兩個職責。這時候就違背的單一職責原則,但此時是否是要進行拆分(感受均可以),但這樣作的風險在於職責擴散的不肯定性,由於咱們不會想到這個職責P,在將來可能會擴散爲P1,P2,P3,P4……Pn。因此記住,在職責擴散到咱們沒法控制的程度以前,馬上對代碼進行重構

六大設計原則(2):里氏替換原則

里氏替換原則(Liskov Substitution Principle - LSP)

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it 使用基類的指針或引用的函數,必須是在不知情的狀況下,可以使用派生類的對象 ,通俗講就是父類可以替換成子類,子類不能替換成父類 ,全部引用基類的地方必須能透明地使用其子類的對象

問題由來:有一個問題q有A的p1方法完成,如今A的子類B進行了功能擴展增長的P2方法,此時又B來處理問題q時可能會出錯 解決方案:當使用繼承時,遵循里氏替換原則。類B繼承類A時,除添加新的方法完成新增功能P2外,儘可能不要重寫父類A的方法,也儘可能不要重載父類A的方法

例子: 咱們須要一個功能兩個數相加有A類完成 ,後來,咱們須要增長一個新的功能:完成兩數相減再減1,而後再翻倍,由類B來負責

public void test(){
        int n1 = 10;
        int n2 = 2;
        A a1 = new A();
        Log.e(TAG, n1 +" - "+n2 + " = "+a1.fun1(n1,n2));
        A a2 = new B();
        Log.e(TAG, n1 +" - "+n2 + " = "+a2.fun1(n1,n2));
    }


    class A {
        //兩個數相減
        public int fun1(int a ,int b){
            return a-b;
        }

    }

    class B extends A {
        @Override
        public int fun1(int a, int b) {
            return super.fun1(a, b) -1;
        }

        //實現 (a-b-1)*2
        public int fun2(int a, int b){
            return fun1(a,b)*2;
        }
    }
複製代碼

此時咱們會發現本來運行正常的相減功能發生了錯誤,這是由於B是進行功能擴展的時候無心中重寫了父類的方法致使了本來運行正常的功能出現了錯誤,因此在子類進行功能擴展時須要遵循里氏替換原則

里氏替換原則通俗的來說就是:子類能夠擴展父類的功能,但不能改變父類原有的功能

六大設計原則(3):依賴倒置原則

依賴倒置原則 (Dependence Inversion Principle - DIP) High level modules should not depends upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. 高層模塊不該該依賴於低層模塊,它們應該依賴於抽象。抽象不該該依賴於細節,細節應該依賴於抽象

問題由來:類A直接依賴類B,假如要將類A改成依賴類C,則必須經過修改類A的代碼來達成。這種場景下,類A通常是高層模塊,負責複雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操做;假如修改類A,會給程序帶來沒必要要的風險。

解決方案:將類A修改成依賴接口I,類B和類C各自實現接口I,類A經過接口I間接與類B或者類C發生聯繫,則會大大下降修改類A的概率。

相對於細節的多變性,抽象的東西要穩定的多。以抽象爲基礎搭建起來的架構比以細節爲基礎搭建起來的架構要穩定的多。在java中,抽象指的是接口或者抽象類,細節就是具體的實現類,使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操做,把展示細節的任務交給他們的實現類去完成,依賴倒置原則的核心思想是面向接口編程

例子:媽媽給小朋友講書本上的故事

public void test(){
        Book book = new Book("貓和老鼠的故事.......");
        Mother mother = new Mother();
        mother.readStory(book);
    }


    class Mother {
        public void readStory(Book book){
            Log.e(TAG,"開始講故事了");
            Log.e(TAG,book.getContent());
        }
    }

    class Book {
        private String content;

        public Book(String content){
            this.content = content;
        }

        public String getContent(){
            return "讀物內容:"+content;
        }
    }
複製代碼

如上所示已經完成了媽媽講故事,可是若是哪一天媽媽想給小朋友讀報紙,你會發現媽媽幹不了,須要需改上面的內容,添加讀報紙的功能,這是就很是很差,假如日後還要添加讀手機內容的功能,豈不是要一直進行代碼修改,此時就用到了依賴倒置原則(面向接口編程),咱們設計也給IReader接口,讓書,報紙....都繼承這個接口

public void test(){
        IReader book = new Book("貓和老鼠的故事.......");
        IReader newPaper = new NewPaper("中美貿易戰有了最近進展.......");
        Mother mother = new Mother();
        mother.readStory(book);
        mother.readStory(newPaper);
    }


    class Mother {
        public void readStory(IReader reader){
            Log.e(TAG,"開始講故事了");
            Log.e(TAG,reader.getContent());
        }
    }

    interface IReader{
        abstract String getContent();
    }

    class NewPaper implements IReader{
        public String content;
        public NewPaper(String c){
            this.content = c;
        }

        @Override
        public String getContent() {
            return "報紙內容:"+content;
        }
    }

    class Book implements IReader{
        private String content;

        public Book(String content){
            this.content = content;
        }

        @Override
        public String getContent() {
            return "書本內容:"+content;
        }
    }
複製代碼

如上所示媽媽就完成了讀報紙的功能,且若是之後想讀手機,讀期刊,在不修改代碼的狀況下都能很便捷的擴展開發

六大設計原則(4):接口隔離原則

接口隔離原則 (Interface Segregation Principle - ISP)

The dependency of one class to another one should depend on the smallest possible interface.一個類對另外一個類的依賴應該創建在最小的接口上 ,通俗的說就是一個類不該該依賴它不須要的接口

如上圖所示類B只須要方法4和方法5兩個功能,類D只須要方法2,方法3兩個功能,但因爲接口顆粒度比較多,致使類B不能不現實方法1,方法2,方法3 三個無用的功能,類D不能不現實方法1,方法4,方法5 三個無用的功能

接口隔離原則主要指:創建單一接口,不要創建龐大臃腫的接口,儘可能細化接口,接口中的方法儘可能少。要爲各個類創建專用的接口,而又創建一個接口供全部類使用

六大設計原則(5):迪米特法則 (最少知識原則)

迪米特法則 (Law of Demeter)

Only talk to you immediate friends,只與你最直接的朋友交流 ,一個對象應該對其餘對象保持最少的瞭解

在開發過程當中最常提到的就是高內聚,低耦合,只有耦合度變低,代碼的複用率才能提升,這也是迪米特法則所要求的 迪米特法則又叫最少知道原則,也就是說對本身依賴的類知道越少越好,一個類只向外暴露必要的信息和方法,迪米特法則還有一個更簡單的定義:只與直接的朋友通訊 ,首先來解釋一下什麼是直接的朋友:每一個對象都會與其餘對象有耦合關係,只要兩個對象之間有耦合關係,咱們就說這兩個對象之間是朋友關係。耦合的方式不少,依賴、關聯、組合、聚合等。其中,咱們稱出現成員變量、方法參數、方法返回值中的類爲直接的朋友,而出如今局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要做爲局部變量的形式出如今類的內部

六大設計原則(6):開閉原則

開閉原則 (Open Closed Principle - OCP) Software entities like classes, modules and functions should be open for extension but closed for modifications.類、模塊與方法,對於擴展開放對於修改封閉

開閉原則是面向對象程序設計的終極目標,它使軟件實體擁有必定的適應性和靈活性的同時具有穩定性和延續性。具體來講,其做用以下。

  1. 對軟件測試的影響 軟件遵照開閉原則的話,軟件測試時只須要對擴展的代碼進行測試就能夠了,由於原有的測試代碼仍然可以正常運行。
  2. 能夠提升代碼的可複用性 粒度越小,被複用的可能性就越大;在面向對象的程序設計中,根據原子和抽象編程能夠提升代碼的可複用性。
  3. 能夠提升軟件的可維護性 遵照開閉原則的軟件,其穩定性高和延續性強,從而易於擴展和維護。

思考:java中三大特性之一的多態是否違反了里氏替換原則??

里氏替換原則的定義大體是:子類能夠擴展父類的功能,但不能改變父類原有的功能,就是進行不要重寫父類的方法。 多態的定義是:在父類中定義的屬性和方法被子類繼承以後,能夠具備不一樣的數據類型或表現出不一樣的行爲,這使得同一個屬性或方法在父類及其各個子類中具備不一樣的含義。 多態是讓子類去複寫父類的方法從而到達一樣的引用不一樣的行爲的效果,這貌似是和里氏替換原則是相悖的 其實里氏替換原則是針對繼承而言的,繼承是爲了實現代碼重用,也就是爲了共享方法,那麼共享的父類方法就應該保持不變,不能被子類從新定義。 而多態更多的是用在父類是接口或者抽象類的情形下,子類覆蓋並從新定義父類的方法。不一樣的子類有不一樣的實現,從而有不一樣的行爲 Ps:純屬我的理解,若是哪位大神感受個人理解有錯誤,歡迎指正

相關文章
相關標籤/搜索