設計模式16-代理模式

概念


定義

爲其他對象提供一種代理以控制對這個對象的訪問。本質:“控制對象訪問

結構

該模式有三種角色。代理對象,目標接口,具體的目標對象。

代理對象:

1.代理對象需要和具體的目標對象一樣實現目標接口
2.代理對象要持有具體目標對象的引用,還要提供給用戶傳入具體目標對象的入口
3.代理對象的方法,可以控制對具體目標的訪問

目標接口:

定義代理和具體對象的接口,這樣代理和具體對象都是其子類,纔可以在需要使用目標對象的時候使用代理對象

目標對象:

真正的對象,用戶真正想要調用的方法在這裏,也是真正實現目標接口要求的業務功能的

這裏寫圖片描述

關鍵思想

代理模式較爲複雜,且有多種不同的變形。但其核心部分是,代理和真正的目標對象都實現同一個接口,而這個接口,主要是用來約束,且提供可以代換的功能。邏輯稍微複雜點的是代理類

小實例


目標接口

/**
 * 抽象的目標接口,定義具體的目標對象和代理公用的接口
 */
public interface Subject {
    /**
     * 示意方法:一個抽象的請求方法
     */
    public void request();
}

真正目標對象

/**
 * 具體的目標對象,是真正被代理的對象
 */
public class RealSubject implements Subject{

    public void request() {
        //執行具體的功能處理
    }

}

代理對象

/**
 * 代理對象
 */
public class Proxy implements Subject{
    /**
     * 持有被代理的具體的目標對象
     */
    private RealSubject realSubject=null;
    /**
     * 構造方法,傳入被代理的具體的目標對象
     * @param realSubject 被代理的具體的目標對象
     */
    public Proxy(RealSubject realSubject){
        this.realSubject = realSubject;
    }

    public void request() {
        //在轉調具體的目標對象前,可以執行一些功能處理

        //轉調具體的目標對象的方法
        realSubject.request();

        //在轉調具體的目標對象後,可以執行一些功能處理
    }

}

實例一


這是一種一對多映射關係。數據庫經常需要使用到級聯查詢,以查出另外的對象。如:有一個部門表和員工表。一個部門下有多個員工,員工表存儲着部門表外鍵id。現在需要查出某個部分下所有員工的信息

不使用設計模式

javaBean類

/**
 * 描述用戶數據的對象
 */
public class UserModel {    
    /**
     * 用戶編號
     */
    private String userId;
    /**
     * 用戶姓名
     */
    private String name;
    /**
     * 部門編號
     */
    private String depId;
    /**
     * 性別
     */
    private String sex;

    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDepId() {
        return depId;
    }
    public void setDepId(String depId) {
        this.depId = depId;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString(){
        return "userId="+userId+",name="+name+",depId="+depId+",sex="+sex+"\n";
    }
}

dao層類

/**
 * 實現示例要求的功能
 */
public class UserManager {

    /**
     * 根據部門編號來獲取該部門下的所有人員
     * @param depId 部門編號
     * @return 該部門下的所有人員
     */
    public Collection<UserModel> getUserByDepId(String depId)throws Exception{
        Collection<UserModel> col = new ArrayList<UserModel>();
        Connection conn = null;
        try{
            conn = this.getConnection();
            String sql = "select * from tbl_user u,tbl_dep d "
                +"where u.depId=d.depId and d.depId like ?";

            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, depId+"%");

            ResultSet rs = pstmt.executeQuery();
            while(rs.next()){
                UserModel um = new UserModel();
                um.setUserId(rs.getString("userId"));
                um.setName(rs.getString("name"));
                um.setDepId(rs.getString("depId"));
                um.setSex(rs.getString("sex"));

                col.add(um);
            }

            rs.close();
            pstmt.close();
        }finally{
            conn.close();
        }
        return col;
    }
    /**
     * 獲取與數據庫的連接
     * @return 數據庫連接
     */
    private Connection getConnection() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        return DriverManager.getConnection(
                "jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
    }
}

用戶類

public class Client {
    public static void main(String[] args) throws Exception{
        UserManager userManager = new UserManager();
        Collection<UserModel> col = userManager.getUserByDepId("0101");
        System.out.println(col);
    }
}

上面的例子是及時加載的,不管查出多少條,連帶用戶信息都會被查出來。如果一次性數據較多,內存開銷會很大,而且會產生浪費。

