理解軟件設計模式

設計模式能夠幫助消除冗餘代碼。學習如何利用 Java 使用單例模式、工廠模式和觀察者模式。java

若是你是一名正在致力於計算機科學或者相關學科的程序員或者學生,很快,你將會遇到一條術語 「軟件設計模式software design pattern」。根據維基百科,「軟件設計模式是在日常的軟件設計工做中所遭遇的問題的一種通用的、可重複使用的解決方案」。我對該定義的理解是:當在從事於一個編碼項目時,你常常會思考,「嗯,這裏貌似是冗餘代碼,我以爲是否能改變這些代碼使之更靈活和便於修改?」所以,你會開始考慮怎樣分割那些保持不變的內容和須要常常改變的內容。linux

設計模式是一種經過分割那些保持不變的部分和常常變化的部分,讓你的代碼更容易修改的方法。git

不出意外的話,每一個從事編程項目的人均可能會有一樣的思考。特別是那些工業級別的項目,在那裏一般工做着數十甚至數百名開發者;協做過程代表必須有一些標準和規則來使代碼更加優雅並適應變化。這就是爲何咱們有了 面向對象編程(OOP)和 軟件框架工具。設計模式有點相似於 OOP,但它經過將變化視爲天然開發過程的一部分而進一步發展。基本上,設計模式利用了一些 OOP 的思想,好比抽象和接口,可是專一於改變的過程。程序員

當你開始開發項目時,你常常會聽到這樣一個術語重構,它意味着經過改變代碼使它變得更優雅和可複用;這就是設計模式耀眼的地方。當你處理現有代碼時(不管是由其餘人構建仍是你本身過去構建的),瞭解設計模式能夠幫助你以不一樣的方式看待事物,你將發現問題以及改進代碼的方法。github

有不少種設計模式,其中單例模式、工廠模式和觀察者模式三種最受歡迎,在這篇文章中我將會一一介紹它們。apache

如何遵循本指南

不管你是一位有經驗的編程工做者仍是一名剛剛接觸的新手,我想讓這篇教程讓每一個人都很容易理解。設計模式概念並不容易理解,減小開始旅程時的學習曲線始終是首要任務。所以,除了這篇帶有圖表和代碼片斷的文章外,我還建立了一個 GitHub 倉庫,你能夠克隆倉庫並在你的電腦上運行這些代碼來實現這三種設計模式。你也能夠觀看我建立的 YouTube視頻編程

必要條件

若是你只是想了解通常的設計模式思想,則無需克隆示例項目或安裝任何工具。可是,若是要運行示例代碼,你須要安裝如下工具:設計模式

  • Java 開發套件(JDK):我強烈建議使用 OpenJDK
  • Apache Maven:這個簡單的項目使用 Apache Maven 構建;好的是許多 IDE 自帶了Maven。
  • 交互式開發編輯器(IDE):我使用 社區版 IntelliJ,可是你也可使用 Eclipse IDE 或者其餘你喜歡的 Java IDE。
  • Git:若是你想克隆這個工程,你須要 Git 客戶端。

安裝好 Git 後運行下列命令克隆這個工程:安全

git clone https://github.com/bryantson/OpensourceDotComDemos.git
複製代碼

而後在你喜歡的 IDE 中,你能夠將 TopDesignPatterns 倉庫中的代碼做爲 Apache Maven 項目導入。ruby

我使用的是 Java,但你也可使用支持抽象原則的任何編程語言來實現設計模式。

單例模式:避免每次建立一個對象

單例模式singleton pattern是很是流行的設計模式,它的實現相對來講很簡單,由於你只須要一個類。然而,許多開發人員爭論單例設計模式的是否利大於弊,由於它缺少明顯的好處而且容易被濫用。不多有開發人員直接實現單例;相反,像 Spring Framework 和 Google Guice 等編程框架內置了單例設計模式的特性。

可是瞭解單例模式仍然有巨大的用處。單例模式確保一個類僅建立一次且提供了一個對它的全局訪問點。

單例模式:確保僅建立一個實例且避免在同一個項目中建立多個實例。

下面這幅圖展現了典型的類對象建立過程。當客戶端請求建立一個對象時,構造函數會建立或者實例化一個對象並調用方法返回這個類給調用者。可是每次請求一個對象都會發生這樣的狀況:構造函數被調用,一個新的對象被建立而且它返回了一個獨一無二的對象。我猜面嚮對象語言的建立者有每次都建立一個新對象的理由,可是單例過程的支持者說這是冗餘的且浪費資源。

