设计模式笔记--代理模式

news/2024/7/6 6:31:30 标签: 设计模式, 代理模式

常用设计模式有23中,分为:

创建型模式(主要用于创建对象)

1、单例模式    2、工厂方法模式    3、抽象工厂模式    4、建造者模式     5、原型模式 
行为型模式 (主要用于描述对象或类是怎样交互和怎样分配职责)

1、模板方法模式  2、中介者模式  3、命令模式    4、责任链模式   5、策略模式   6、迭代器模式  

7、观察者模式      8、备忘录模式   9、访问者模式   10、状态模式   11、解释器模式

结构型模式(主要用于处理类或对象的组合)

1、代理模式  2、装饰模式   3、适配器模式   4、组合模式   5、外观模式(门面模式)   6、享元模式    7、桥梁模式




定义:

为其他对象提供一种代理以控制对这个对象的访问。  

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制  




类图中的三个角色的定义:

● Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。

● RealSubject具体主题角色
也叫做被委托角色、被代理角色。它才是冤大头,是业务逻辑的具体执行者。

● Proxy代理主题角色
也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。  

通用源码  

<span style="font-size:18px;">代码清单12-6 抽象主题类
public interface Subject {
//定义一个方法
public void request();
}</span>
在接口中我们定义了一个方法request来作为方法的代表,


RealSubject对它进行实现,如代码清单12-7所示。
<span style="font-size:18px;">public class RealSubject implements Subject {
//实现方法
public void request() {
//业务逻辑处理
}
}
</span>
RealSubject是一个正常的业务实现类,代理模式的核心就在代理类上,如代码清单12-8所示。


<span style="font-size:18px;">代码清单12-8 代理类
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
public Proxy(){
this.subject = new Proxy();
}
//通过构造函数传递代理者
public Proxy(Object...objects ){
}
//实现接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something
}
//善后处理
private void after(){
//do something
}
}</span>

一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的。  


优点   

● 职责清晰  
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。  

● 高扩展性  
具体主题角色是随时都会发生变化的, 但代理类完全就可以在不做任何修改的情况下使用。 

● 智能化  
动态代理中体现了 


实例:代练打游戏



接口IGamePlayer  
<span style="font-size:18px;">代码清单12-1 游戏者接口
public interface IGamePlayer {
//登录游戏
public void login(String user,String password);
//杀怪,网络游戏的主要特色
public void killBoss();
//升级
public void upgrade();
}</span>
定义了三个方法,分别是我们在网络游戏中最常用的功能:登录游戏、杀怪和升级,


游戏者  
<span style="font-size:18px;">public class GamePlayer implements IGamePlayer {
private String name = "";
//通过构造函数传递名称
public GamePlayer(String _name){
this.name = _name;
}
//打怪,最期望的就是杀老怪
public void killBoss() {
System.out.println(this.name + "在打怪!");
}
//进游戏之前你肯定要登录吧,这是一个必要条件
public void login(String user, String password) {
System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
}
//升级,升级有很多方法,花钱买是一种,做任务也是一种
public void upgrade() {
System.out.println(this.name + " 又升了一级!");
}
}</span>
在实现类中通过构造函数传递进来玩家姓名,方便进行后期的调试工作  


GamePlayerProxy类来代表游戏代练者
<span style="font-size:18px;">代码清单12-4 代练者
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
}</span>


场景类 
<span style="font-size:18px;">public class Client {
public static void main(String[] args) {
//定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("张三");
//然后再定义一个代练者
IGamePlayer proxy = new GamePlayerProxy(player);
//开始打游戏,记下时间戳
System.out.println("开始时间是:2009-8-25 10:45");
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2009-8-26 03:40");
}
}</span>

 
代理模式的扩展  

1)普通代理  

我们设计模式中的普通代理和强制代理也是类似的一种结构,

普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;
强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定
 
普通代理它的要求就是客户端只能访问代理角色,而不能访问真实角色  


普通代理的游戏者
<span style="font-size:18px;">public class GamePlayer implements IGamePlayer {
private String name = "";
//构造函数限制谁能创建对象,并同时传递姓名
public GamePlayer(IGamePlayer _gamePlayer,String _name) throws Exception{
if(_gamePlayer == null ){
throw new Exception("不能创建真实角色!");
}else{
this.name = _name;
}
}
//打怪,最期望的就是杀老怪
public void killBoss() {
System.out.println(this.name + "在打怪!");
}
//进游戏之前你肯定要登录吧,这是一个必要条件
public void login(String user, String password) {
System.out.println("登录名为"+user + "的用户" + this.name + "登录成功!");
}
//升级,升级有很多方法,花钱买是一种,做任务也是一种
public void upgrade() {
System.out.println(this.name + " 又升了一级!");
}
}</span>
在构造函数中,传递进来一个IGamePlayer对象,检查谁能创建真实的角色,当然还可以有其他的限制,比如类名必须为Proxy类等  


普通代理的代理者
<span style="font-size:18px;">public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(String name){
try {
gamePlayer = new GamePlayer(this,name);
} catch (Exception e) {
// TODO 异常处理
}
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
}</span>
仅仅修改了构造函数,传递进来一个代理者名称,即可进行代理,在这种改造下,系统更加简洁了,调用者只知道代理存在就可以,不用知道代理了谁  


