1. 程式人生 > >復習寶典之設計模式

復習寶典之設計模式

裏氏替換 類的抽象 voc pre target != main star 不變

查看更多寶典,請點擊《金三銀四,你的專屬面試寶典》

第四章:設計模式

設計模式是前人(一般都是大師)對程序設計經驗的總結,學習並應用設計模式可以使我們開發的項目更加規範、便於擴展和維護。

1)面向對象四大特征

封裝:即將對象封裝成一個高度自治和相對封閉的個體,對象狀態(屬性)由這個對象自己的行為(方法)來讀取和改變。

張三這個人,他的姓名等屬性,要有自己提供的獲取或改變的方法來操作。

private name setName getName

抽象:就是找出一些事物的相似和共性之處,然後將這些事物歸為一個類,這個類只考慮這些事物的相似和共性之處,並且會忽略與當前主題和目標無關的那些方面,將註意力集中在與當前目標有關的方面。 就是把現實生活中的對象,抽象為類。

繼承:在定義和實現一個類的時候,可以在一個已經存在的類的基礎之上來進行,把這個已經存在的類所定義的內容作為自己的內容,並可以加入若幹新的內容,或修改原來的方法使之更適合特殊的需要,這就是繼承。遺產繼承

多態:是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量倒底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。

Object obj = new xxx();

UserDao userDao = new UserDaoJdbcImpl();

UserDao userDao = new UserDaoHibernateImpl();

靠的是父類或接口定義的引用變量可以指向子類或具體實現類的實例對象(裏氏替換原則),而程序調用的方法在運行期才動態綁定,就是引用變量所指向的具體實例對象的方法,也就是內存裏正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。

擴展:一個程序就是一個世界,世界由不同的種類組成,每個類別下的個體就是一個個對象。封裝是對對象屬性的私有化,然後通過公開的方法進行訪問。繼承是is-a的關系,子類會繼承父類的非私有屬性與方法。多態分靜態的多態(構造函數的重載)與動態的多態(編譯時多態,裏氏替換原則),表面意義是一種類型在不同情況下的不同形態【如類的多態,行為(接口)的多態】。

2)五大設計原則

單一職責原則(即一個類只負責一項職責

  單一職責原則(Single Responsibility Principle,SRP):就一個類而言,應該僅有一個引起它變化的原因。即一個類應該只負責一個功能領域中的相應職責。

  單一職責原則是實現高內聚、低耦合的指導方針,它是最簡單但又最難運用的原則,需要設計人員發現類的不同職責並將其分離,而發現類的多重職責需要設計人員具有較強的分析設計能力和相關實踐經驗。

開閉原則(一個類或方法應該對擴展開放,對修改關閉

  開閉原則(Open-Closed Principle,OCP): 是指軟件實體(類、模塊、函數等等)應該可以擴展,但是不可修改。即軟件實體應該盡量在不修改原有代碼的情況下進行擴展。

  為了滿足開閉原則,需要對系統進行抽象化設計,抽象化是開閉原則的關鍵。

裏氏替換原則(子類可以擴展父類的功能,但不能改變父類原有的功能,子類對象實例化父類對象

  裏氏替換原則(Liskov Substitution Principle,LSP):所有引用父類的地方必須能夠透明的使用子類的對象。即子類型必須能夠替換掉它們的父類型。

  裏氏替換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那麽它不一定能夠使用基類對象。因此在程序中盡量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。同時,裏氏代換原則是實現開閉原則的重要方式之一。

它包含以下含義:

1) 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。

2) 子類的方法重載父類的方法時,方法的入參要比父類方法的入參更寬松。

3) 子類的方法實現父類的抽象方法時,方法的返回值要比父類更嚴格。

