1. 程式人生 > 程式設計 >Maven中optional和scope元素的使用弄明白了嗎

Maven中optional和scope元素的使用弄明白了嗎

技術標籤:設計模式設計模式java

橋接模式是一種結構型設計模式,英文是 Bridge Design Pattern。在 GoF 的《設計模式》一書中,橋接模式是這麼定義的:Decouple an abstraction from its implementation so that the two can vary independently。翻譯成中文就是:將抽象和實現解耦,讓它們可以獨立變化。單從官方概念來理解,相當晦澀,通常對抽象與實現的第一反應就是介面(或抽象類)與實現類的繼承關係,介面和實現獨立變化就更加讓人難以理解。

關於橋接模式,還有另外一種理解方式:一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴充套件。再配合類圖(

https://zh.wikipedia.org/wiki/%E6%A9%8B%E6%8E%A5%E6%A8%A1%E5%BC%8F)就比較容易理解一些。

Bridge UML class diagram.svg

橋接模式所涉及的角色有:

  • 抽象(Abstraction)角色:抽象角色,採用抽象類方式,並持有一個實現角色物件的引用。
  • 具體抽象(RefinedAbstraction)角色:具體抽象角色具體實現。
  • 實現(Implementor)角色:給出實現角色的介面。實現化角色應當只給出抽象層操作,抽象角色持有抽象角色並只給出基於抽象角色的操作。
  • 具體實現(ConcreteImplementor)角色:這個角色給出實現角色介面的具體實現。

可以看到抽象和實現是兩個角色,是呼叫者和被呼叫者的關係,通過組合關係使抽象角色持有實現角色,通過組合關係來替代繼承關係,避免繼承層次過多。

接下來通過程式碼理解一下橋接模式,當我們辦理銀行卡時,有不同的銀行(工商銀行,建設銀行)可選擇,每個銀行有不同的卡(儲蓄卡,信用卡),所以銀行和卡就相當於兩個獨立變化的維度,我們將銀行和卡進行抽象,將銀行作為抽象角色,將卡作為實現角色,通過組合的方式使銀行持有卡的介面引用,將其組合在一起。

public class BridgePattern {

    /**
     * 銀行抽象類,橋接模式的抽象部分
     */
    abstract static class Bank {
        protected Card card;

        public Bank(Card card) {
            this.card = card;
        }

        abstract Card applyCard();
    }

    /**
     * 建設銀行實現類
     */
    static class CCBBank extends Bank {

        public CCBBank(Card card) {
            super(card);
        }

        @Override
        Card applyCard() {
            System.out.print("申請建設銀行 ");
            card.applyCard();
            return card;
        }
    }

    /**
     * 工商銀行實現類
     */
    static class ICBCBank extends Bank {

        public ICBCBank(Card card) {
            super(card);
        }

        @Override
        Card applyCard() {
            System.out.print("申請工商銀行 ");
            card.applyCard();
            return card;
        }
    }

    /**
     * 銀行賬號,橋接模式的實現部分介面
     */
    interface Card {
        /**
         * 申請卡
         *
         * @return
         */
        Card applyCard();

        /**
         * 檢視卡型別
         */
        void showCardType();
    }

    /**
     * 信用卡
     */
    static class CreditCard implements Card {
        @Override
        public CreditCard applyCard() {
            return new CreditCard();
        }

        @Override
        public void showCardType() {
            System.out.println("信用卡");
        }
    }

    /**
     * 儲蓄卡
     */
    static class DebitCard implements Card {
        @Override
        public DebitCard applyCard() {
            return new DebitCard();
        }

        @Override
        public void showCardType() {
            System.out.println("儲蓄卡");
        }
    }

    public static void main(String[] args) {
        Bank icbcBank = new ICBCBank(new CreditCard());
        Card icbcCard = icbcBank.applyCard();
        icbcCard.showCardType();

        Bank ccbBank = new CCBBank(new DebitCard());
        Card ccbCard = ccbBank.applyCard();
        ccbCard.showCardType();
    }

}

輸出結果

申請工商銀行 信用卡
申請建設銀行 儲蓄卡

通過使用橋接模式,就使得銀行和卡這兩個維度可以獨立變化,互不干擾。

JDBC資料庫訪問介面API正是經典的橋接模式實現,具體的程式碼如下所示:


Class.forName("com.mysql.jdbc.Driver");// 載入及註冊JDBC驅動程式
String url = "jdbc:mysql://localhost:3306/mydb?user=root&password=****";
Connection con = DriverManager.getConnection(url);

如果我們想要把 MySQL 資料庫換成 Oracle 資料庫,只需要把第一行程式碼中的 com.mysql.jdbc.Driver 換成 oracle.jdbc.driver.OracleDriver便可。

com.mysql.jdbc.Driver類程式碼實現如下:

package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

這裡涉及另外一個知識,Class.forName和ClassLoader載入類的區別:ClassLoader只能將類載入到JVM方法區(JDK8為元資料區),但是Class.forName除了載入類外,還可以直接載入類中的靜態程式碼塊。

所以當執行 Class.forName(“com.mysql.jdbc.Driver”) 這條語句的時候,會將 com.mysql.jdbc.Driver 註冊到 DriverManager 類中。

java.sql.DriverManager類程式碼精簡後實現如下:

package java.sql;

public class DriverManager {

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    private DriverManager() {
    }

    public static synchronized void registerDriver(java.sql.Driver driver)
            throws SQLException {

        registerDriver(driver, null);
    }

    public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if (driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }

    public static Connection getConnection(String url, String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();
        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

    private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
        for (DriverInfo aDriver : registeredDrivers) {
            println("    trying " + aDriver.driver.getClass().getName());
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
                // Success!
                println("getConnection returning " + aDriver.driver.getClass().getName());
                return (con);
            }
        }
    }

}