Normal class instantiation
Normal class instantiation

下面這幅圖使用單例模式建立對象。這裏,構造函數僅當對象首次經過調用預先設計好的 getInstance() 方法時纔會被調用。這一般經過檢查該值是否爲 null 來完成,而且這個對象被做爲私有變量保存在單例類的內部。下次 getInstance() 被調用時,這個類會返回第一次被建立的對象。而沒有新的對象產生;它只是返回舊的那一個。

Singleton pattern instantiation
Singleton pattern instantiation

下面這段代碼展現了建立單例模式最簡單的方法:

package org.opensource.demo.singleton;

public class OpensourceSingleton {

    private static OpensourceSingleton uniqueInstance;

    private OpensourceSingleton() {
    }

    public static OpensourceSingleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new OpensourceSingleton();
        }
        return uniqueInstance;
    }

}
複製代碼

在調用方,這裏展現瞭如何調用單例類來獲取對象:

Opensource newObject = Opensource.getInstance();
複製代碼

這段代碼很好的驗證了單例模式的思想:

  1. getInstance() 被調用時,它經過檢查 null 值來檢查對象是否已經被建立。
  2. 若是值爲 null,它會建立一個新對象並把它保存到私有域,返回這個對象給調用者。不然直接返回以前被建立的對象。

單例模式實現的主要問題是它忽略了並行進程。當多個進程使用線程同時訪問資源時,這個問題就產生了。對於這種狀況有對應的解決方案,它被稱爲雙重檢查鎖,用於多線程安全,以下所示:

package org.opensource.demo.singleton;

public class ImprovedOpensourceSingleton {

    private volatile static ImprovedOpensourceSingleton uniqueInstance;

    private ImprovedOpensourceSingleton() {}

    public static ImprovedOpensourceSingleton getInstance() {
        if (uniqueInstance == null) {
            synchronized (ImprovedOpensourceSingleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new ImprovedOpensourceSingleton();
                }
            }
        }
        return uniqueInstance;
    }

}
複製代碼

再強調一下前面的觀點,確保只有在你認爲這是一個安全的選擇時才直接實現你的單例模式。最好的方法是經過使用一個製做精良的編程框架來利用單例功能。

工廠模式:將對象建立委派給工廠類以隱藏建立邏輯

工廠模式factory pattern是另外一種衆所周知的設計模式,可是有一小點複雜。實現工廠模式的方法有不少,而下列的代碼示例爲最簡單的實現方式。爲了建立對象,工廠模式定義了一個接口,讓它的子類去決定實例化哪個類。

工廠模式:將對象建立委派給工廠類,所以它能隱藏建立邏輯。

下列的圖片展現了最簡單的工廠模式是如何實現的。

Factory pattern
Factory pattern

客戶端請求工廠類建立類型爲 x 的某個對象,而不是客戶端直接調用對象建立。根據其類型,工廠模式決定要建立和返回的對象。

在下列代碼示例中,OpensourceFactory 是工廠類實現,它從調用者那裏獲取類型並根據該輸入值決定要建立和返回的對象:

package org.opensource.demo.factory;

public class OpensourceFactory {

    public OpensourceJVMServers getServerByVendor(String name) {
        if(name.equals("Apache")) {
            return new Tomcat();
        }
        else if(name.equals("Eclipse")) {
            return new Jetty();
        }
        else if (name.equals("RedHat")) {
            return new WildFly();
        }
        else {
            return null;
        }
    }
}
複製代碼

OpenSourceJVMServer 是一個 100% 的抽象類(即接口類),它指示要實現的是什麼,而不是怎樣實現:

package org.opensource.demo.factory;

public interface OpensourceJVMServers {
    public void startServer();
    public void stopServer();
    public String getName();
}
複製代碼

這是一個 OpensourceJVMServers 類的實現示例。當 RedHat 被做爲類型傳遞給工廠類,WildFly 服務器將被建立:

package org.opensource.demo.factory;

public class WildFly implements OpensourceJVMServers {
    public void startServer() {
        System.out.println("Starting WildFly Server...");
    }

    public void stopServer() {
        System.out.println("Shutting Down WildFly Server...");
    }

    public String getName() {
        return "WildFly";
    }
}
複製代碼

觀察者模式:訂閱主題並獲取相關更新的通知

