天天看點

懂一點設計模式

零、參考資料

原文位址

一直認為,設計模式沒啥卵用,不需要學。

設計模式是設計的模式,它妄想通過總結提煉優秀設計的常有特點來達到實作優秀設計的目的。

先有符合設計模式的代碼,然後才有設計模式。設計模式是對工整代碼的總結提煉。

設計模式的本質是:面向接口程式設計,大量使用接口,使得擴充性強。

設計模式幾乎為每一種良好的代碼設計都起了一個名字,幾乎順手一寫不經意間都能寫出設計模式。

有些人自己吃了屎,感覺很不爽,非要叫全天下的人一起吃屎,他們才能感到平衡,比如那些使用vim的和使用php的。。。

一、設計模式分類

設計模式共有23種,總體來說設計模式分為三大類:

  • 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
  • 結構型模式,共七種:擴充卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
  • 行為型模式,共十一種:政策模式、模闆方法模式、觀察者模式、疊代子模式、責任鍊模式、指令模式、備忘錄模式、狀态模式、通路者模式、中介者模式、解釋器模式。

二、設計模式六大原則

  1. 開閉原則(Open Close Principle)

    開閉原則就是說對擴充開放,對修改關閉。在程式需要進行拓展的時候,不能去修改原有的代碼,實作一個熱插拔的效果。是以一句話概括就是:為了使程式的擴充性好,易于維護和更新。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。

  2. 裡氏代換原則(Liskov Substitution Principle)

    裡氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 裡氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,隻有當衍生類可以替換掉基類,軟體機關的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行為。裡氏代換原則是對“開-閉”原則的補充。實作“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實作,是以裡氏代換原則是對實作抽象化的具體步驟的規範。—— From Baidu 百科

  3. 依賴倒轉原則(Dependence Inversion Principle)

    這個是開閉原則的基礎,具體内容:真對接口程式設計,依賴于抽象而不依賴于具體。

  4. 接口隔離原則(Interface Segregation Principle)

    這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟體的設計思想,從大型軟體架構出發,為了更新和維護友善。是以上文中多次出現:降低依賴,降低耦合。

  5. 迪米特法則(最少知道原則)(Demeter Principle)

    為什麼叫最少知道原則,就是說:一個實體應當盡量少的與其他實體之間發生互相作用,使得系統功能子產品相對獨立。

  6. 合成複用原則(Composite Reuse Principle)

    原則是盡量使用合成/聚合的方式,而不是使用繼承。

三、工廠模式(Factory Method)

工廠模式就是一個工廠可以new出來好幾種類。它的好處就是入口統一。當人們在學習一個庫的時候, 如果有一個統一的入口,就可以做到提綱挈領的效果。

比如一個Animal工廠,可以生産Dog、Cat兩種Animal

class Animal:
    pass

class Dog(Animal):
    pass

class Cat(Animal):
    pass                

工廠模式的實作有三種寫法:

  • 普通工廠模式(傳字元串)
class AnimalFactory:
    @staticmethod
    def newAnimal(what: str) -> Animal:
        if what == "dog":
            return Dog()
        elif what == "cat":
            return Cat()


dog = AnimalFactory.newAnimal("dog")
cat = AnimalFactory.newAnimal("cat")                
  • 多個工廠模式(寫成多個函數)
class AnimalFactory:
    def newDog(self) -> Animal:
        return Dog()

    def newCat(self) -> Animal:
        return Cat()


factory = AnimalFactory()
dog = factory.newDog()
cat = factory.newCat()                
  • 靜态工廠模式
class AnimalFactory:
    @staticmethod
    def newDog() -> Animal:
        return Dog()

    @staticmethod
    def newCat() -> Animal:
        return Cat()


dog = AnimalFactory.newDog()
cat = AnimalFactory.newCat()                

四、抽象工廠模式(Abstract Factory)

抽象工廠模式是為Dog、Cat各建立一個工廠,讓這兩個工廠實作工廠接口

class AnimalFactory:
    def newAnimal(self):
        pass

class DogFactory(AnimalFactory):
    def newAnimal(self) -> Dog:
        return Dog()

class CatFactory(AnimalFactory):
    def newAnimal(self) -> Cat:
        return Cat()

animalFactory = DogFactory()  # 這個地方可以很容易把DogFactory改成CatFactory而不需要更改其它地方
dog = animalFactory.newAnimal()                

五、單例模式(Singleton)

單例對象(Singleton)是一種常用的設計模式。在Java應用中,單例對象能保證在一個JVM中,該對象隻有一個執行個體存在。這樣的模式有幾個好處:

  1. 某些類建立比較頻繁,對于一些大型的對象,這是一筆很大的系統開銷。
  2. 省去了new操作符,降低了系統記憶體的使用頻率,減輕GC壓力。
  3. 有些類如交易所的核心交易引擎,控制着交易流程,如果該類可以建立多個的話,系統完全亂了。(比如一個軍隊出現了多個司令員同時指揮,肯定會亂成一團),是以隻有使用單例模式,才能保證核心交易伺服器獨立控制整個流程。
public class Singleton {  
  
    /* 持有私有靜态執行個體,防止被引用,此處指派為null,目的是實作延遲加載 */  
    private static Singleton instance = null;  
  
    /* 私有構造方法,防止被執行個體化 */  
    private Singleton() {  
    }  
    /* 靜态工程方法,建立執行個體 */  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}                

單例模式一旦設計并發,就會變得有些繁瑣。如果不求行,直接看下節。再說了,就算執行個體被多建立了幾次,又有什麼關系。

