設計模式——介面卡模式、代理模式、裝飾器模式、橋接模式
一、介面卡模式(Adapter):將一個類的介面轉換成客戶希望的另外一個介面。
介面卡的特點在於相容,從程式碼上的特點來說,適配類與原有的類具有相同的介面,並且持有新的目標物件。
是將一個類(a)通過某種方式轉換成另一個類(b)。
二、代理模式(Proxy):一個類代表另一個類的功能。在代理模式中,我們建立具有現有物件的物件,以便向外界提供功能介面。(靜態代理)
抽象角色:宣告真實物件和代理物件的共同介面,這樣可在任何使用真實物件的地方都可以使用代理物件。
真實角色:即為代理物件所代表的目標物件,代理角色所代表的真實物件,是我們最終要引用的物件。
代理角色:代理物件內部含有真實物件的引用,從而可以在任何時候操作真實物件。代理物件提供一個與真實物件相同的介面,以便可以在任何時候替代真實物件。代理物件通常在客戶端呼叫傳遞給真實物件之前或之後,執行某個操作,而不是單純地將呼叫傳遞給真實物件,同時,代理物件可以在執行真實物件操作時,附加其他的操作,相當於對真實物件進行封裝。
Subject抽象角色、RealSubject真實角色、Proxy代理角色。其中:Subject角色負責定義RealSubject和Proxy角色應該實現的介面;RealSubject角色用來真正完成業務服務功能;Proxy角色負責將自身的request請求,呼叫RealSubject對應的request功能來實現業務功能,自己不真正做業務。
- subject類:抽象角色,定義了RealSubject和Proxy的共用介面,這樣就能在任何使用RealSubject的地方都可以使用Proxy;
- RealSubject類:真實角色,定義Proxy所代表的真實實體;
- Proxy類:代理角色,用來代替實體,儲存一個引用,使代理可以訪問實體,並提供一個與Subject的介面相同的介面。
動態代理:Jdk代理和cglib代理 AOP
jdk動態代理:jdk的動態代理是基於介面的,必須實現了某一個或多個任意接口才可以被代理,並且只有這些介面中的方法會被代理。
在呼叫過程中使用了通用的代理類包裝了RealSubject例項,然後呼叫了Jdk的代理工廠方法例項化了一個具體的代理類。最後呼叫代理的doSomething方法,還有附加的before、after方法可以被任意複用(只要我們在呼叫程式碼處使用這個通用代理類去包裝任意想要需要包裝的被代理類即可)。當介面改變的時候,雖然被代理類需要改變,但是我們的代理類卻不用改變了。這個呼叫雖然足夠靈活,可以動態生成一個具體的代理類,而不用自己顯示的建立一個實現具體介面的代理類。
三、裝飾器模式:豐富原介面的功能,並且不改動原先的介面
四、橋接模式:將抽象部分與實現部分分離,使它們都可以獨立的變化。
1、意圖:
將抽象部分(抽象介面)與它的實現部分(程式碼實現)分離,使它們都可以獨立地變化。
理解:抽象部分是對外展現的介面(api),而實現部分是針對抽象介面提供的不同版本的功能實現,使兩者獨立變化指兩者可以在各自的維度上自由變化,而不會產生太大的影響。如可以在api中新增新的介面,而不影響具體實現部分;可以在實現部分針對特定介面新增新的實現方式,而不影響抽象介面定義。
橋接模式將類的繼承關係轉變為類的聚合關係(見下圖)。對於抽象介面及其實現,Java中通常的實現方式是通過抽象類的繼承或介面的實現,但是橋接模式,將這種抽象介面與實現之間的關係變成了聚合關係。
2、橋接模式(也稱:橋樑模式)類圖
角色:
- Abstraction:定義抽象介面,對外提供api;自身維護一個Implementor(橋)的引用,使用Implementor的操作來完成自身介面功能,如operation方法通過impl.doOperation()來實現;
- RefinedAbstraction:擴充套件Abstraction的抽象介面;
- Implementor:架在抽象介面與不同實現方式之間的橋,且自身的介面不必與Abstraction完全相同;
- ConcreteImplementorA:實現Implementor(橋)的介面,針對Implementor(橋)中的介面doOperation提供特色化的實現;
- Client:使用者類。
協作:
- Client呼叫Abstraction提供的介面,而Abstraction將Client的呼叫通過Implementor來實現。
3、JDBC與橋接:
JDBC是橋接模式的典型實現。
先看下類圖:
通常使用JDBC連線資料庫時,會使用如下程式碼:
1 Class.forName("資料庫類驅動器");
2 Connection conn = DriverManager.getConnection("資料庫url", "使用者名稱", "密碼");
3 //.................
針對不同的資料庫,JDBC都可以通過java.sql.DriverManager類的靜態方法getConnection(資料庫url, 使用者名稱, 密碼)來獲取資料庫的連線。JDBC通過DriverManager對外提供了操作資料庫的統一介面getConnection,通過該方法可以獲取不同資料庫的連線,並且通過Connection類提供的介面來進行資料的查詢操作。
JDBC為不同的資料庫操作提供了相同的介面,但是JDBC本身並沒有針對每種資料庫提供一套具體實現程式碼,而是通過介面java.sql.Driver的connect方法連線到了不同的資料庫實現。
1 public interface Driver
2 {
3 public abstract Connection connect(String s, Properties properties) throws SQLException;
4 //其他方法
5 }
在JDBC中,針對不同資料庫提供的統一的操作介面通過java.sql.Driver(橋)連線到了不同的資料庫實現。如連線mysql資料庫。
1 package com.mysql.jdbc;
2 public class NonRegisteringDriver implements java.sql.Driver //對java.sql.Driver介面提供了實現
3 {
4 public Connection connect(String url, Properties info) throws SQLException
5 {
6 //實現
7 }
8 //其他方法
9 }
Java在連線MySQL時需要使用mysql-connector-java.jar,mysql-connector-java.jar包提供了對MySQL資料庫操作的具體實現,並通過介面Driver連線到了JDBC統一的api。
4、JDBC中橋接模式具體如何實現?
既然,針對不同的資料庫,通過DriverManger.getConnection()可以獲得相同的Connection介面,那先看DriverManager的原始碼:
1 public class DriverManager
2 {
3 private static final CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList();
4 //存放DriverInfo的連結串列
5 public static synchronized void registerDriver(Driver driver) throws SQLException
7 {
8 if(driver != null)
9 registeredDrivers.addIfAbsent(new DriverInfo(driver));
10 //向連結串列中新增DriverInfo例項,DriverInfo封裝了Driver
11 else
12 throw new NullPointerException();
13 println((new StringBuilder()).append("registerDriver: ").append(driver).toString());
14 }
15 private static Connection getConnection(String s, Properties properties, Class class1)
16 throws SQLException
17 {
18 //.....
19 Iterator iterator = registeredDrivers.iterator(); //遍歷registeredDrivers表
20 do
21 {
22 if(!iterator.hasNext())
23 break;
24 DriverInfo driverinfo = (DriverInfo)iterator.next();
25 if(isDriverAllowed(driverinfo.driver, classloader))
26 try
27 {
28 Connection connection = driverinfo.driver.connect(s, properties);
//呼叫Driver介面提供的connect方法來獲取Connection物件
29 if(connection != null)
30 {
31 return connection;
32 }
33 }
34 catch(SQLException sqlexception1)
35 {
36 if(sqlexception == null)
37 sqlexception = sqlexception1;
38 }
39 } while(true);
40 }
41 //其他方法
42 }
從DriverManager.getConnection()原始碼可見,方法中遍歷了包含DriverInfo例項的表registeredDrivers,通過表中例項driverinfo來獲取封裝的java.sql.Driver型別的例項,並呼叫java.sql.Driver介面的connect方法獲取到Connection。
【注:DriverInfo是Driver的封裝類。由DriverInfo原始碼可見。
1 class DriverInfo
2 {
3
4 DriverInfo(Driver driver1)
5 {
6 driver = driver1;
7 }
8
9 public boolean equals(Object obj)
10 {
11 return (obj instanceof DriverInfo) && driver == ((DriverInfo)obj).driver;
12 }
13
14 public int hashCode()
15 {
16 return driver.hashCode();
17 }
18
19 public String toString()
20 {
21 return (new StringBuilder()).append("driver[className=").append(driver).append("]").toString();
22 }
23
24 final Driver driver;
25 }
那麼,Driver例項是何時注入到DriverManager類的registeredDrivers中的呢?以mysql為例,在每次使用JDBC連線mysql時,都會有下面的呼叫:
1 Class.forName("com.mysql.jdbc.Driver");
該行程式碼通過反射載入了com.mysql.jdbc.Driver類(com.mysql.jdbc.Driver類在mysql-connector-java.jar中,而mysql-connector-java.jar是JDBC連線MySQL的jar包),檢視com.mysql.jdbc.Driver類:
1 package com.mysql.jdbc;
2
3 public class Driver extends NonRegisteringDriver
4 implements java.sql.Driver
5 {
6
7 public Driver()
8 throws SQLException
9 {
10 }
11
12 static
13 {
14 try
15 {
16 DriverManager.registerDriver(new Driver());
17 }
18 catch(SQLException E)
19 {
20 throw new RuntimeException("Can't register driver!");
21 }
22 }
23 }
在com.mysql.jdbc.Driver的原始碼中可以看到在載入com.mysql.jdbc.Driver類時,通過類中的靜態域中的紅色程式碼,會呼叫DriverManager的registerDriver方法將當前MySQL的驅動類例項注入到DriverManager的registeredDrivers中。
通過整個程式碼呼叫,展示了橋接模式在JDBC中是如何運用的。
5、適用性:
- 當不希望抽象介面和實現部分採用固定的繫結關係時;
6、特點:
- 橋接模式良好地實現了開閉原則:通常應用橋接模式的地方,抽象介面和具體實現部分都是可以變化的,且抽象介面與實現耦合度低;
- 橋接模式將類繼承關係轉換成了物件組合關係,實現了類的複用,減少了類的個數。