設計模式6大原則
第一:單一職責原則(SPR)
先來看一個場景,一個類中包含兩個職責T1和T2,當由於職責T1的需求需要修改類時,很有可能會影響正在執行的職責T2。因此得出單一職責的概念,即一個類應該有且僅有一個原因導致該類的變更,換句話說就時一個類應該只負責一項職責,這個類的變更智能是由這一項職責引起的。
單一職責告訴我們,一個類不能太“累”!當一個類(包括模塊和方法)承擔的責任越多時,它被復用的可能性越小;而且一個類承擔的職責過多就相當於把這些職責都耦合在一起了,一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力,因此這種耦合是非常不利的。我們需要做的就是把職責分離,把不同的職責封裝到不同的類中。
舉個例子來說:
/* * 這個接口很明顯的有兩個職責,即連接管理和數據傳送 */ public interface Modem { public void dial(String pno); public void hangup(); public void send(char c); public void recv(); }
當連接管理或者數據傳送發生變化時都會引起這個類發生變化,因此上述接口就違背了單一職責原則,應該把接口拆分如下:
* * 負責連接管理的接口 */ public interface Connenction { public void dial(String pno); publicvoid hangup(); }
/* * 負責數據傳送的接口 */ public interface DataChannel { public void send(char c); public void recv();
現在來總結一下,SRP的優點是消除耦合,減小因需求的變化引起代碼僵化的問題。SRP強調的就是根據職責來衡量接口或者類,但是往往我們對於職責的定義時不明確的,因此我們要因項目而異。一個合理的類中盡量做到僅由一個原因引起它的變化;但是對於接口一定要做到單一職責;在需求發生實際變化時應該使用SRP等原則來重構代碼。
第二:裏氏替換原則(LSP)
裏氏替換原則的概念是所有引用基類的地方必須能透明的使用其子類的對象,即只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,因此在程序中盡量使用基類類型對對象進行定義,而在運行時再確定其子類類型,用子類對象替代父類類型;但是有子類出現的地方,父類未必能適應。
在使用LSP時,要註意以下:
(1)子類必須完全實現父類的方法,在程序中使用父類來進行定義對象;如果一個方法只存在子類中,則父類對象時無法使用該方法的。如果子類不能完整的實現父類的方法,則要考慮斷開繼承關系,采用依賴聚集組合等關系代替繼承。
(2)在用LSP時,盡量把父類設計成接口或者抽象類,讓子類繼承父類或者實現父類的接口,並實現在父類中聲明的方法,運行時子類實例代替父類實例,要進行系統擴展時,不需要修改以前子類的代碼,只要再增加一個新的子類來實現即可。
(3)子類在覆蓋或者實現父類的方法時輸入參數可以被放大。換句話說如果你想讓子類的方法運行,則必須重寫父類的方法,也可以重載這個方法,但是前提是子類的參數範圍要大於父類的參數範圍。
(4)重寫或實現父類的方法時輸出結果類型要小於等於父類的輸出結果。
Father.java
public class Father { public void show(int i){ System.out.println(i); } public Object show1(){ return "父類"; } public void show2(HashMap map){ System.out.println("父類"); }
Son.java
public class Son extends Father{ public void show(int i){ System.out.println("子類重寫了父類的show"); System.out.println(i); } public String show1(){ return "11"; } public void show2(Map map){ System.out.println("子類"); } }
main1.java
/* * 裏氏替換原則LSP * 關於重寫(override)和重載(overload) * 重寫即子類重新寫父類的方法,方法的名字和列表都是完全一致的。 * 重載一般是發生在一個類中的,但是子類在繼承父類的時候也會發生重載,但是為了避免子類在沒有復寫父類的情況下就能被執行, * 即子類重載的這個方法不能被執行,所以在發生重載時務必要保證子類的參數的範圍大於父類的參數範圍 * 子類重寫父類方法,返回值可以不一樣,但是要保證父類的返回值得類型是子類的返回值類型的父類,重載也一樣 * 必須保證父類的返回值類型是子類的返回值類型父類。 */ public class main1 { public static void main(String[] args){ HashMap map=new HashMap<>(); Father f=new Father(); //f.show(1); System.out.println(f.show1()); //f.show2(map); Son s=new Son(); //s.show(1); System.out.println(s.show1()); //s.show2(map); } }
介紹一個裏氏替換的例子:
客戶(customer)可以分為VIP客戶(VIPCustomer)和普通客戶(CommonCustomer)兩類,系統需要提供一個發送郵件的功能,初始設計如下:
我們可以看到兩個send發送的過程都是一樣的,說明這個代碼是重復的,而且如果再增加新的類型的客戶,則要再增加一個一樣的send方法。為了讓系統有更好的擴展性,使用裏氏替換原則進行重構。修改方案是新增加一個抽象客戶類Customer,讓VIP客戶和普通客戶作為其子類,設計如下:
裏氏替換原則能夠保證系統具有良好的擴展性,同時實現基於多態的抽象機制。子類必須滿足基類和客戶端對其行為的約定。
第三:依賴倒置原則(DIP)
依賴倒置原則指的是(1)模塊之間的依賴是通過抽象發生的,實現類之間不能直接的依賴關系,實現類的依賴關系是通過接口或者抽象類產生的;(2)接口或者抽象類不依賴於實現類(3)實現類要依賴接口或者抽象類。或者更加精簡的定義是依賴倒置原則是面向接口編程。
依賴倒轉原則要求我們在程序代碼中傳遞參數時或者在關聯關系中,盡量引用層次高的抽象類,即用接口和抽象類進行變量類型的聲明、參數類型聲明、方法返回類型聲明以及數據類型的轉換等,而不使用具體的類來做這些事情。在引入抽象以後,系統具有很好的靈活性,在程序中盡量使用抽象層進行編程,當系統行為發生變化時,只需要對抽象層進行擴展即可,不需要修改原有系統的代碼。
依賴倒置的本質就是通過抽象使各個類或模塊的實現彼此獨立,實現模塊之間的松耦合。在使用依賴倒置原則時要註意(1)每個類盡量都有接口或抽象類,因為有了抽象才能依賴倒置。(2)變量的表面類型盡量是接口或者是抽象類。註意的是工具類一般是不需要接口或者抽象類的。(3)任何類都盡量的不能從具體類派生(4)盡量不要重寫父類的方法,如果基類是一個抽象類,而且這個方法已經實現了,子類盡量的不要重寫。
在實現依賴倒置原則時,我們需要對抽象層編程,而將具體類的對象通過依賴註入的方式註入到其他對象中。依賴註入指的是一個對象與其他對象發生依賴關系時,通過抽象來註入依賴對象。依賴註入有三種方式,包括構造函數註入、設置(setter)註入和接口註入,常用的就是前兩種。
(1)構造註入,即通過構造函數來傳入具體類的對象。如下:
public class UserRegister { private UserDao userDao =null;//由容器通過構造函數註入的實例對象 publicUserRegister(UserDao userDao){ this.userDao =userDao; } }
(2)Setter設置註入,即在抽象中設置Setter方法聲明依賴關系,如下:
public interface InjectUserDao { public void setUserDao(UserDao userDao); } public class UserRegister implementsInjectUserDao{ private UserDao userDao = null;//該對象實例由容器註入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
(3)接口註入,即在接口的方法中聲明依賴對象,如上面的InjectUserDao就屬於接口註入。
第四:接口隔離原則(ISP)
接口隔離原則指的是使用多個專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。接口要盡量的細化,同時接口中的方法盡量少。一般而言,接口中僅包含為某一類用戶定制的方法即可。
第五:迪米特法則(LoD)
迪米特法則指的是一個軟件實體應當盡可能少的與其他實體發生相互作用。迪米特法則可以降低系統的耦合度,使類與類之間保持松耦的耦合關系。即只和朋友交流。對於一個對象,它的朋友包括當前對象本身、當前對象的成員對象、如果當前對象的成員對象是一個集合那麽集合中的元素也是朋友、當前對象所創建的對象,出現在方法體內的類不屬於朋友類,類與類之間的關系是建立在類間的,而不是方法間。
第六:開閉原則
一個軟件實體應當對擴展開閉,對修改關閉。即實體軟件應盡量在不修改原有代碼的情況下進行擴展。抽象化是開閉原則的關鍵。開閉原則是最基礎的一個原則,對於其他五個原則來說,開閉原則屬於抽象類,其他五個原則屬於實現類。
如何使用開閉原則:(1)通過接口或者抽象類約束擴展,在抽象中的方法應該都是public的;參數類型引用對象應盡量的使用接口或者抽象類而不是具體的類;一旦抽象層確定後就盡量的保持穩定,不要修改(2)不應該有兩個不同的變化出現在同一個接口或者抽象類中。
類必須做到低內聚高耦合。
設計模式6大原則