代理模式 (设计模式)
几天开始学习另外一种类型的设计模式: 结构型模式。
结构型模式,主要包括,门面,代码,适配,装饰,桥接,享元模式。
在设计模式中,结构型模式和行为型模式是两个不同的概念,它们分别关注于软件设计中的不同方面。
-
结构型模式(Structural Patterns):
- 结构型模式关注的是类与对象之间的组合,通过使用继承和组合来形成更大的结构,从而解决软件设计中的对象组织和关系问题。
- 这些模式主要用于处理类或对象的组合,以便在运行时实现新的功能或适应不同的场景。
- 典型的结构型模式包括:适配器模式、装饰器模式、代理模式、组合模式、桥接模式、享元模式。
-
行为型模式(Behavioral Patterns):
- 行为型模式关注的是对象之间的通信和职责分配,它们解决的是对象之间的协作问题,以实现更加灵活和可扩展的设计。
- 这些模式主要用于描述对象之间的交互、职责分配和算法的使用。
- 典型的行为型模式包括:模板方法模式、策略模式、命令模式、责任链模式、状态模式、观察者模式、中介者模式、访问者模式、备忘录模式、解释器模式。
代理模式:
代理模式的原理和代码实现都不难掌握。它在不改变原始类(或者被代理类)代码的情况下,通过引入代理类来给原始类附加功能。我们通过例子来解释一下这段话。
比如我们在前边中讲到的性能技术器。 当我们开发了一个MetricsController 类,用来收集接口请求的原始数据,比如访问时间,处理市场等,在业务系统中,我们采用如下方式使用这个类
public class UserController { //...省略其他属性和方法... private MetricsCollector metricsCollector; // 依赖注入 public UserVo login(String telephone, String password) { long startTimestamp = System.currentTimeMillis(); // ... 省略login逻辑... long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimes metricsCollector.recordRequest(requestInfo); //...返回UserVo数据... } public UserVo register(String telephone, String password) { long startTimestamp = System.currentTimeMillis(); // ... 省略register逻辑... long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("register", responseTime, startTi metricsCollector.recordRequest(requestInfo); //...返回UserVo数据... } }
很明显上边代码有两个问题,第一,性能计数器框架代码侵入到业务代码中,跟业务代码高度耦合。如果未来需要替换这个框架,那替换的成本比较大。第二,手机接口请求的代码跟业务代码无关,本就不应该放到一个类中。业务类最好职责更加单一。
为了将框架代码和业务代码解耦,代理模式就排上了用场。代理类UserControllerProxy 和原始UserConterooler 实现了相同的接口IUserControler .UserController 赋值业务的功能,代理类UserControlerProxy 负责业务代码中执行其他附加逻辑,并通过委托的方式调用原始类来执行业务代码。 具体的代码实现如下所示:
public interface IUserController { UserVo login(String telephone, String password); UserVo register(String telephone, String password); } public class UserController implements IUserController { //...省略其他属性和方法... @Override public UserVo login(String telephone, String password) { //...省略login逻辑... //...返回UserVo数据... } @Override public UserVo register(String telephone, String password) { //...省略register逻辑... //...返回UserVo数据... } } public class UserControllerProxy implements IUserController { private MetricsCollector metricsCollector; private UserController userController; public UserControllerProxy(UserController userController) { this.userController = userController; this.metricsCollector = new MetricsCollector(); @Override public UserVo login (String telephone, String password){ long startTimestamp = System.currentTimeMillis(); // 委托 UserVo userVo = userController.login(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("login", responseTime, startTimes metricsCollector.recordRequest(requestInfo); return userVo; } @Override public UserVo register (String telephone, String password){ long startTimestamp = System.currentTimeMillis(); UserVo userVo = userController.register(telephone, password); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; RequestInfo requestInfo = new RequestInfo("register", responseTime, startTi metricsCollector.recordRequest(requestInfo); return userVo; } } //UserControllerProxy使用举例 //因为原始类和代理类实现相同的接口,是基于接口而非实现编程 //将UserController类对象替换为UserControllerProxy类对象,不需要改动太多代码 IUserController userController = new UserControllerProxy(new UserController() }
参照基于接口而非实现的编程设计思想,将原始类对象替换为代理类对象的时候,为了让代码改动尽量少,在刚刚的代理模式的代码实现中,代理类和原始类都实现相同的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的(它来自一个第三方库) 我们也没有办法,之际修改原始类,给它重新定义一个接口,在这种情况下,我们该如何实现代理模式呢?
对于这种外部类的扩展,我们一般都是采用继承的方式,这也不例外。我们让代理类继承原始类,然后扩展附加功能。原理很简单。
动态代理的原理解析
不过,刚刚的代码实现,还是有点问题,一方面,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要天际的附加功能的类不止一个。我们需要针对每个类都创建一个代理类。
如果有50 个类都要附加功能呢的原始类,那我们就要创建50 个对应的代理类。者会导致项目中类的个数成本增加,增加了代码维护成本,并且,每个代理类中的代码都有点像模板的样子重复,也增加了不必要的开发成本。那么这个问题如何解决。
我们可以用动态代理来解决这个问题,所谓动态代理,就是我们不需要事先为么个原始类编写代理类,而是在运行的时候,动态的创建原始类的代理类,然后在系统中代理类替换掉原始类,那如何实现动态代理?
如果你熟悉的是java 语言,那么动态代理实现是一件非常简单的事情,因为java 语言本身已经提供了动态代理的语法,(实际上,动态代理地城依赖的就是java de 反射语法)下边是动态代理的例子
public class MetricsCollectorProxy { private MetricsCollector metricsCollector; public MetricsCollectorProxy() { this.metricsCollector = new MetricsCollector(); } public Object createProxy(Object proxiedObject) { Class<?>[] interfaces = proxiedObject.getClass().getInterfaces(); DynamicProxyHandler handler = new DynamicProxyHandler(proxiedObject); return Proxy.newProxyInstance(proxiedObject.getClass().getClassLoader(), in } private class DynamicProxyHandler implements InvocationHandler { private Object proxiedObject; public DynamicProxyHandler(Object proxiedObject) { this.proxiedObject = proxiedObject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Th long startTimestamp = System.currentTimeMillis(); Object result = method.invoke(proxiedObject, args); long endTimeStamp = System.currentTimeMillis(); long responseTime = endTimeStamp - startTimestamp; String apiName = proxiedObject.getClass().getName() + ":" + method.getNam RequestInfo requestInfo = new RequestInfo(apiName, responseTime, startTim metricsCollector.recordRequest(requestInfo); return result; } }
实际上apo 底层的实现原理就是基于动态代理。 用户配置好需要给那些类创建代理,并定义好执行原始的业务代码前后执行那些附加功能,Spring 为这类创建动态代理。 Spring 为这些类创建动态代理,并在Jvm 中替原始的对象,原本在代码中执行的原始类的方法,被换做执行代理类的方法,也就是实现给原始类添加附加功能的目的。
代理模式的应用场景:
代理模式最常用的一个应用场景就是,在业务系统中开发一些非功能性的需求,比如将恐,统计,健全,限流,事务,幂等,日志,
实际上rpc ,缓存中的应用。 RPC 它也可以成为一种代码,我们把这种代理称之为远程代理。