給getInstance()函數加個鎖

public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
}                  

給函數加鎖有點笨重

public static Singleton getInstance() {
        if (instance == null) {
            synchronized (instance) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
}                

為啥用單例類而不是一個類中有很多靜态方法?

  • 首先,靜态類不能實作接口。(從類的角度說是可以的,但是那樣就破壞了靜态了。因為接口中不允許有static修飾的方法,是以即使實作了也是非靜态的)
  • 其次,單例可以被延遲初始化,靜态類一般在第一次加載是初始化。之是以延遲加載,是因為有些類比較龐大,是以延遲加載有助于提升性能。
  • 再次,單例類可以被繼承,他的方法可以被覆寫。但是靜态類内部方法都是static,無法被覆寫。
  • 最後一點,單例類比較靈活,畢竟從實作上隻是一個普通的Java類,隻要滿足單例的基本需求,你可以在裡面随心所欲的實作一些其它功能,但是靜态類不行。

而我覺得,單例寫起來簡單,不用每個函數前面都加個static

六、建造者模式

跟工廠模式一樣,隻不過它産生的是一個清單

public class Builder {  
    private List<Sender> list = new ArrayList<Sender>();  
      
    public void produceMailSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new MailSender());  
        }  
    }  
      
    public void produceSmsSender(int count){  
        for(int i=0; i<count; i++){  
            list.add(new SmsSender());  
        }  
    }  
}  
public class Test {  
    public static void main(String[] args) {  
        Builder builder = new Builder();  
        builder.produceMailSender(10);  
    }  
}                  

七、原型模式(Prototype)

原型模式就是執行個體之間互相獨立,沒有共同引用的資料。這可能會涉及到深複制、淺複制問題

Java中深複制是通過寫入讀出ObjectStream來實作的

public Object deepClone() throws IOException, ClassNotFoundException {  
  
        /* 寫入目前對象的二進制流 */  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  
  
        /* 讀出二進制流産生的新對象 */  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return ois.readObject();  
    }                  

八、擴充卡模式

擴充卡模式其實相當于一種映射,可以使兩套不同的體系互相相容。

一個事物有兩種描述方式,這兩種描述方式之間的溝通就需要擴充卡模式。

擴充卡模式有三種形式:

  • 類的擴充卡模式:當希望将一個類轉換成滿足另一個新接口的類時,可以使用類的擴充卡模式,建立一個新類,繼承原有的類,實作新的接口即可。

    使用場景:使用接口A來引用類B

    方法:編寫類C implement A extends B

  • 對象的擴充卡模式:當希望将一個對象轉換成滿足另一個新接口的對象時,可以建立一個Wrapper類,持有原類的一個執行個體,在Wrapper類的方法中,調用執行個體的方法就行。

    其實就是包裝一下

  • 接口的擴充卡模式:當不希望實作一個接口中所有的方法時,可以建立一個抽象類Wrapper,實作所有方法,我們寫别的類的時候,繼承抽象類即可

    例如Swing中的MouseAdapter它實作了MouseListener接口的全部方法。

九、裝飾器模式(Decorator)

裝飾器對原有類封裝一下,在函數調用前、調用後、抛出異常後可以進行一些處理。

此模式在AOP(面向切面程式設計)中大量應用。

Java中實作AOP挺麻煩,必須要建立一個類,根本原因在于Java中沒有函數指針的概念;Python就簡單多了、直管多了。

十、代理模式(Proxy)

代理模式對原有類封裝一下,代理類調用原有類的方法并對産生的結果進行轉化

跟裝飾器模式容易弄混,裝飾器模式比代理模式更輕。裝飾器類在程式設計時是用不到,它隻是潛在地封裝了一下原有類。

十一、外觀模式(Facade)

集中調用原則,盡量減少類與類之間的依賴關系

十二、橋接模式(Bridge)

接口的完美使用,最經典的應用就是JDBC,它下面可以挂MySQL、Oracle等各種資料庫實作。

十三、組合模式(Composite)

TreeNode和Tree,多個對象之間互相引用

十四、享元模式(Flyweight)

資料庫連接配接池、套接字連接配接池

十五、政策模式

一個遊戲的AI實作有兩種實作思路:SearchAI是搜尋法實作的AI,TableAI是打表法實作的AI,它們都實作了AI接口,這樣就可以友善地進行政策切換。

一言以蔽之,政策模式是接口的一種應用。

十六、模闆方法模式

抽象類實作一個主要方法,這個主要方法調用抽象類的其它抽象方法。派生類繼承抽象類并實作它的所有抽象方法。

這樣一來,就相當于抽象類給派生類規定了一個函數模闆。

應用:例如HttpServlet,我們需要實作doGet()和doPost()方法

冰山十分之九藏在海面以下,我們隻能看到十分之一。

十七、觀察者模式

Java Swing的事件機制大量使用觀察者模式,各個Listener都是訂閱者。

十八、疊代器模式

實作size(),get(index)等函數

十九、責任鍊模式

流水線工程

在Tomcat中,每一個請求的處理都要經過一條鍊

二十、指令模式

實作指令發出者和指令執行者之間的解耦

比如一個指令難以用一個函數實作,需要用一個類來實作。

二十一、備忘錄模式

備份恢複模式,專門實作一個類記錄目前類的狀态。目前類的狀态可以load或者save到備忘錄類中。

轉載于:https://www.cnblogs.com/weiyinfu/p/6476809.html