所以Hibernate使用了代理模式,實現了懶加載,這是我認爲很經典的一個關於代理模式的實例

下面使用的實例,是學習視頻中的例子。包含懶加載和代理的思想。

關鍵步驟:
1.代理類和javaBean類都實現目標接口
2.目標接口包含了javaBean的所有getset方法
3.代理類實現了目標接口,所以實現了所有getset方法。當有一個接口,傳入真正的目標對象(JavaBean)時,用戶調用代理類的getset方法時,可以進行控制獲取屬性。如果調用代理對象不需要懶加載的屬性,那麼直接調用目標對象(JavaBean)的相應get/set方法即可。如果是需要懶加載的屬性,那麼就重置代理類內的目標對象(JavaBean),去數據庫獲取該屬性的數據,然後在返回給用戶,這個時候纔有去數據庫加載數據的動作,所以纔是懶加載

使用設計模式

目標接口

/**
 * 定義用戶數據對象的接口
 */
public interface UserModelApi {
    public String getUserId();
    public void setUserId(String userId);
    public String getName();
    public void setName(String name);
    public String getDepId();
    public void setDepId(String depId);
    public String getSex();
    public void setSex(String sex);
}

實際目標對象

/**
 * 描述用戶數據的對象
 */
public class UserModel implements UserModelApi{ 
    /**
     * 用戶編號
     */
    private String userId;
    /**
     * 用戶姓名
     */
    private String name;
    /**
     * 部門編號
     */
    private String depId;
    /**
     * 性別
     */
    private String sex;

    public String getUserId() {
        return userId;
    }
    public void setUserId(String userId) {
        this.userId = userId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDepId() {
        return depId;
    }
    public void setDepId(String depId) {
        this.depId = depId;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString(){
        return "userId="+userId+",name="+name+",depId="+depId+",sex="+sex+"\n";
    }
}

加載類,不涉及需要外連接其他對象的數據,即不需要懶加載,使用代理的數據

/**
 * 實現示例要求的功能
 */
public class UserManager {  
    /**
     * 根據部門編號來獲取該部門下的所有人員
     * @param depId 部門編號
     * @return 該部門下的所有人員
     */
    public Collection<UserModelApi> getUserByDepId(String depId)throws Exception{
        Collection<UserModelApi> col = new ArrayList<UserModelApi>();
        Connection conn = null;
        try{
            conn = this.getConnection();
            //只需要查詢userId和name兩個值就可以了
            String sql = "select u.userId,u.name "
                +"from tbl_user u,tbl_dep d "
                +"where u.depId=d.depId and d.depId like ?";

            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, depId+"%");

            ResultSet rs = pstmt.executeQuery();
            while(rs.next()){
                //這裏是創建的代理對象,而不是直接創建UserModel的對象
                Proxy proxy = new Proxy(new UserModel());
                //只是設置userId和name兩個值就可以了
                proxy.setUserId(rs.getString("userId"));
                proxy.setName(rs.getString("name"));

                col.add(proxy);
            }

            rs.close();
            pstmt.close();
        }finally{
            conn.close();
        }
        return col;
    }
    /**
     * 獲取與數據庫的連接
     * @return 數據庫連接
     */
    private Connection getConnection() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        return DriverManager.getConnection(
                "jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
    }
}

代理對象

/**
 * 代理對象,代理用戶數據對象
 */
public class Proxy implements UserModelApi{
    /**
     * 持有被代理的具體的目標對象
     */
    private UserModel realSubject=null;
    /**
     * 構造方法,傳入被代理的具體的目標對象
     * @param realSubject 被代理的具體的目標對象
     */
    public Proxy(UserModel realSubject){
        this.realSubject = realSubject;
    }
    /**
     * 標示是否已經重新裝載過數據了
     */
    private boolean loaded = false;


    public String getUserId() {
        return realSubject.getUserId();
    }
    public void setUserId(String userId) {
        realSubject.setUserId(userId);
    }
    public String getName() {
        return realSubject.getName();
    }
    public void setName(String name) {
        realSubject.setName(name);
    }


    public void setDepId(String depId) {
        realSubject.setDepId(depId);
    }
    public void setSex(String sex) {
        realSubject.setSex(sex);
    }

    public String getDepId() {
        //需要判斷是否已經裝載過了
        if(!this.loaded){
            //從數據庫中重新裝載
            reload();
            //設置重新裝載的標誌爲true
            this.loaded = true;
        }
        return realSubject.getDepId();
    }   
    public String getSex() {
        if(!this.loaded){
            reload();
            this.loaded = true;
        }
        return realSubject.getSex();
    }