普通代理的场景类 
<span style="font-size:18px;">public class Client {
public static void main(String[] args) {
//然后再定义一个代练者
IGamePlayer proxy = new GamePlayerProxy("张三");
//开始打游戏,记下时间戳
System.out.println("开始时间是:2009-8-25 10:45");
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2009-8-26 03:40");
}
}</span>

在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合。当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个非常好的方案。  

注意 普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题类是可被重用的和可维护的,使用技术约束的方式对系统维护是一种非常不利的因素。  


2)强制代理  

强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。

 


强制代理的接口类  
<span style="font-size:18px;">public interface IGamePlayer {
//登录游戏
public void login(String user,String password);
//杀怪,这是网络游戏的主要特色
public void killBoss();
//升级
public void upgrade();
//每个人都可以找一下自己的代理
public IGamePlayer getProxy();
}</span>
仅仅增加了一个getProxy方法,指定要访问自己必须通过哪个代理  


强制代理的真实角色 
<span style="font-size:18px;">public class GamePlayer implements IGamePlayer {
private String name = "";
//我的代理是谁
private IGamePlayer proxy = null;
public GamePlayer(String _name){
this.name = _name;
}
//找到自己的代理
public IGamePlayer getProxy(){
this.proxy = new GamePlayerProxy(this);
return this.proxy;
}
//打怪,最期望的就是杀老怪
public void killBoss() {
if(this.isProxy()){
System.out.println(this.name + "在打怪!");
}else{
System.out.println("请使用指定的代理访问");
}
}
//进游戏之前你肯定要登录吧,这是一个必要条件
public void login(String user, String password) {
if(this.isProxy()){
System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
}else{
System.out.println("请使用指定的代理访问");;
}
}
//升级,升级有很多方法,花钱买是一种,做任务也是一种
public void upgrade() {
if(this.isProxy()){
System.out.println(this.name + " 又升了一级!");
}else{
System.out.println("请使用指定的代理访问");
}
}
//校验是否是代理访问
private boolean isProxy(){
if(this.proxy == null){
return false;
}else{
return true;
}
}
}</span>
增加了一个私有方法,检查是否是自己指定的代理,是指定的代理则允许访问,否则不允许访问  


强制代理的代理类  
<span style="font-size:18px;">public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//构造函数传递用户名
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
//代理的代理暂时还没有,就是自己
public IGamePlayer getProxy(){
return this;
}
}</span>


强制代理的场景类
<span style="font-size:18px;">public class Client {
public static void main(String[] args) {
//定义一个游戏的角色
IGamePlayer player = new GamePlayer("张三");
//获得指定的代理
IGamePlayer proxy = player.getProxy();
//开始打游戏,记下时间戳
System.out.println("开始时间是:2009-8-25 10:45");
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2009-8-26 03:40");
}
}</span>



3)代理是有个性的  

也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。

例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,  




代理类的接口  
<span style="font-size:18px;">public interface IProxy {
//计算费用
public void count();
}</span>


GamePlayerProxy带来的变化  
<span style="font-size:18px;">代码清单12-20 代理类
public class GamePlayerProxy implements IGamePlayer,IProxy {
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer){
this.gamePlayer = _gamePlayer;
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
//计算费用
public void count(){
System.out.println("升级总费用是:150元");
}
}</span>

代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能。当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系,


4)动态代理   

动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象  



在类图中增加了一个InvocationHandler接口和GamePlayIH类,作用就是产生一个对象的代理对象,其中InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。我们来看程序,接口保持不变,实现类也没有变化  
<span style="font-size:18px;">public class GamePlayIH implements InvocationHandler {
//被代理者
Class cls =null;
//被代理的实例
Object obj = null;
//我要代理谁
public GamePlayIH(Object _obj){
this.obj = _obj;
}
//调用被代理的方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = method.invoke(this.obj, args);
return result;
}
}</span>

其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。

们来详细讲解一下InvocationHandler接口,

动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,

动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,

那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。  
 

场景类
<span style="font-size:18px;">public class Client {
public static void main(String[] args) throws Throwable {
//定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("张三");
//定义一个handler
InvocationHandler handler = new GamePlayIH(player);
//开始打游戏,记下时间戳
System.out.println("开始时间是:2009-8-25 10:45");
//获得类的class loader 类加载器
ClassLoader cl = player.getClass().getClassLoader();
//动态产生一个代理者
IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(cl,new Class[])
//登录
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2009-8-26 03:40");
}
}  </span>

关于相关反射方法,可以参考 http://blog.csdn.net/bihansheng2010/article/details/50150909


 通用动态代理模型




 两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。

通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务  


先来看Subject接口,如代码清单12-24所示。 
<span style="font-size:18px;">代码清单12-24 抽象主题
public interface Subject {
//业务操作
public void doSomething(String str);
}</span>
其中的doSomething是一种标识方法,可以有多个逻辑处理方法