可以看到註冊的過程無非是把Driver物件存放進入了CopyOnWriteArrayList型別的registeredDrivers變數中,獲取連線的時候,從registeredDrivers中取出對應的Driver並呼叫相應的connect方法進行連線。

JDBC 這個例子中,JDBC DriverManager就相當於“抽象”角色,java.sql.Driver相當於實現角色,具體的 Driver(比如com.mysql.jdbc.Driver)就相當於實現角色的具體實現。JDBC DriverManager 和 Driver 獨立開發,通過物件之間的組合關係,組裝在一起。JDBC 的所有邏輯操作,最終都委託給 Driver 來執行。

JDBC的類族是由SUN公司設計了一套介面,再由各個資料庫公司實現介面,我們在呼叫的過程中只需要使用介面去定義,然後在載入Driver的過程中底層程式碼會給我們選擇好介面真正的實現類,以此來實現真正的資料庫連線。

總體上來講,橋接模式的原理比較難理解,但程式碼實現相對簡單。在 GoF 的《設計模式》一書中,橋接模式被定義為:將抽象和實現解耦,讓它們可以獨立變化。還有另外一種更加簡單的理解方式:一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴充套件。對於第一種 GoF 的理解方式,弄懂定義中“抽象”和“實現”兩個概念,是理解它的關鍵。定義中的“抽象”,指的並非“抽象類”或“介面”,而是被抽象出來的角色,它只包含骨架程式碼,真正的業務邏輯需要委派給定義中的“實現”來完成。而定義中的“實現”,也並非“介面的實現類”,而是一套獨立的角色。“抽象”和“實現”獨立開發,通過物件之間的組合關係,組裝在一起。

橋接模式的優點使抽象和實現可以沿著各自的維度來變化,所謂抽象和實現沿著各自維度的變化,也就是說抽象和實現不再在同一個繼承層次結構中,使它們各自都具有自己的子類,從而獲得多維度組合物件。橋接模式可以取代多層繼承方案,多層繼承方案違背了“單一職責原則”,提高了系統的可擴充套件性,在兩個變化維度中任意擴充套件一個維度,都不需要修改原有系統,符合“開閉原則”。

橋接模式的缺點是原理比較難理解,會增加系統的理解與設計難度,由於關聯關係建立在抽象層,要求開發者一開始就針對抽象層進行設計與程式設計。還要求正確識別出系統中兩個獨立變化的維度,因此其使用範圍具有一定的侷限性,如何正確識別兩個獨立維度也並非一件易事。