    /**
     * 重新查詢數據庫以獲取完整的用戶數據
     */
    private void reload(){
        System.out.println("重新查詢數據庫獲取完整的用戶數據,userId=="+realSubject.getUserId());
        Connection conn = null;
        try{
            conn = this.getConnection();
            String sql = "select * from tbl_user where userId=? ";

            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, realSubject.getUserId());

            ResultSet rs = pstmt.executeQuery();
            if(rs.next()){
                //只需要重新獲取除了userId和name外的數據
                realSubject.setDepId(rs.getString("depId"));
                realSubject.setSex(rs.getString("sex"));
            }

            rs.close();
            pstmt.close();
        }catch(Exception err){
            err.printStackTrace();
        }finally{
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public String toString(){
        return "userId="+getUserId()+",name="+getName()
        +",depId="+getDepId()+",sex="+getSex()+"\n";
    }

    private Connection getConnection() throws Exception {
        Class.forName("oracle.jdbc.driver.OracleDriver");
        return DriverManager.getConnection(
                "jdbc:oracle:thin:@localhost:1521:orcl", "test", "test");
    }
}

用戶類

public class Client {
    public static void main(String[] args) throws Exception{
        UserManager userManager = new UserManager();
        Collection<UserModelApi> col = userManager.getUserByDepId("0101");

        //如果只是顯示用戶名稱,那麼不需要重新查詢數據庫
        for(UserModelApi umApi : col){
            System.out.println("用戶編號:="+umApi.getUserId()+",用戶姓名:="+umApi.getName());
        }
        //如果訪問非用戶編號和用戶姓名外的屬性,那就會重新查詢數據庫
        for(UserModelApi umApi : col){
            System.out.println("用戶編號:="+umApi.getUserId()+",用戶姓名:="+umApi.getName()+",所屬部門:="+umApi.getDepId());
        }
    }
}

懶加載是以時間換空間的形式,及時加載,是以空間換時間的形式
Lazy Load===〉實現機制就是 代理

代理模式相關信息


代理模式是通過創建一個代理對象,用這個代理對象去代表真正的對象,客戶端得到這個代理對象後,對客戶端沒有任何影響,跟得到真正的對象一樣。
客戶端操作代理對象,真正的功能還是由真實的對象完成,不過通過代理進行操作,所以是客戶端操作代理,代理操作真正對象。
因爲代理對象在客戶端和真正對象的中間,是一箇中裝,所以他可以有很多種變形,分類

代理的分類

1.虛代理,即上面的懶加載。通常會用於需要創建開銷很大的對象,且該對象只在需要使用的時候才被真正創建
2.遠程代理。即在不同的地址空間中代表同一對象,不同的地址空間,就是不一樣的機器,不一定在本機,即遠程調用。java典型的RMI技術就是這個
3.copy-on-write代理。只有在對象真正改變了,纔會拷貝目標對象,是虛代理的一個分支
4.保護代理:控制對原始對象的訪問,控制他們的訪問權限
5.緩存代理:爲那些昂貴的操作結果提供臨時存儲空間,以便多個客戶端可以共享結果
6.防火牆代理:保護對象不被惡意用戶訪問和操作
7.同步代理:使多個用戶能同時訪問目標對象且沒有衝突
8.智能指引:當目標接口被引用是,進行額外的動作,比如計算一個對象被引用的額次數

常用的代理有:虛代理,保護代理,遠程代理和智能指引。本篇會有虛代理和保護代理,另外會弄個遠程代理,關於java的RMI技術。

遠程代理:控制訪問遠程對象
虛代理:控制訪問創建開銷大的資源
保護代理:基於權限控制對資源的訪問

保護代理


保護代理是一種權限控制對原始對象訪問的代理,多用與對象應該有不同的訪問權限的時候。保護代理會檢查調用目標對象,從而實現對目標對象的保護

實例二

實例說明:有一個訂單系統,需求是:訂單一旦被創建,只有訂單創建人可以修改訂單數據,其他人不能修改
所以把訂單當做一個訂單對象實例。那麼代理對象只需要控制外部對它的訪問即可。

目標接口

/**
 * 訂單對象的接口定義
 */
public interface OrderApi {
    /**
     * 獲取訂單訂購的產品名稱
     * @return 訂單訂購的產品名稱
     */
    public String getProductName();
    /**
     * 設置訂單訂購的產品名稱
     * @param productName 訂單訂購的產品名稱
     * @param user 操作人員
     */
    public void setProductName(String productName,String user);
    /**
     * 獲取訂單訂購的數量
     * @return 訂單訂購的數量
     */
    public int getOrderNum();
    /**
     * 設置訂單訂購的數量
     * @param orderNum 訂單訂購的數量
     * @param user 操作人員
     */
    public void setOrderNum(int orderNum,String user);
    /**
     * 獲取創建訂單的人員
     * @return 創建訂單的人員
     */
    public String getOrderUser();
    /**
     * 設置創建訂單的人員
     * @param orderUser 創建訂單的人員
     * @param user 操作人員
     */
    public void setOrderUser(String orderUser,String user);
}

代理類

/**
 * 訂單的代理對象
 */
public class OrderProxy implements OrderApi{
    /**
     * 持有被代理的具體的目標對象
     */
    private Order order=null;
    /**
     * 構造方法,傳入被代理的具體的目標對象
     * @param realSubject 被代理的具體的目標對象
     */
    public OrderProxy(Order realSubject){
        this.order = realSubject;
    }
    public void setProductName(String productName,String user) {
        //控制訪問權限,只有創建訂單的人員才能夠修改
        if(user!=null && user.equals(this.getOrderUser())){
            order.setProductName(productName, user);
        }else{
            System.out.println("對不起"+user+",您無權修改訂單中的產品名稱。");
        }
    }
    public void setOrderNum(int orderNum,String user) {
        //控制訪問權限,只有創建訂單的人員才能夠修改
        if(user!=null && user.equals(this.getOrderUser())){
            order.setOrderNum(orderNum, user);
        }else{
            System.out.println("對不起"+user+",您無權修改訂單中的訂購數量。");
        }
    }
    public void setOrderUser(String orderUser,String user) {
        //控制訪問權限,只有創建訂單的人員才能夠修改
        if(user!=null && user.equals(this.getOrderUser())){
            order.setOrderUser(orderUser, user);
        }else{
            System.out.println("對不起"+user+",您無權修改訂單中的訂購人。");
        }
    }
    public int getOrderNum() {
        return this.order.getOrderNum();
    }
    public String getOrderUser() {
        return this.order.getOrderUser();
    }
    public String getProductName() {
        return this.order.getProductName();
    }
    public String toString(){
        return "productName="+this.getProductName()+",orderNum="+this.getOrderNum()+",orderUser="+this.getOrderUser();
    }
}

測試類

public class Client {
    public static void main(String[] args) {
        //張三先登錄系統創建了一個訂單
        OrderApi order = new OrderProxy(new Order("設計模式",100,"張三"));
        //李四想要來修改,那就會報錯
        order.setOrderNum(123, "李四");
        //輸出order
        System.out.println("李四修改後訂單記錄沒有變化:"+order);
        //張三修改就不會有問題
        order.setOrderNum(123, "張三");
        //再次輸出order
        System.out.println("張三修改後,訂單記錄:"+order);
    }
}

java中的代理

java中有兩種大概念的代理,一爲靜態代理,二爲動態代理
靜態代理:即前面所有自己實現的代理模式,都是靜態代理。但是如果Subject接口一旦發生變化,代理類和目標實例都需要變化,靈活度不夠。
動態代理:使用Java內建對代理模式支持的功能來實現的代理叫動態代理。與靜態相比,區別在於:

1. 靜態代理Subject接口定義的方法,代理類實現Subject接口,就要都實現這些方法。
2. 動態代理始終只有invoke方法,當Subject接口發生變化時,動態代理的接口就不需要變化
3. 動態代理要實現InvocationHandler接口,需要實現invoke方法。同時需要有一個方法,獲取目標接口。看似獲取目標接口,但是實際上在這個方法中有進行綁定,所以獲取的是代理對象

小實例

目標接口

/**
 * 訂單對象的接口定義
 */
public interface OrderApi {
    /**
     * 獲取訂單訂購的產品名稱
     * @return 訂單訂購的產品名稱
     */
    public String getProductName();
    /**
     * 設置訂單訂購的產品名稱
     * @param productName 訂單訂購的產品名稱
     * @param user 操作人員
     */
    public void setProductName(String productName,String user);
    /**
     * 獲取訂單訂購的數量
     * @return 訂單訂購的數量
     */
    public int getOrderNum();
    /**
     * 設置訂單訂購的數量
     * @param orderNum 訂單訂購的數量
     * @param user 操作人員
     */
    public void setOrderNum(int orderNum,String user);
    /**
     * 獲取創建訂單的人員
     * @return 創建訂單的人員
     */
    public String getOrderUser();
    /**
     * 設置創建訂單的人員
     * @param orderUser 創建訂單的人員
     * @param user 操作人員
     */
    public void setOrderUser(String orderUser,String user);
}

目標實例

/**
 * 訂單對象
 */
public class Order implements OrderApi{
    /**
     * 訂單訂購的產品名稱
     */
    private String productName;
    /**
     * 訂單訂購的數量
     */
    private int orderNum;
    /**
     * 創建訂單的人員
     */
    private String orderUser;