依賴倒置原則(上層模塊不應該依賴低層模塊的實現,而應該依賴其抽象

  依賴倒置原則(Dependency Inversion Principle,DIP):抽象不應該依賴細節,細節應該依賴於抽象。即應該針對接口編程,而不是針對實現編程。

  在大多數情況下,我們會同時使用開閉原則、裏氏代換原則和依賴倒轉原則,開閉原則是目標,裏氏代換原則是基礎,依賴倒轉原則是手段。

依賴倒置的優點:減少類之間的耦合,提高系統的穩定性,減少並行開發引起的風險。

接口隔離原則(一個類對另一個類的依賴應該建立在最小的接口上

  接口隔離原則(Interface Segregation Principle,ISP):使用專門的接口,而不使用單一的總接口,即客戶端不應該依賴那些它不需要的接口。

  根據接口隔離原則,當一個接口太大時,我們需要將它分割成一些更細小的接口,使用該接口的客戶端僅需知道與之相關的方法即可。每一個接口應該承擔一種相對獨立的角色,不幹不該幹的事,該幹的事都要幹。

3)設計模式基礎

一般地設計模式分為三類共23種:

3.1. 創建型模式

單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。

這些設計模式提供了一種在創建對象的同時隱藏創建邏輯的方式,而不是使用 new 運算符直接實例化對象。這使得程序在判斷針對某個給定實例需要創建哪些對象時更加靈活。

3.2. 結構型模式

適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。

這些設計模式關註類和對象的組合。繼承的概念被用來組合接口和定義組合對象獲得新功能的方式。

3.3. 行為型模式

模版模式、命令模式、叠代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式、狀態模式、策略模式、責任鏈模式、訪問者模式。

這些設計模式特別關註對象之間的通信。

4)單例模式

確保一個類最多只有一個實例,並提供一個全局訪問點。

有些對象我們只需要一個:線程池,緩存,硬件設備,如果多個實例會造成沖突,結果不一致等問題,可以用靜態變量來實現,或者程序員之間協商一個全局變量。

具體實現原理:可以將一個類的構造函數私有化,然後在該類內部創建自己的對象。

飽漢模式:不管是否用到,都先創建好

/**
* @author: 肖德子裕
* @date: 2019/02/28 20:50
* @description: 飽漢模式
*/
public class Singleton2 {
private static Singleton2 uniqeInstance=new Singleton2();
?
private Singleton2() {
?
}
?
public static Singleton2 getInstance(){
return uniqeInstance;
}
}

饑漢模式:需要時才創建

/**
* @author: 肖德子裕
* @date: 2019/02/28 20:50
* @description: 饑漢模式
*/
public class Singleton {
private static Singleton uniqeInstance=null;
?
private Singleton() {
?
}
?
public static Singleton getInstance(){
if (uniqeInstance==null){
uniqeInstance=new Singleton();
}
return uniqeInstance;
}
}

在多線程的情況下,可能2個線程同時訪問該方法,這樣的話就會創建2個實例對象,所以使用雙重檢查加鎖解決該問題

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

擴展知識:

synchronized是Java中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:

  1. 修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的範圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;

  2. 修飾一個方法,被修飾的方法稱為同步方法,其作用的範圍是整個方法,作用的對象是調用這個方法的對象;

  3. 修改一個靜態的方法,其作用的範圍是整個靜態方法,作用的對象是這個類的所有對象;

  4. 修改一個類,其作用的範圍是synchronized後面括號括起來的部分,作用主的對象是這個類的所有對象。

修飾一個代碼塊:一個線程訪問一個對象中的synchronized(this)同步代碼塊時,其他試圖訪問該對象的線程將被阻塞。

當兩個並發線程(thread1和thread2)訪問同一個對象(syncThread)中的synchronized代碼塊時,在同一時刻只能有一個線程得到執行,另一個線程受阻塞,必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。Thread1和thread2是互斥的,因為在執行synchronized代碼塊時會鎖定當前的對象,只有執行完該代碼塊才能釋放該對象鎖,下一個線程才能執行並鎖定該對象。

創建了兩個SyncThread的對象syncThread1和syncThread2,線程thread1執行的是syncThread1對象中的synchronized代碼(run),而線程thread2執行的是syncThread2對象中的synchronized代碼(run);我們知道synchronized鎖定的是對象,這時會有兩把鎖分別鎖定syncThread1對象和syncThread2對象,而這兩把鎖是互不幹擾的,不形成互斥,所以兩個線程可以同時執行。

當一個線程訪問對象的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該對象中的非synchronized(this)同步代碼塊。

Synchronized修飾一個方法很簡單,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修飾方法和修飾一個代碼塊類似,只是作用範圍不一樣,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。

在用synchronized修飾方法時要註意以下幾點:

  1. synchronized關鍵字不能繼承。

  2. 在定義接口方法時不能使用synchronized關鍵字。

  3. 構造方法不能使用synchronized關鍵字,但可以使用synchronized代碼塊來進行同步。

5)工廠模式

遵循依賴抽象原則:

變量不要持有具體類的引用;不要讓類繼承自具體類,要繼承自抽象類或接口;不要覆蓋基類中已經實現的方法。

5.1 簡單工廠模式:

定義了一個創建對象的類,由這個類來封裝實例化對象的行為

問題:如一家披薩店,在下訂單時會根據不同的披薩實例化不同的對象,這樣以後每次提出新品種或者下架某品種,就要去修改訂單裏面的實例化操作。

如下可以定義一個簡單的工廠類來封裝實例化過程:

/**
* 簡單工廠模式
*/
public class SimplePizzaFactory {
/**
* 傳入披薩的類型
*
* @param ordertype
* @return
*/
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
?
if (ordertype.equals("cheese")) {
pizza = new CheesePizza();
} else if (ordertype.equals("greek")) {
pizza = new GreekPizza();
} else if (ordertype.equals("pepper")) {
pizza = new PepperPizza();
}
return pizza;
}
}

/**
* 披薩訂單模塊
*/
public class OrderPizza {
SimplePizzaFactory mSimplePizzaFactory;
?
public OrderPizza(SimplePizzaFactory mSimplePizzaFactory) {
setFactory(mSimplePizzaFactory);
}
?
public void setFactory(SimplePizzaFactory mSimplePizzaFactory) {
Pizza pizza = null;
String ordertype;
this.mSimplePizzaFactory = mSimplePizzaFactory;
//工廠生產披薩並進行包裝發貨
do {
ordertype = gettype();
pizza = mSimplePizzaFactory.CreatePizza(ordertype);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
} while (true);
}
?
private String gettype() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(
System.in));
System.out.println("input pizza type:");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}

/**
* 披薩店中購買披薩
*/
public class PizzaStroe {
public static void main(String[] args) {
SimplePizzaFactory mSimplePizzaFactory;
OrderPizza mOrderPizza;
mOrderPizza=new OrderPizza(new SimplePizzaFactory());
}
}

5.2 方法工廠模式:

定義了一個創建對象的抽象方法,由子類決定要實例化的類。

問題:如果一家披薩店要在全國開連鎖,這樣地方的配料肯定不一樣,所以單純的將工廠復制過去是不行的。

如下可以將創建披薩的方法抽象化,讓子類繼承後去實例化具體的對象:

/**
* 抽象化下訂單的模塊
*/
public abstract class OrderPizza {
public OrderPizza() {
Pizza pizza = null;
String ordertype;
do {
ordertype = gettype();
pizza = createPizza(ordertype);
?
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} while (true);
}
?
abstract Pizza createPizza(String ordertype);
?
private String gettype() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(
System.in));
System.out.println("input pizza type:");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}

/**
* 倫敦的披薩店
*/
public class LDOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (ordertype.equals("pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}

/**
* 紐約的披薩店
*/
public class NYOrderPizza extends OrderPizza {
@Override
Pizza createPizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new NYCheesePizza();
} else if (ordertype.equals("pepper")) {
pizza = new NYPepperPizza();
}
return pizza;
}
}

/**
* 披薩店中購買披薩
*/
public class PizzaStroe {
public static void main(String[] args) {
OrderPizza mOrderPizza;
mOrderPizza=new NYOrderPizza();
}
}

5.3 抽象工廠模式

定義了一個接口用於創建相關或有依賴關系的對象族,而無需明確指定具體類。

/**
* 抽象工廠模式
*/
public interface AbsFactory {
public Pizza CreatePizza(String ordertype) ;
}

/**
* 倫敦工廠
*/
public class LDFactory implements AbsFactory {
@Override
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new LDCheesePizza();
} else if (ordertype.equals("pepper")) {
pizza = new LDPepperPizza();
}
return pizza;
}
}

/**
* 紐約工廠
*/
public class NYFactory implements AbsFactory {
@Override
public Pizza CreatePizza(String ordertype) {
Pizza pizza = null;
if (ordertype.equals("cheese")) {
pizza = new NYCheesePizza();
} else if (ordertype.equals("pepper")) {
pizza = new NYPepperPizza();
}
return pizza;
}
}

