设计模式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);

    }
}

猜你喜欢

转载自blog.csdn.net/qq_32020035/article/details/81023529