    /**
     * 構造方法,傳入構建需要的數據
     * @param productName 訂單訂購的產品名稱
     * @param orderNum 訂單訂購的數量
     * @param orderUser 創建訂單的人員
     */
    public Order(String productName,int orderNum,String orderUser){
        this.productName = productName;
        this.orderNum = orderNum;
        this.orderUser = orderUser;
    }

    public String getProductName() {
        return productName;
    }
    public void setProductName(String productName,String user) {
        this.productName = productName;
    }
    public int getOrderNum() {
        return orderNum;
    }
    public void setOrderNum(int orderNum,String user) {
        this.orderNum = orderNum;
    }
    public String getOrderUser() {
        return orderUser;
    }
    public void setOrderUser(String orderUser,String user) {
        this.orderUser = orderUser;
    }
    public String toString(){
        return "productName="+this.getProductName()+",orderNum="+this.getOrderNum()+",orderUser="+this.getOrderUser();
    }
}

代理類

/**
 * 使用Java中的動態代理
 */
public class DynamicProxy implements InvocationHandler{
    /**
     * 被代理的對象
     */
    private OrderApi order = null;
    /**
     * 獲取綁定好代理和具體目標對象後的目標對象的接口
     * @param order 具體的訂單對象,相當於具體目標對象
     * @return 綁定好代理和具體目標對象後的目標對象的接口
     */
    public OrderApi getProxyInterface(Order order){
        //設置被代理的對象,好方便invoke裏面的操作
        this.order = order;
        //把真正的訂單對象和動態代理關聯起來
        OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
                order.getClass().getClassLoader(),
//獲得這個對象所實現的接口。 
            order.getClass().getInterfaces(), 
                this);
        return orderApi;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //如果是調用setter方法就需要檢查權限
        if(method.getName().startsWith("set")){
            //如果不是創建人,那就不能修改
            if(order.getOrderUser()!=null && order.getOrderUser().equals(args[1])){
                //可以操作
                return method.invoke(order, args);
            }else{
                System.out.println("對不起,"+args[1]+",您無權修改本訂單中的數據");
            }
        }else{
            //不是調用的setter方法就繼續運行
            return method.invoke(order, args);
        }
        return null;
    }
}

代碼解析

OrderApi orderApi = (OrderApi) Proxy.newProxyInstance(
            order.getClass().getClassLoader(),
        //獲得這個對象所實現的接口。 
        order.getClass().getInterfaces(), 
            this);
newProxyInstance方法創建代理,裏面需要傳入目標對象(不是目標接口)的類加載器,
目標接口(不是目標接口)的所實現的對象的接口


public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
每次代理類proxy的所有方法被訪問,該方法都會被會提前調用。

測試類

public class Client {
    public static void main(String[] args) {
        //張三先登錄系統創建了一個訂單
        Order order = new Order("設計模式",100,"張三");

        //創建一個動態代理
        DynamicProxy dynamicProxy = new DynamicProxy();     
        //然後把訂單和動態代理關聯起來
        OrderApi orderApi = dynamicProxy.getProxyInterface(order);

        //以下就需要使用被代理過的接口來操作了
        //李四想要來修改,那就會報錯
        orderApi.setOrderNum(123, "李四");
        //輸出order
        System.out.println("李四修改後訂單記錄沒有變化:"+orderApi);
        //張三修改就不會有問題
        orderApi.setOrderNum(123, "張三");
        //再次輸出order
        System.out.println("張三修改後,訂單記錄:"+orderApi);

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章