/**
* 下訂單的模塊
*/
public class OrderPizza {
AbsFactory mFactory;
?
public OrderPizza(AbsFactory mFactory) {
setFactory(mFactory);
}
?
public void setFactory(AbsFactory mFactory) {
Pizza pizza = null;
String ordertype;
this.mFactory = mFactory;
do {
ordertype = gettype();
pizza = mFactory.CreatePizza(ordertype);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
} while (true);
}
?
private String gettype() {
try {
BufferedReader strin = new BufferedReader(new InputStreamReader(
System.in));
System.out.println("input pizza type:");
String str = strin.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}

/**
* 披薩店中購買披薩
*/
public class PizzaStroe {
public static void main(String[] args) {
OrderPizza mOrderPizza;
mOrderPizza=new OrderPizza(new LDFactory());
}
}

6)策略模式

分別封裝行為接口,實現算法族,超類裏放行為接口對象,在子類裏具體設置行為對象。

策略模式註重功能實現。

原則:分離變化部分,封裝接口,基於接口編程各種功能,此模式讓行為算法的變化獨立於算法的使用者。

將行為定義為接口:

/**
* 飛行
*/
public interface FlyBehavior {
void fly();
}

具體的實現類:

/**
* 會飛
*/
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("--GoodFly--");
}
}

/**
* 不會飛
*/
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("--NoFly--");
}
}

如果還有其他行為也可以向上面一樣,先定義接口,在實現,比如叫聲可以分呱呱叫還有其他叫。

定義一個鴨子類:

/**
* 鴨子
*/
public abstract class Duck {
/**
* 飛行
*/
FlyBehavior mFlyBehavior;

/**
* 叫聲
*/
QuackBehavior mQuackBehavior;
?
public abstract void display();
?
public Duck() {
?
}
?
public void Fly() {
mFlyBehavior.fly();
}
?
public void Quack() {
mQuackBehavior.quack();
}
?
public void SetQuackBehavoir(QuackBehavior qb) {
mQuackBehavior = qb;
}
?
public void SetFlyBehavoir(FlyBehavior fb) {
mFlyBehavior = fb;
}
?
public void swim() {
System.out.println("~~im swim~~");
}
}

鴨子下的子類:

/**
* 綠頭鴨
*/
public class GreenHeadDuck extends Duck {
public GreenHeadDuck() {
//實例化具體的飛行方式
mFlyBehavior = new GoodFlyBehavior();
//實例化具體的叫聲
mQuackBehavior = new GaGaQuackBehavior();
}
?
@Override
public void display() {
System.out.println("**GreenHead**");
}
}

這樣就能實現按不同需求實現:

public class StimulateDuck {
public static void main(String[] args) {
GreenHeadDuck mGreenHeadDuck = new GreenHeadDuck();
RedHeadDuck mRedHeadDuck = new RedHeadDuck();
?
mGreenHeadDuck.display();
mGreenHeadDuck.Fly();
mGreenHeadDuck.Quack();
mGreenHeadDuck.swim();
?
mRedHeadDuck.display();
mRedHeadDuck.Quack();
mRedHeadDuck.swim();
mRedHeadDuck.Fly();
}
}

7)模板模式

封裝了一個算法步驟,並允許子類為一個或多個步驟方法提供實現;模板模式可以使子類在不改變算法結構的情況下,重新定義算法中的某些步驟。

模板模式註重步驟實現。

/**
* @author: 肖德子裕
* @date: 2019/03/03 19:31
* @description: 模板模式
*/
public abstract class HotDrinkTemplate {
/**
* 這是一個模板方法
*/
public final void prepareRecipe() {
//燒水,固定
boilWater();
//喝什麽,由子類實現
brew();
//加水,固定
pourInCup();
//通過鉤子方法,讓子類確定是否配料
if (wantCondimentsHook()) {
//加配料
addCodiments();
} else {
System.out.println("不加配料");
}
}
?
/**
* 鉤子方法
*
* @return
*/
public boolean wantCondimentsHook() {
return true;
}
?
public final void boilWater() {
System.out.println("Boiling water");
}
?
public abstract void brew();
?
public final void pourInCup() {
System.out.println("put in cup");
}
?
public abstract void addCodiments();
}

/**
* @author: 肖德子裕
* @date: 2019/03/03 19:52
* @description: 泡茶
*/
public class TeaWithHook extends HotDrinkTemplate{
@Override
public boolean wantCondimentsHook() {
System.out.println("y/n:");
BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
String result="";
try {
result=in.readLine();
}catch (IOException e){
e.printStackTrace();
}
if (result.equals("n")){
return false;
}
return true;
}
?
@Override
public void brew() {
System.out.println("泡茶");
}
?
@Override
public void addCodiments() {
System.out.println("加檸檬");
}
}

/**
* @author: 肖德子裕
* @date: 2019/03/03 19:58
* @description: 測試
*/
public class Main {
public static void main(String[] args) {
TeaWithHook tea=new TeaWithHook();
tea.prepareRecipe();
}
}