实现类 
<span style="font-size:18px;">代码清单12-25 真实主题
public class RealSubject implements Subject {
//业务操作
public void doSomething(String str) {
System.out.println("do something!---->" + str);
}
}</span>


重点是我们的MyInvocationHandler,如代码清单12-26所示。
<span style="font-size:18px;">代码清单12-26 动态代理的Handler类
public class MyInvocationHandler implements InvocationHandler {
//被代理的对象
private Object target = null;
//通过构造函数传递一个对象
public MyInvocationHandler(Object _obj){
this.target = _obj;
}
//代理方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行被代理的方法
return method.invoke(this.target, args);
}
}</span>


所有通过动态代理实现的方法全部通过invoke方法调用。DynamicProxy代码如代码清单12-27所示。
<span style="font-size:18px;">代码清单12-27 动态代理类
public class DynamicProxy {
public static T newProxyInstance(ClassLoader loader, Class[] interfaces, Handler h){
//寻找JoinPoint连接点,AOP框架使用元数据定义
if(true){
//执行一个前置通知
(new BeforeAdvice()).exec();
}
//执行目标,并返回结果
return (T)Proxy.newProxyInstance(loader,interfaces, h);
}
}</span>


我们来看通知Advice,接口和实现如代码清单12-28所示。
<span style="font-size:18px;">public interface IAdvice {
//通知只有一个方法,执行即可
public void exec();
}</span>

Advice实现类
<span style="font-size:18px;">public class BeforeAdvice implements IAdvice{
public void exec(){
System.out.println("我是前置通知,我被执行了!");
}
}</span>


最后就是看我们怎么调用了,如代码清单12-29所示。
<span style="font-size:18px;">代码清单12-29 动态代理的场景类
public class Client {
public static void main(String[] args) {
//定义一个主题
Subject subject = new RealSubject();
//定义一个Handler
InvocationHandler handler = new MyInvocationHandler(subject);
//定义主题的代理
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(),handler);
//代理的行为
proxy.doSomething("Finish");
}
}</span>


 在DynamicProxy类  中

this.obj=Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(),new MyInvocationHandler(_obj));  

该方法是重新生成了一个对象,为什么要重新生成?要使用代理

注意c.getInterfaces()这句话,这是非常有意思的一句话,是说查找到该类的所有接口,然后实现接口的所有方法  
于是我们知道一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现  

动态代理的主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变我们已有代码结构的情况下增强或控制对象的行为。  
 
注意 要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下前面的分析吧。当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。  

 

http://www.niftyadmin.cn/n/1678250.html

相关文章

100内数之和

vim sum100.sh#!/bin/bashI1SUM0for I in {1..100}dolet SUM$I #也可以替换成 SUMexpr $SUM $Ilet I #也可以替换成 Iexpr $I 1 echo "The sum is $SUM"2、vim sum100.shI1SUM0while [ $I -le 100 ]dolet SUM$Ilet Idon…

springboot入门知识了解

springboot入门知识了解一、springboot了解1、springboot是什么&#xff1f;2、为什么要使用springboot3、特征4、备注二、入门实例&#xff08;HelloController&#xff09;1、准备工作内置tomcat2、开始代码三、配置修改1、配置文件后缀名变化2、端口号改变3、地址名添加4、R…

PHPExcel 之常用功能

PHPExcel基本操作&#xff1a; 定义EXCEL实体 即定义一个PHPEXCEL对象&#xff0c;并设置EXCEL对象内显示内容 ?123456789101112131415161718192021222324// Excel开始// 准备EXCEL的包括文件// Error reporting error_reporting(0);// PHPExcel require_once dirname(__FILE_…

航电 -1232 畅通工程

畅通工程 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 41555 Accepted Submission(s): 22045 Problem Description某省调查城镇交通状况&#xff0c;得到现有城镇道路统计表&#xff0c;表中列出了每条道路…

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

给定一个字符串 s&#xff0c;找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。一、题目描述二、题目详解三、思路分析dp方程四、详细代码五、错误一、题目描述 给定一个字符串 s&#xff0c;找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。 二、题目…

设计模式笔记--原型模式

常用设计模式有23中&#xff0c;分为&#xff1a; 创建型模式&#xff08;主要用于创建对象&#xff09; 1、单例模式 2、工厂方法模式 3、抽象工厂模式 4、建造者模式 5、原型模式 行为型模式 &#xff08;主要用于描述对象或类是怎样交互和怎样分配职责&…

Swift中构造器的继承和重写

import Foundation/* 构造器的继承: Swift的子类不会自动继承父类的构造器, 若继承, 则满足如下规则: 1.如果子类没有提供任何指定构造器, 那么它将自动继承父类的所有指定构造器 2.如果子类实现了父类所有的指定构造器, 无论如何实现的, 都将自动继承父类的所有便利构造器 *//…

设计模式笔记--中介者模式

常用设计模式有23中&#xff0c;分为&#xff1a; 创建型模式&#xff08;主要用于创建对象&#xff09; 1、单例模式 2、工厂方法模式 3、抽象工厂模式 4、建造者模式 5、原型模式 行为型模式 &#xff08;主要用于描述对象或类是怎样交互和怎样分配职责&…