設計模式六大原則

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中設計模式的目的就是遵循開閉原則。

相關文章
相關標籤/搜索