模板方法模式的優點

  • 封裝不變的內容,擴展可變部分,如果我們要添加一個H3悍馬模型,只需要繼承父類就可以了。

  • 提取公共部分代碼,便於維護

  • 行為由父類控制,子類實現。基本方法是由子類實現的,因此子類可以通過拓展的方法增加相應的功能,符合開閉原則。

模板方法模式的使用場景

  • 多個子類有公有的方法,並且邏輯基本相同時

  • 重復、復雜的算法,可以把核心算法設計為模板方法,周邊的細節則有各個子類實現

  • 代碼重構時,模板方法模式是一個經常使用的模式,把相同的代碼抽取到父類中,然後用鉤子方法約束其行為。

鉤子方法源於設計模式中模板方法模式,模板方法模式中分為兩大類:模版方法和基本方法,而基本方法又分為:抽象方法,具體方法,鉤子方法。

好萊塢原則:明星無需聯系經紀人問名具體流程,只需做好自己的事即可,經紀人會通知明星去哪裏做什麽。高層無需知道調用底層的細節,利於解耦。好比模板模式的子類無需知道模板的步驟,只需做好自己該實現的方法。

8)代理模式

為一個對象提供一個替身,以控制這個對象的訪問;被代理的對象可以是遠程對象,創建開銷大的對象或需要安全控制的對象;代理模式有很多變體,都是為了控制與管理對象訪問。

擴展:RMI

RMI遠程方法調用是計算機之間通過網絡實現對象調用的一種通訊機制;使用這種機制,一臺計算機上的對象可以調用另外一臺計算機上的對象來獲取遠程數據;在過去,TCP/IP通訊是遠程通訊的主要手段,面向過程開發;而RPC使程序員更容易地調用遠程程序,但在復雜的信息傳訊時,RPC依然未能很好的支持;RMI被設計成一種面向對象開發方式,允許程序員使用遠程對象來實現通信。

代理模式分類:

靜態代理:在軟件設計時常用的代理一般是指靜態代理,也就是在代碼中顯式指定的代理。

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:23
* @description: 用戶登錄接口
*/
public interface UserServer {
void login();
}

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:24
* @description: 用戶登錄實現類
*/
public class UserServerImpl implements UserServer{
@Override
public void login() {
System.out.println("用戶登錄");
}
}

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:27
* @description: 靜態代理模式
*/
public class UserServerProxy implements UserServer{
private UserServer userServer;
?
public UserServerProxy(UserServer userServer){
this.userServer=userServer;
}
?
@Override
public void login() {
before();
userServer.login();
after();
}
?
public void before(){
System.out.println("登錄之前");
}
?
public void after(){
System.out.println("登錄之後");
}
}

/**
* @author: 肖德子裕
* @date: 2018/10/12 11:32
* @description: 測試靜態代理
*/
public class Main {
public static void main(String[] args) {
UserServer userServer=new UserServerProxy(new UserServerImpl());
userServer.login();
}
}
?
?

靜態代理的缺點很明顯:一個代理類只能對一個業務接口的實現類進行包裝,如果有多個業務接口的話就要定義很多實現類和代理類才行。而且,如果代理類對業務方法的預處理、調用後操作都是一樣的(比如:調用前輸出提示、調用後自動關閉連接),則多個代理類就會有很多重復代碼。這時我們可以定義這樣一個代理類,它能代理所有實現類的方法調用:根據傳進來的業務實現類和方法名進行具體調用,那就是動態代理。

JDK動態代理:JDK動態代理所用到的代理類在程序調用到代理類對象時才由JVM真正創建,JVM根據傳進來的 業務實現類對象 以及 方法名 ,動態地創建了一個代理類的class文件並被字節碼引擎執行,然後通過該代理類對象進行方法調用。**我們需要做的,只需指定代理類的預處理、調用後操作即可。

1)被代理對象必須要實現接口,才能產生代理對象,如果沒有接口將不能使用動態代理技術;

2)動態代理可對方法進行增強,如增加事務的打開與提交;

3)在不破壞原有結構的情況下,生成動態代理對象,對原有方法進行增強。

註意: JDK動態代理的代理對象在創建時,需要使用業務實現類所實現的接口作為參數(因為在後面代理方法時需要根據接口內的方法名進行調用)。如果業務實現類是沒有實現接口而是直接定義業務方法的話,就無法使用JDK動態代理了。並且,如果業務實現類中新增了接口中沒有的方法,這些方法是無法被代理的(因為無法被調用)。

