1.設計模式的目的
設計模式是爲了更好的代碼重用性,可讀性,可靠性,可維護性。java
2.經常使用的六大設計模式
1)單一職責原則
2)里氏替換原則
3)依賴倒轉原則
4)接口隔離原則
5)迪米特法則
6)開閉原則編程
3.單一職責原則
該原則是針對類來講的,即一個類應該只負責一項職責。
如類T負責兩個不一樣職責:職責P1,職責P2。當職責P1需求變動而改變T時,可能形成職責P2發生故障,因此須要將類T的粒度分解爲T1,T2。
示例以下:
用一個類秒數動物呼吸這個場景設計模式
class Animal { public void breathe(string animal) { Console.WriteLine(animal+"呼吸空氣"); } } class Program { static void Main(string[] args) { Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("豬"); animal.breathe("魚"); Console.ReadLine(); } }
輸出結果:架構
咱們發現不是全部動物都是呼吸空氣的,好比魚就是呼吸水的,根據單一職責原則,咱們將Animal類細分爲陸生動物類和水生動物類,以下所示:框架
class Terrestrial { public void breathe(string animal) { Console.WriteLine(animal+"呼吸空氣"); } } class Aquatic { public void breathe(string animal) { Console.WriteLine(animal + "呼吸水"); } } class Program { static void Main(string[] args) { Terrestrial terrestrial = new Terrestrial(); terrestrial.breathe("牛"); terrestrial.breathe("羊"); terrestrial.breathe("豬"); Aquatic aquatic = new Aquatic(); aquatic.breathe("魚"); Console.ReadLine(); } }
咱們發現這樣修改的花銷很大,既要將原來的類分解,又要修改客戶端。而直接修改Animal類雖然違背了單一職責原則,但花銷小的多,以下所示:函數
class Animal { public void breathe(string animal) { if ("魚".Equals(animal)) { Console.WriteLine(animal + "呼吸水"); } else { Console.WriteLine(animal + "呼吸空氣"); } } } class Program { static void Main(string[] args) { Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("豬"); animal.breathe("魚"); Console.ReadLine(); } }
能夠看到,這種修改方式簡單的多。但卻存在隱患,一天須要將魚分爲淡水魚,海水魚,又須要修改Animal類的breathe方法。可能給「豬牛羊」等相關功能帶來風險,這種修改直接在代碼級別違背了單一職責原則,雖然修改起來最簡單,但隱患最大。還有一種修改方式:this
class Animal { public void breathe(string animal) { Console.WriteLine(animal + "呼吸空氣"); } public void breathe2(string animal) { Console.WriteLine(animal + "呼吸水"); } } class Program { static void Main(string[] args) { Animal animal = new Animal(); animal.breathe("牛"); animal.breathe("羊"); animal.breathe("豬"); animal.breathe2("魚"); Console.ReadLine(); } }
這種修改方式沒有改動原來的方法,而是在類中新加了一個方法,這樣雖然違背了單一職責原則,但在方法級別上倒是符合單一職責原則的。那麼在實際編程中,採用哪種呢?個人原則是,只有邏輯足夠簡單,才能夠在代碼級違反單一職責原則;只有類中方法數量足夠少,才能夠在方法級別違反單一職責原則。編碼
遵循單一職責的優勢:
1)下降類的複雜度,一個類只負責一項職責。
2)提升類的可讀性,可維護性
3)下降變動引發的風險。spa
4.里氏替換原則
該原則是在1988年,由麻省理工學院的覺得姓裏的女士提出的。
若是對每一個類型爲T1的對象o1,都有類型爲T2的對象o2,使得以T1定義的全部程序P在全部的對象o1都代換成o2時,程序P的行爲沒有發生變化,那麼類型T2是類型T1的子類型。
換句話說,全部引用基類的地方必須能透明地使用其子類的對象。設計
由定義可知,在使用繼承時,遵循里氏替換原則,在子類中儘可能不要重寫和重載父類的方法。
繼承包含這樣一層含義:父類中凡是已經實現好的方法(相對抽象方法而言),其實是在設定一系列的規範和契約,雖然它不強制要求全部的子類必須遵循這些契約,可是若是子類對這些非抽象方法任意修改,就會對整個繼承體系形成破壞。而里氏替換原則就是表達了這一層含義。
繼承做爲面向對象三大特性之一,在給程序設計帶來巨大遍歷的同時,也帶來了弊端。好比使用繼承會給程序帶來侵入性,程序的可移植性下降,增長對象間的耦合性,若是一個類被其餘的類所繼承,則當這個類須要修改時,必須考慮到全部的子類,而且父類修改後,全部涉及到子類的功能都有可能產生故障。
舉例說明繼承的風險,咱們須要完成一個兩數相減的功能,由類A來負責。
class A{ public int func1(int a,int b){ return a-b; } } public class Client{ public static void main(string[] args){ A a=new A(); System.out.println("100-50="+a.func1(100,50)); System.out.println("100-80="+a.func1(100,80)); } }
運行結果:
100-50=50
100-80=20
後來,咱們須要增長一個新的功能:完成兩數相加,而後再與100求和,由類B來負責。
Class B extends A{ public int func1(int a,int b){ return a+b; } public int func2(int a,int b){ return func1(a,b)+100; } } public class Client{ public static void main(string[] args){ B a=new B(); System.out.println("100-50="+b.func1(100,50)); System.out.println("100-80="+b.func1(100,80)); System.out.println("100+20+100="+b.func2(100,20)); } }
運行結果:
100-50=150
100-80=180
100+20+100=220
咱們發現原來運行正常的相減功能發生了錯誤。緣由就是類B無心中重寫了父類的方法,形成原有功能出現錯誤。在實際編程中,咱們經常會經過重寫父類的方法完成新的功能,這樣寫起來雖然簡單,但整個繼承體系的複用性會比較差。特別是運行多態比較頻繁的時候,若是非要重寫父類的方法,通用的作法是:原來的父類和子類都繼承一個更通俗的基類,原有的繼承關係去掉,採用依賴,聚合,組合等關係代替。
5.依賴倒轉原則
高層模塊不該該依賴低層模塊,兩者都應該依賴其抽象;抽象不該該依賴細節,細節應該依賴抽象。
類A直接依賴類B,若是要將類A改成依賴類C,則必須經過修改類A的代碼來達成。此時,類A通常是高層模塊,負責複雜的業務邏輯,類B和類C是低層模塊,負責基本的原子操做;修改A會給程序帶來風險。
將類A修改未依賴接口I,類B和類C各自實現接口I,類A經過接口I間接與類B或類C發生聯繫,則會大大下降修改類A的記概率。
依賴倒置原則基於這樣一個事實:相對於細節的多變性,抽象的東西要穩定的多。以抽象爲基礎搭建的架構比以細節爲基礎的架構要穩定的多。在java中,抽象指的是接口或抽象類,細節就是具體的實現類,使用接口或抽象類的目的是制定好規範,而不涉及任何具體的操做,把展示細節的任務交給他們的實現類去完成。
依賴倒置的中心思想是面向接口編程。
代碼示例以下:
class Book { public string getContent() { return "好久好久之前。。。。。"; } } class Mother { public void narrate(Book book) { Console.WriteLine(book.getContent()); } } class Program { static void Main(string[] args) { Mother monther = new Mother(); monther.narrate(new Book()); Console.ReadLine(); } }
運行結果:
若是讀的對象是報紙,雜誌,卻發現客戶端不適用了。
咱們引入一個抽象的接口IReader,表明讀物
interface IReader{ public string getContent(); }
這樣Mother類與接口IReader發生依賴關係,而Book和Newspaper都屬於讀物的範疇,他們各自都去實現IReader接口,這樣就符合依賴倒置原則了,修改代碼以下:
interface IReader { string getContent(); } class Newspaper: IReader { public string getContent() { return "切爾西豪取12連勝"; } } class Book:IReader { public string getContent() { return "好久好久之前。。。。"; } } class Mother { public void narrate(IReader reader) { Console.WriteLine(reader.getContent()); } } class Program { static void Main(string[] args) { Mother monther = new Mother(); monther.narrate(new Book()); monther.narrate(new Newspaper()); Console.ReadLine(); } }
運行結果:
採用依賴倒置原則給多人並行開發帶來極大的便利,好比上列中Mother類與Book類直接耦合,Mother必須等Book類編碼完成後才能夠進行編碼,由於Mother類依賴於Book類。修改後的程序能夠同時開工,互不影響。
依賴關係的傳遞有三種方式,接口傳遞,構造方法傳遞和setter方法傳遞。
接口傳遞:
interface IDriver{ public void drive(ICar car); } public class Driver:IDriver{ public void drive(ICar car){ car.run(); } }
構造方法傳遞:
interface IDriver{ public void drive(); } public class Driver implements IDriver{ public ICar car; public Driver(ICar _car){ this.car=_car; } public void drive(){ this.car.run(); } }
setter方式傳遞:
interface IDriver{ public void setCar(ICar car); public void drive(); } public class Driver:IDriver{ PRIVATE ICar car; public void setCar(ICar car){ this.car=car; } public void drive(){ this.car.run(); } }
在實際編程中,通常須要作到以下3點:
低層模塊儘可能都要有抽象類或接口,或者二者都有。
變量的聲明類型儘可能是抽象類或接口。
使用繼承時遵循里氏替換原則
6.接口隔離原則
客戶端不該該依賴它不須要的接口;一個類對另外一個類的依賴應該創建在最小的接口上。
類A經過接口I依賴類B,類C經過接口I依賴類D,若是接口I對於類A和類C來講不是最小接口,則類B和類D必須去實現他們不須要的方法。
將臃腫的接口I拆分爲獨立的幾個接口,類A和類C分別與他們須要的接口創建依賴關係。也就是採用接口隔離原則。
舉例說明接口隔離原則:
這個圖的意思是:類A依賴接口I中的方法1,方法2,方法3,類B是對類A依賴的實現;類C依賴接口I中的方法1,方法4,方法5,類D是對類C依賴的實現。對於類B和類D來講,雖然存在用不到的方法(紅色標記所示),但因爲實現了接口I,因此也必需要實現這些用不到的方法。代碼以下:
interface I{ void method1(); void method2(); void method3(); void method4(); void method5(); } class A{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method2(); } public void depend3(I i){ i.method3(); } } class C{ public void depend1(I i){ i.method1(); } public void depend2(I i){ i.method4(); } public void depend3(I i){ i.method5(); } } class B:I{ public void method1(){ Console.WriteLine("類B實現接口I的方法1"); } public void method2(){ Console.WriteLine("類B實現接口I的方法2"); } public void method3(){ Console.WriteLine("類B實現接口I的方法3"); } public void method4(){} public void method5(){} } class D:I{ public void method1(){ Console.WriteLine("類B實現接口I的方法1"); } public void method2(){} public void method3(){} public void method4(){ Console.WriteLine("類B實現接口I的方法4"); } public void method5(){ Console.WriteLine("類B實現接口I的方法5"); } } class Program { static void Main(string[] args) { A a=new A(); a.depend1(new B()); a.depend2(new B()); a.depend3(new B()); C c=new C(); c.depend1(new D()); c.depend2(new D()); c.depend3(new D()); Console.ReadLine(); } }
能夠看到,接口中出現的方法,無論對依賴於它的類有沒有做用,實現類中都必須去實現這些方法。因而咱們將原接口I拆分爲三個接口:
代碼以下所示:
interface I1{ void method1(); } interface I2{ void method2(); void method3(); } interface I3{ void method4(); void method5(); } class A{ public void depend1(I1 i){ i.method1(); } public void depend2(I2 i){ i.method2(); } public void depend3(I2 i){ i.method3(); } } class C{ public void depend1(I1 i){ i.method1(); } public void depend2(I3 i){ i.method4(); } public void depend3(I3 i){ i.method5(); } } class B:I1,I2{ public void method1(){ Console.WriteLine("類B實現接口I1的方法1"); } public void method2(){ Console.WriteLine("類B實現接口I2的方法2"); } public void method3(){ Console.WriteLine("類B實現接口I2的方法3"); } } class D:I1,I3{ public void method1(){ Console.WriteLine("類B實現接口I的方法1"); } public void method4(){ Console.WriteLine("類B實現接口I的方法4"); } public void method5(){ Console.WriteLine("類B實現接口I的方法5"); } } class Program { static void Main(string[] args) { A a=new A(); a.depend1(new B()); a.depend2(new B()); a.depend3(new B()); C c=new C(); c.depend1(new D()); c.depend2(new D()); c.depend3(new D()); Console.ReadLine(); } }
說到這裏,可能會以爲接口隔離原則和以前的單一職責原則很類似,其實否則。一,單一職責注重職責,而接口隔離原則注重對接口依賴的隔離;二,單一職責是約束類,其次是方法,針對的是程序中的實現和細節;而接口隔離原則約束的是接口,針對的是抽象,程序總體框架的構建。
7.迪米特法則
一個對象應該對其餘對象保持最少的瞭解。
類與類關係越密切,耦合度越大。
迪米特法則又叫最少知道原則,即一個類對本身依賴的類知道的越少越好。也就是說,對於被依賴的類無論多麼複雜,都儘可能將邏輯封裝在類的內部。對外除了提供的public 方法,不對外泄露任何信息。
迪米特法則還有個更簡單的定義:只與直接的朋友通訊。
什麼是直接的朋友:每一個對象都會與其餘對象由耦合關係,只要兩個對象之間有耦合關係,咱們就說這兩個對象之間是朋友關係。耦合的方式不少,依賴,關聯,組合,聚合等。其中,咱們稱出現成員變量,方法參數,方法返回值中的類爲直接的朋友,而出如今局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量的形式出如今類的內部。
舉例額說明以下,有一個集團公司,下屬單位有分公司和直屬部門,現要求打印出全部下屬單位的員工ID。
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(); 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(); emp.setId("總公司"+i); list.add(emp); } return list; } publi void printAllEmployee(SubCompanyManager sub){ List<SubEmployee> list1=sub.getAllEmployee(); foreach(SubEmployee e in list1){ Console.WriteLine(e.getId()); } List<Employee> list2=this.getAllEmployee(); foreach(Employee e in list2){ Console.WriteLine(e.getId()); } } } class Program { static void Main(string[] args) { CompanyManager e=new CompanyManager(); e.printAllEmployee(new SubCompanyManager()); Console.ReadLine(); } }
這個設計的問題在於CompanyManager中,SubEmployee類並非CompanyManager類的直接朋友,按照迪米特法則,應該避免類中出現這樣非直接朋友關係的耦合。修改後的代碼以下:
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(){ 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){ sub.printEmployee(); List<Employee> list2 = this.getAllEmployee(); for(Employee e:list2){ System.out.println(e.getId()); } } }
迪米特法則的初衷是下降類之間的耦合,因爲每一個類都減小了沒必要要的依賴,所以的確能夠下降耦合關係。
8.開閉原則一個軟件實體如類,模塊和函數應該對擴展開放,對修改關閉。用抽象構建框架,用實現擴展細節。當軟件須要變化時,儘可能經過擴展軟件實體的行爲來實現變化,而不是經過修改已有的代碼來實現變化。當咱們遵循前面介紹的5大原則,以及使用23中設計模式的目的就是遵循開閉原則。