草庐IT

设计模式 08 代理模式

程序航 2023-03-28 原文

代理模式(Proxy Pattern)属于结构型模式

概述

代理模式就是一个代理对象来间接访问对象,常用于无法直接访问某个对象或访问某个对象不方便的情况。

实际上代理在生活中处处都存在,比如房屋中介就是代理,Apple 的授权经销商就是代理,访问国外网站所用的代理服务器也是代理,Spring 框架的 AOP 也是通过代理模式实现的。

这些代理都有一个共同特点,就是使用的一致性和中间环节的透明性,也就是说找代理做的事情需要与找对象本身做的事情是一样的,只是中间环节隐藏了而已。

代理模式分为静态代理动态代理

静态代理

静态代理一般包含以下角色:

  • 动作 : 一般使用接口或者抽象类来实现。
  • 真实角色 : 被代理的角色。
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作。
  • 客户 : 使用代理角色来进行一些操作。

代码实现1

1、定义租赁操作

/**
 * 租赁操作
 */
public interface Rent {

    /**
     * 租房
     */
    void rentHouse();
}

2、定义房东

/**
 * 房东
 */
public class Landlord implements Rent{

    @Override
    public void rentHouse() {
        System.out.println("房东出租房子");
    }
}

3、定义中介

/**
 * 中介
 */
public class Intermediary implements Rent{

    /**
     * 房东
     */
    private Landlord landlord;

    public Intermediary() {
    }

    public Intermediary(Landlord landlord) {
        this.landlord = landlord;
    }

    @Override
    public void rentHouse() {
        // 看房
        seeHouse();
        // 签合同
        contract();
        // 租房
        landlord.rentHouse();
        // 收取费用
        toll();
    }

    /**
     * 看房
     */
    public void seeHouse() {
        System.out.println("中介带你看房");
    }

    /**
     * 签合同
     */
    public void contract() {
        System.out.println("签租赁合同");
    }

    /**
     * 收取费用
     */
    public void toll() {
        System.out.println("收中介费");
    }
}

4、租客租房

// 房东
Landlord landlord = new Landlord();
// 中介给房东代理
Proxy proxy = new Proxy(landlord);
// 租房。不用面对房东,直接找中介租房即可
proxy.rentHouse();

在这个过程中,租客直接接触的是中介,见不到房东,但是租客依旧通过代理租到了房东的房子。

代码实现2

日常工作中最常见的就是增删改查业务,这里以实现增删改查代码的日志插入为例理解静态代理。

1、定义用户服务

/**
 * 用户服务
 */
public interface UserService {

    /**
     * 新增
     */
    public void add();

    /**
     * 删除
     */
    public void delete();

    /**
     * 修改
     */
    public void update();

    /**
     * 查询
     */
    public void query();
}

2、定义用户服务实现类

/**
 * 用户服务实现类
 */
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("新增了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}

3、定义用户服务代理类

/**
 * 用户服务代理类
 */
public class UserServiceProxy implements UserService {

    /**
     * 用户服务实现类
     */
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    @Override
    public void add() {
        log("add");
        userService.add();
    }

    @Override
    public void delete() {
        log("delete");
        userService.delete();
    }

    @Override
    public void update() {
        log("update");
        userService.update();
    }

    @Override
    public void query() {
        log("query");
        userService.query();
    }

    /**
     * 打印日志
     *
     * @param msg 消息
     */
    public void log(String msg) {
        System.out.println("使用了" + msg + "方法");
    }
}

4、客户端使用

// 用户服务实现类
UserServiceImpl userService = new UserServiceImpl();
// 用户服务代理类
UserServiceProxy proxy = new UserServiceProxy();
// 代理用户服务
proxy.setUserService(userService);
// 代理实现新增
proxy.add();
// 代理实现删除
proxy.delete();
// 代理实现修改
proxy.update();
// 代理实现查询
proxy.query();

执行结果为:

使用了add方法
新增了一个用户
使用了delete方法
删除了一个用户
使用了update方法
修改了一个用户
使用了query方法
查询了一个用户

使用代理类实现在不改动原有业务代码的情况下增加了日志。

优缺点

优点

1、可以使得真实角色更加轻松,不用再去关注一些琐碎的事情。

2、公共的业务由代理来完成,实现了业务的分工。

3、公共业务发生变化时扩展更加方便。

缺点

类变多了,多了代理类,工作量变大了,开发效率降低。


我们想要静态代理的优点,又不想要静态代理的缺点,所以 , 就有了动态代理

动态代理

  • 动态代理的角色和静态代理的一样。

  • 动态代理的代理类是动态生成的,静态代理的代理类是提前写好的。

  • 动态代理分为两类 : 一类是基于接口 , 一类是基于类

    • 基于接口的动态代理:JDK 动态代理。

    • 基于类的动态代理:CGLIB 动态代理。

    • 现在用的比较多的是 Javassist 来生成动态代理。

    • 这里使用 JDK 的原生代码来实现,其余的道理都是一样的。

JDK 的动态代理需要了解两个类:ProxyInvocationHandler。查看 JDK 帮助文档:

Proxy:代理类

Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现了接口 InvocationHandler

newProxyInstance 方法:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) throws IllegalArgumentException

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

参数:

  • loader - 类加载器来定义代理类。

  • interfaces - 代理类实现的接口列表。

  • h - 调度方法调用的调用处理函数。

返回值:

具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例。

异常:

IllegalArgumentException:非法参数异常。

InvocationHandler:调用处理程序

InvocationHandler 是由代理实例的调用处理程序实现的接口 。 每个代理实例都有一个关联的调用处理程序。

invoke 方法:

Object invoke(Object proxy,
              Method method,
              Object[] args) throws Throwable

处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。

参数:

  • proxy - 调用该方法的代理实例。
  • method - 所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
  • args - 包含的方法调用传递代理实例的参数值的对象的阵列,或 null 如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer 或 java.lang.Boolean。

代码实现1

抽象角色和真实角色和之前的一样。

1、定义租赁操作

/**
 * 租赁操作
 */
public interface Rent {

    /**
     * 租房
     */
    void rentHouse();
}

2、定义房东

/**
 * 房东
 */
public class Landlord implements Rent {

    @Override
    public void rentHouse() {
        System.out.println("房东出租房子");
    }
}

3、定义中介

/**
 * 中介
 */
public class Intermediary implements InvocationHandler {

    /**
     * 租赁操作
     */
    private Rent rent;

    /**
     * 代理租赁
     *
     * @param rent 需要租赁的对象
     */
    public void setRent(Rent rent) {
        this.rent = rent;
    }

    /**
     * 生成代理对象
     *
     * @return 代理对象
     */
    public Object getProxy() {

        // 重点是第二个参数,获取要代理的抽象角色,之前都是一个角色,现在可以代理一类角色
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

    /**
     * 处理代理实例上的方法调用并返回结果
     *
     * @param proxy  代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args   包含的方法调用传递代理实例的参数值的对象的阵列
     * @return 代理对象
     * @throws Throwable 错误
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 看房
        seeHouse();
        // 签合同
        contract();
        // 动态代理租房业务:本质利用反射实现
        Object result = method.invoke(rent, args);
        // 收取费用
        toll();
        return result;
    }

    /**
     * 看房
     */
    public void seeHouse() {
        System.out.println("中介带你看房");
    }

    /**
     * 签合同
     */
    public void contract() {
        System.out.println("签租赁合同");
    }

    /**
     * 收取费用
     */
    public void toll() {
        System.out.println("收中介费");
    }

}

4、租客租房

// 房东
Landlord landlord = new Landlord();
// 中介
Intermediary intermediary = new Intermediary();
// 中介给房东提供代理服务
intermediary.setRent(landlord);
// 动态生成对应的代理类
Rent proxy = (Rent) intermediary.getProxy();
// 代理类执行租房操作
proxy.rentHouse();

一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口。

代码实现2

使用动态代理再来实现前面的增删改查业务。

1、定义用户服务

/**
 * 用户服务
 */
public interface UserService {

    /**
     * 新增
     */
    public void add();

    /**
     * 删除
     */
    public void delete();

    /**
     * 修改
     */
    public void update();

    /**
     * 查询
     */
    public void query();
}

2、定义用户服务实现类

/**
 * 用户服务实现类
 */
public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("新增了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void query() {
        System.out.println("查询了一个用户");
    }
}

3、定义用户服务代理类

/**
 * 用户服务代理类
 */
public class UserServiceProxy implements InvocationHandler {

    /**
     * 目标对象
     */
    private Object target;

    /**
     * 代理目标对象
     *
     * @param target 目标对象
     */
    public void setTarget(Object target) {
        this.target = target;
    }

    /**
     * 生成代理对象
     *
     * @return 代理对象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    /**
     * 处理代理实例上的方法调用并返回结果
     *
     * @param proxy  代理类
     * @param method 代理类的调用处理程序的方法对象
     * @param args   包含的方法调用传递代理实例的参数值的对象的阵列
     * @return 代理对象
     * @throws Throwable 错误
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 打印日志
        log(method.getName());
        // 执行代理方法
        return method.invoke(target, args);
    }

    /**
     * 打印日志
     * @param msg 消息
     */
    public void log(String msg) {
        System.out.println("使用了" + msg + "方法");
    }
}

4、客户端使用

// 用户服务实现类
UserServiceImpl userService = new UserServiceImpl();
// 用户服务代理类
UserServiceProxy userServiceProxy = new UserServiceProxy();
// 代理用户服务
userServiceProxy.setTarget(userService);
// 动态生成代理对象
UserService proxy = (UserService) userServiceProxy.getProxy();
// 代理实现新增
proxy.add();
// 代理实现删除
proxy.delete();
// 代理实现修改
proxy.update();
// 代理实现查询
proxy.query();

执行结果为:

使用了add方法
新增了一个用户
使用了delete方法
删除了一个用户
使用了update方法
修改了一个用户
使用了query方法
查询了一个用户

这样就用动态代理实现了增删改查业务,而且由于目标对象为 Object,需要代理其他类时只需要转化为对应的类即可,十分易于扩展。

优缺点

优点

1、静态代理有的它都有,静态代理没有的,它也有。

2、可以使得真实角色更加轻松,不用再去关注一些琐碎的事情。

3、公共的业务由代理来完成,实现了业务的分工。

4、公共业务发生变化时扩展更加方便。

5、动态代理可以代理一类业务。

6、动态代理可以代理多个类,代理的是接口。

缺点

1、需要对实现动态代理的类和方法有一定了解,学习成本较静态代理更高。

2、动态代理的使用逻辑更为复杂,不如静态代理好理解。

使用场景

按职责来划分,通常有以下使用场景:

1、远程代理。

2、虚拟代理。

3、Copy-on-Write 代理。

4、保护(Protect or Access)代理。

5、Cache代理。

6、防火墙(Firewall)代理。

7、同步化(Synchronization)代理。

8、智能引用(Smart Reference)代理。

注意事项

1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。

2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。


参考

https://www.bilibili.com/video/BV1mc411h719?p=9&vd_source=299f4bc123b19e7d6f66fefd8f124a03

有关设计模式 08 代理模式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  9. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

  10. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

随机推荐