通熟易懂的設計模式(一)

寫在前面

評判一個程序員是否優秀,就是 show me the code。優秀的代碼可讀性強,高內聚低耦合,可擴展。想要寫優秀的代碼,作個優秀的程序員,就須要多看看大牛寫的開源框架,吸收其中的精華,多學學設計模式,除此以外,沒有任何其餘捷徑。java

設計模式主要分爲建立型模式結構型模式行爲型模式三種類型。程序員

工廠方法(Factory method pattern)

定義一個建立對象的接口,讓實現這個接口的類來決定實例化哪一個類。工廠方法讓類的實例化推遲到子類中進行,它屬於建立型模式。編程

工廠對象一般包含一個或多個方法,用來建立這個工廠所能建立的各類類型的對象。這些方法可能接收參數,用來指定對象建立的方式,最後返回建立的對象。設計模式

工廠一般是一個用來建立其餘對象的對象。工廠是構造方法的抽象,用來實現不一樣的分配方案。數組

維基百科工廠方法的例子bash

// 定義了 Button 如何建立
public interface Button{}
// 實現了 WinButton 
public class WinButton implements Button{}
// 實現了 MacButton 
public class MacButton implements Button{}

// 建立 Button 的工廠類
public interface ButtonFactory {
    Button createButton();
}
// 真正建立 WinButton 的實現類,實現了 ButtonFactory
public class WinButtonFactory implements ButtonFactory {
    @Override
    public static Button createButton(){
        return new WinButton();
    }
}
// 真正建立 MacButton的實現類,實現了 ButtonFactory
public class MacButtonFactory implements ButtonFactory {
    @Override
    public static Button createButton(){
        return new MacButton();
    }
}
複製代碼

抽象工廠模式(Abstract factory pattern)

將一組具備同一主題的單獨的工廠封裝起來。在使用中,使用方須要建立抽象工廠的具體實現,而後使用抽象工廠做爲接口來建立這一方法的具體對象。它屬於建立型模式。微信

抽象工廠就好像是對工廠方法的一種擴展,有個產品族的概念,也就是一堆產品。上面的工廠方法就一個產品(Button),而下面抽象工廠的例子裏有兩個產品(Button和 Border)。app

抽象工廠的優勢:
具體產品從客戶代碼中被分離出來。
容易改變產品的系列。
將一個系列的產品族統一到一塊兒建立。框架

抽象工廠的缺點:
在產品族中擴展新的產品是很困難的,它須要修改抽象工廠的接口。ide

維基百科抽象工廠的例子

public interface Button {}
public interface Border {}

public class WinButton implements Button {}
public class WinBorder implements Border {}

public class MacButton implements Button {}
public class MacBorder implements Border {}

public interface AbstractFactory {
	Button createButton();
	Border createBorder();
}

public class WinFactory {
    @Override
    public static Button createButton() {
	return new WinButton();
    }
    @Override
    public static Border createBorder() {
	return new WinBorder();
    }
}

public class MacFactory {
    @Override
    public static Button createButton() {
	return new MacButton();
    }
    @Override
    public static Border createBorder() {
	return new MacBorder();
    }
}

複製代碼

構建模式(Builder pattern)

當構建一個複雜對象時,就可使用建造者模式。它實際上就是傳入一個參數,而後返回對象自己,方便下一個屬性或者參數來構建。能夠按需構建對象,可擴展性強。它屬於建立型模式。

在 JDK 中,StringBuilder 類的 append() 方法就是一個很好的構建模式例子。

// java.lang.StringBuilder
    @Override
    public StringBuilder append(CharSequence s) {
        super.append(s);
        return this;
    }
複製代碼

原型模式(Prototype pattern)

它的特色在於經過 複製 一個已經存在的實例來返回新的實例,而不是新建實例。被複制的實例就是咱們所稱的原型,這個原型是可定製的。它屬於建立型模式。

原型模式多用於建立複雜的或者耗時的實例,由於這種狀況下,複製一個已經存在的實例使程序運行更高效,它們實際上就是命名不同的同類數據。

在 JDK 中,Object 類中的 clone() 方法就是典型的原型模式。

//在具體的實現類裏能夠直接重寫 clone() 方法
    protected native Object clone() throws CloneNotSupportedException;

複製代碼

單例模式(Singleton pattern)

無論在任什麼時候候,獲取一個對象時只會返回同一個實例。一般咱們在都讀取配置文件時,文件裏的內容是不變的,所以咱們就可使用單例模式來實現。它屬於建立型模式。

單例模式很簡單,只須要三步就完成,下面是 JDK 裏的 Runtime 類實現的單例模式。

public class Runtime {
    // 1.new 一個私有的靜態的 Runtime 實例
    private static Runtime currentRuntime = new Runtime();

    // 2.返回當前應用的 Runtime 實例
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    // 3.私有化構造方法,不容許在其它類構造 Runtime 實例
    private Runtime() {}
}
複製代碼

適配器模式(Adapter pattern)

適配器模式也稱做包裝(wrapper),它屬於結構型模式。