public interface BookFacade {  
public void addBook();
}

public class BookFacadeImpl implements BookFacade {   
@Override
public void addBook() {
System.out.println("增加圖書方法。。。");
}
}

public class BookFacadeProxy implements InvocationHandler {  
//這其實業務實現類對象,用來調用具體的業務方法
private Object target;

/**
* 綁定業務對象並返回一個代理類
*/
public Object bind(Object target) {
this.target = target; //接收業務實現類對象參數
?
//通過反射機制,創建一個代理類對象實例並返回。用戶進行方法調用時使用
//創建代理對象時,需要傳遞該業務類的類加載器(用來獲取業務實現類的元數據,在包裝方法是調用真正的業務方法)、接口、handler實現類
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); }
/**
* 包裝調用方法:進行預處理、調用後處理
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
?
System.out.println("預處理操作——————");
//調用真正的業務方法
result=method.invoke(target, args);
?
System.out.println("調用後處理——————");
return result;
}
}

public static void main(String[] args) {  
BookFacadeImpl bookFacadeImpl=new BookFacadeImpl();
BookFacadeProxy proxy = new BookFacadeProxy();
BookFacade bookfacade = (BookFacade) proxy.bind(bookFacadeImpl);
bookfacade.addBook();
}

在列舉一個動態代理的例子:

public interface UserDao {
public String creatSqlSession();
}

public class UserDaoImpl implements UserDao{
@Override
public String creatSqlSession() {
return "創建SQLSESSION對象";
}
}

public class UserDaoProxyFactory implements InvocationHandler{
private UserDao userDao;
?
public UserDaoProxyFactory(UserDao userDao){
this.userDao=userDao;
}
?
public UserDao getUserDaoProxy(){
//生成動態代理
UserDao userProxy=(UserDao) Proxy.newProxyInstance(UserDaoProxyFactory.class.getClassLoader(),
UserDaoImpl.class.getInterfaces(),
this);
//返回一個動態代理對象
return userProxy;
}
?
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke=method.invoke(userDao,args);
return invoke;
}
}

public class Main {
public static void main(String[] args) {
UserDao userDao=new UserDaoImpl();
UserDaoProxyFactory factory=new UserDaoProxyFactory(userDao);
UserDao userProxy=factory.getUserDaoProxy();
System.out.println(userProxy.creatSqlSession());
System.out.println(userProxy instanceof UserDaoImpl);
}
}
?
?

CGLIB動態代理:cglib是針對類來實現代理的,原理是對指定的業務類生成一個子類,並覆蓋其中業務方法實現代理。因為采用的是繼承,所以不能對final修飾的類進行代理。

public class BookFacadeImpl1 {  
public void addBook() {
System.out.println("新增圖書...");
}
}

public class BookFacadeCglib implements MethodInterceptor {  
private Object target;//業務類對象,供代理方法中進行真正的業務方法調用

//相當於JDK動態代理中的綁定
public Object getInstance(Object target) {
this.target = target; //給業務對象賦值
Enhancer enhancer = new Enhancer(); //創建加強器,用來創建動態代理類
enhancer.setSuperclass(this.target.getClass()); //為加強器指定要代理的業務類(即:為下面生成的代理類指定父類)
//設置回調:對於代理類上所有方法的調用,都會調用CallBack,而Callback則需要實現intercept()方法進行攔
enhancer.setCallback(this);
// 創建動態代理類對象並返回
return enhancer.create();
}
// 實現回調方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("預處理——————");
proxy.invokeSuper(obj, args); //調用業務類(父類中)的方法
System.out.println("調用後操作——————");
return null;
}

public static void main(String[] args) {      
BookFacadeImpl1 bookFacade=new BookFacadeImpl1();
BookFacadeCglib cglib=new BookFacadeCglib();
BookFacadeImpl1 bookCglib=(BookFacadeImpl1)cglib.getInstance(bookFacade);
bookCglib.addBook();
}
?
?

總結:

靜態代理是通過在代碼中顯式定義一個業務實現類一個代理,在代理類中對同名的業務方法進行包裝,用戶通過代理類調用被包裝過的業務方法;

JDK動態代理是通過接口中的方法名,在動態生成的代理類中調用業務實現類的同名方法;

CGlib動態代理是通過繼承業務類,生成的動態代理類是業務類的子類,通過重寫業務方法進行代理;

復習寶典之設計模式