最後是觀察者模式observer pattern。像單例模式那樣,不多有專業的程序員直接實現觀察者模式。可是,許多消息隊列和數據服務實現都借用了觀察者模式的概念。觀察者模式在對象之間定義了一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴它的對象都將被自動地通知和更新。

觀察者模式:若是有更新,那麼訂閱了該話題/主題的客戶端將被通知。

理解觀察者模式的最簡單方法是想象一個郵件列表,你能夠在其中訂閱任何主題,不管是開源、技術、名人、烹飪仍是您感興趣的任何其餘內容。每一個主題維護者一個它的訂閱者列表,在觀察者模式中它們至關於觀察者。當某一個主題更新時,它全部的訂閱者(觀察者)都將被通知此次改變。而且訂閱者老是能取消某一個主題的訂閱。

以下圖所示,客戶端能夠訂閱不一樣的主題並添加觀察者以得到最新信息的通知。由於觀察者不斷的監聽着這個主題,這個觀察者會通知客戶端任何發生的改變。

Observer pattern
Observer pattern

讓咱們來看看觀察者模式的代碼示例,從主題/話題類開始:

package org.opensource.demo.observer;

public interface Topic {

    public void addObserver(Observer observer);
    public void deleteObserver(Observer observer);
    public void notifyObservers();
}
複製代碼

這段代碼描述了一個爲不一樣的主題去實現已定義方法的接口。注意一個觀察者如何被添加、移除和通知的。

這是一個主題的實現示例:

package org.opensource.demo.observer;

import java.util.List;
import java.util.ArrayList;

public class Conference implements Topic {
    private List<Observer> listObservers;
    private int totalAttendees;
    private int totalSpeakers;
    private String nameEvent;

    public Conference() {
        listObservers = new ArrayList<Observer>();
    }

    public void addObserver(Observer observer) {
        listObservers.add(observer);
    }

    public void deleteObserver(Observer observer) {
        int i = listObservers.indexOf(observer);
        if (i >= 0) {
            listObservers.remove(i);
        }
    }

    public void notifyObservers() {
        for (int i=0, nObservers = listObservers.size(); i < nObservers; ++ i) {
            Observer observer = listObservers.get(i);
            observer.update(totalAttendees,totalSpeakers,nameEvent);
        }
    }

    public void setConferenceDetails(int totalAttendees, int totalSpeakers, String nameEvent) {
        this.totalAttendees = totalAttendees;
        this.totalSpeakers = totalSpeakers;
        this.nameEvent = nameEvent;
        notifyObservers();
    }
}
複製代碼

這段代碼定義了一個特定主題的實現。當發生改變時,這個實現調用它本身的方法。注意這將獲取觀察者的數量,它以列表方式存儲,而且能夠通知和維護觀察者。

這是一個觀察者類:

package org.opensource.demo.observer;

public interface Observer {
    public void update(int totalAttendees, int totalSpeakers, String nameEvent);
}
複製代碼

這個類定義了一個接口,不一樣的觀察者能夠實現該接口以執行特定的操做。

例如,實現了該接口的觀察者能夠在會議上打印出與會者和發言人的數量:

package org.opensource.demo.observer;

public class MonitorConferenceAttendees implements Observer {
    private int totalAttendees;
    private int totalSpeakers;
    private String nameEvent;
    private Topic topic;

    public MonitorConferenceAttendees(Topic topic) {
        this.topic = topic;
        topic.addObserver(this);
    }

    public void update(int totalAttendees, int totalSpeakers, String nameEvent) {
        this.totalAttendees = totalAttendees;
        this.totalSpeakers = totalSpeakers;
        this.nameEvent = nameEvent;
        printConferenceInfo();
    }

    public void printConferenceInfo() {
        System.out.println(this.nameEvent + " has " + totalSpeakers + " speakers and " + totalAttendees + " attendees");
    }
}
複製代碼

接下來

如今你已經閱讀了這篇對於設計模式的介紹引導,你還能夠去尋求瞭解其餘設計模式,例如外觀模式,模版模式和裝飾器模式。也有一些併發和分佈式系統的設計模式如斷路器模式和錨定模式。

但是,我相信最好的磨礪你的技能的方式首先是經過在你的業餘項目或者練習中實現這些設計模式。你甚至能夠開始考慮如何在實際項目中應用這些設計模式。接下來,我強烈建議你查看 OOP 的 SOLID 原則。以後,你就準備好了解其餘設計模式。


via: opensource.com/article/19/…

做者:Bryant Son 選題:lujun9972 譯者:arrowfeng 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章
相關標籤/搜索