它的做用是把本來兩個不兼容的接口經過一個適配器或者包裝成兼容的接口,而後它們能夠一塊兒工做。例如咱們的手機數據線,就比如是一個適配器,一端是 USB接口,一端是 Type-C 或者 Lightning 接口,原本它們是不能一塊兒工做的,但咱們用這跟數據線(適配器)就可讓它們協同工做了。

它的優勢:
1.可讓任何兩個沒有關聯的接口一塊兒工做。
2.提升了接口的複用。
3.增長了接口的透明度。
4.靈活性好。

它的缺點:
1.過多地使用適配器,會讓系統很是零亂,不易總體進行把握。
2.因爲 JAVA 只能繼承一個類,因此最多隻能適配一個適配者類,並且目標類必須是抽象類。

在 JDK 中,Arrays 類中的 asList(T... a) 方法就是適配器模式的例子,把一個數組轉換爲一個集合。

public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }
複製代碼

在 JDK 中,Collections 工具類的 list() 方法把枚舉轉集合。

public static <T> ArrayList<T> list(Enumeration<T> e) {
        ArrayList<T> l = new ArrayList<>();
        while (e.hasMoreElements())
            l.add(e.nextElement());
        return l;
    }
複製代碼

固然,這上面兩個例子是很是簡單的,好像看不出來使用了任何的設計模式,跟咱們平時使用的轉換如出一轍。這裏只是一個理念的介紹,實際上,在使用中是用一箇中間的 Adapter 類來作兼容或者包裝的。

橋接模式(Bridge pattern)

將抽象與其實現分離,以便二者能夠獨立變化。它屬於結構型模式。

當一個類常常變化時,面向對象編程的特性變得很是有用,由於能夠用最少的關於程序的先驗知識來容易地改變程序的代碼。當類和它常常變化時,橋模式頗有用。類自己能夠被認爲是抽象,類能夠做爲實現來作。橋模式也能夠被認爲是兩層抽象。

它經過提供抽象化和實現化之間的橋接結構,來實現兩者的解耦。

它的優勢:
1.抽象和實現分離開。
2.優秀的擴展能力。
3.實現細節對調用方透明。

它的缺點:
橋接模式的引入會增長系統的理解與設計難度,因爲聚合關聯關係創建在抽象層,須要針對抽象進行設計與編程,加深編程難度。

這是個生產不一樣車輛和須要不一樣生產流程的例子。首先,定義一個車輛(Vehicle)的抽象接口,裏面有個生產車輛的對應流程集合和一個生產的抽象方法。而後是自行車(Bike)和汽車(Car)對抽象接口的實現。而後定義一個生產車輛須要的流程(WorkShop),它有兩個實現 ProduceWorkShop 和 TestWorkShop。最後,main 方法的代碼就是對這個橋接模式的使用方式。

這種設計模式的好處是方便添加一種車巴士(Bus),只須要繼承 Vehicle 類。也很是方便的添加一種生產流程噴漆(PaintWorkShop),只須要繼承 WorkShop 類便可,擴展性很高。

//車輛的抽象接口
public abstract class Vehicle {
    //
    protected List<WorkShop> workshops = new ArrayList<WorkShop>();
    public Vehicle() {
        super();
    }
    public boolean joinWorkshop(WorkShop workshop) {
        return workshops.add(workshop);
    }
    public abstract void manufacture();
}

//自行車的實現
public class Bike extends Vehicle {
    @Override
    public void manufacture() {
        System.out.println("Manufactoring Bike...");
        workshops.stream().forEach(workshop -> workshop.work(this));
    }
}
//汽車的實現
public class Car extends Vehicle {
    @Override
    public void manufacture() {
        System.out.println("Manufactoring Car");
        workshops.stream().forEach(workshop -> workshop.work(this));
    }
}
//生產車的抽象接口
public abstract class WorkShop {
    public abstract void work(Vehicle vehicle);
}
//製造車的實現
public class ProduceWorkShop extends WorkShop {
    public ProduceWorkShop() {
        super();
    }
    @Override
    public void work(Vehicle vehicle) {
        System.out.print("Producing... ");
    }
}
//測試車的實現
public class TestWorkShop extends WorkShop {
    public TestWorkShop() {
        super();
    }
    @Override
    public void work(Vehicle vehicle) {
        System.out.print("Testing... ");
    }
}
//使用
public class Main {
    public static void main(String[] args) {
        //生產一輛自行車
        Vehicle bike = new Bike();
        bike.joinWorkshop(new ProduceWorkShop());
        bike.manufacture();
        //生產一輛汽車
        Vehicle car = new Car();
        car.joinWorkshop(new ProduceWorkShop());
        car.joinWorkshop(new TestWorkShop());
        car.manufacture();
    }
}
複製代碼

最後,單看設計模式例子是很是簡單的,但有時候寫代碼時卻用不上這些設計模式,這是一種寫代碼思惟上的轉變。也就是在寫代碼以前,咱們須要根據業務場景思考,那種設計模式適合使用,記住使用設計模式的最終目的是代碼可讀性強,高內聚低耦合,可擴展。這是一種思惟上的轉變,多思考在動手,熟能生巧。

PS:
清山綠水始於塵,博學多識貴於勤。
微信公衆號:「清塵閒聊」。
歡迎一塊兒談天說地,聊代碼。

相關文章
相關標籤/搜索