一、代理模式例子
先看一个例子,是有关于打游戏杀怪兽的。
//先定义一个游戏者接口
public interface IGamePlayer {
//登陆游戏
public void login(String user, String password);
//杀怪,网络游戏的主要角色
public void killBoss();
//升级
public void upgrade();
}
//游戏者
public class GamePlayer implements IGamePlayer {
private String name = "";
//通过构造方法传递名称
public GamePlayer(String _name) {
this.name = _name;
}
//打怪,最期望的就是打怪
@Override
public void killBoss() {
System.out.println(this.name + "在打怪!");
}
//在游戏之前肯定要登录吧
@Override
public void login(String user, String password) {
System.out.println("登陆名" + user + "的用户" + this.name + "登陆成功");
}
//升级,升级有很多方法,花钱买是一种,做任务又是一种
@Override
public void upgrade() {
System.out.println(this.name + "又升了一级");
}
}
//场景类
public class Client {
public static void main(String[] args) {
//定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("张三");
//开始打游戏,记录下时间
System.out.println("开始时间是:2013-10-12 21:51");
player.login("zhangsan", "password");
//开始杀怪
player.killBoss();
//升级
player.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2013-10-13 21:51");
}
}
但是有时候我们很累,要熬夜,但是我们又想玩游戏,又想摆脱那些烦恼,如何解决呢?有办法,现在游戏公司代练的公司很多,我把自己账号交给代练人员,由他们帮我升级,去打怪,非常好的想法,我们修改一下类结构.
//增加一个GamePlayerProxy来代表游戏代练者,它也不能有作弊的方法呀,游戏代练者也是手动打怪呀,因此需要同样继承IGamePlayer接口,代码如下:
public class GamePlayerProxy implements IGamePlayer {
private IGamePlayer gamePlayer = null;
//通过构造方法传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer = _gamePlayer;
}
//代练杀怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
@Override
public void login(String user, String password) {
this.gameplayer.login(user, password);
}
//升级
@Override
public void upgrade() {
this.gamePlayer.upgrade();
}
}
很简单,首先通过构造方法说明要代谁打怪升级,然后通过手动开始代用户打怪\升级.场景类Client代码也稍作改动.
public class Client {
public static void main(String[] args) {
//定义一个痴迷的玩家
IGamePlayer player = new GamePlayer("张三");
//然后再定义一个代练者
IGamePlayer proxy = new GamePlayerProxy(player);
//开始打游戏,记录下时间
System.out.println("开始时间是:2013-10-12 21:51");
proxy.login("zhangsan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是:2013-10-13 21:51");
}
}
二、代理模式的定义
代理模式(Proxy Pattern)是一个使用率非常高的模式,其定义:Provide a surrogate or placeholder for another object to control access to it(为其他对象提供一种代理以控制对这个对对象的访问。)代理模式也叫做委托模式,它是一项基本的设计技巧,很多其他的模式本质上是在更特殊的场合采用了委托模式。
代理模式通用代码如下
1. 抽象主题类
public interface Subject {
//定义一个方法
public void request();
}
2. 真实主题类
public class RealSubject implements Subject{
//实现方法
public void request() {
//业务逻辑处理
}
}
3. 代理类
//代理类
public class Proxy implements Subject{
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
public Proxy() {
this.subject = new Proxy();
}
//通过构造函数传递被代理者
public Proxy(Object...objects){
}
//注意:代理的构造函数!!!!!!!!!!!!!!!
public Proxy(Subject _subject){
this.subject = _subject;
}
//实现 接口中定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something
}
//善后处理
private void after(){
//do something
}
}
三、代理模式的优点
1)职责清晰:这是的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
2)高扩展性:具体的主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以再不做任何修改的情况下使用。
3)这在我们以上的讲解中都没有体现出来,不过在我们一下的动态代理章节中你就会看到代理店智能化有兴趣的读者也可以看看Structs是如何把表单元素映射到对象上的。
四、代理模式的使用场景
我相信第一次接触代理模式的读者肯定很郁闷,为什么要用代理呀?想想我们现实世界吧,打官司为什么要找个律师?因为你不想参与中间过程的是是非非,只要完成自己的啊答辩就成,其他的比如事前调查、事后追查都由律师来搞掂,这就是为了减轻你的负担。代理模式的使用场景非常多,大家可以看看spring AOP,这是一个非常典型的动态代理。
五、代理模式的扩展
1. 普通代理
要求就是客户端只能访问代理角色,而不能访问真实角色。
代练者构造函数不传递游戏者对象,而是传递名称,在代练者构造函数中实例化游戏者
还有:在代理类中实例化固定游戏者——游戏者类写死了,如果想new一个同样实现接口的另一个类,就要做判断!!!,个人感觉不放便
在网络上代理服务器设置分为透明代理和普通代理,是什么意思呢?透明代理就是用户不用设置服务地址,就可以直接访问,也就是说代理服务器对用户来说是透明的,不用知道它的存在的;普通代理则是需求用户自己设置代理服务器的IP地址,用户必须知道代理的存在.我们设计模式中的普通代理模式和强制代理模式也是类似的一种结构,普通的代理就是我们需要知道的代理存在,也就是累世的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不关心代理是否存在,其代理的产生游真实的角色决定的,这样的解释比较复杂,我们还是用实例来讲解.
首先说普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色,这是比较简单的.我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定不练级了,也就是场景类不能再直接new一个GamePlayer对象了,它必须由GamePlayerProxy来进行模拟场景,修改如下
1). 游戏者接口(包括代理者和代练者)
public interface IGamePlayer {
//登录游戏
public void login(String user,String password);
//杀怪,网络游戏的主要特色
public void killBoss();
//升级
public void upgrade();
}
2) 普通代理的游戏者
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 login(String user, String password) {
System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
}
//打怪
public void killBoss() {
System.out.println(this.name+"在打怪");
}
//升级
public void upgrade() {
System.out.println(this.name+"又升了一级");
}
}
3) 普通代理的代理者
public class GamePlayerProxy implements IGamePlayer{
private IGamePlayer gamePlayer = null;
//通过构造函数传递要对谁进行代练
public GamePlayerProxy(String name) {
try {
this.gamePlayer = new GamePlayer(this, name);
} catch (Exception e) {
e.printStackTrace();
}
}
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
public void killBoss() {
this.gamePlayer.killBoss();
}
public void upgrade() {
this.gamePlayer.upgrade();
}
}
4) 普通代理的场景类
public class Client {
public static void main(String[] args) {
//定义一个代练者
IGamePlayer proxy = new GamePlayerProxy("张三");
//登录
proxy.login("zhangsan", "password");
//杀怪
proxy.killBoss();
//升级
proxy.upgrade();
}
}
运行结果完全相同.在该模式下,调用者只知道代理而不用知道真实角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,改模式非常适合对扩展性要求比较高的场合.当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色的,这也是一个非常好的方案.
注意:普通代理模式的约束问题,尽量通过团队内的编程规范类的约束,因为每个主题类是可以被重复用的和可维护的,使用技术约束的方式对系统是一种非常不利的因素.
2. 强制代理
就是要从真实角色查找到代理角色,不允许直接访问真实角色。高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要产生一个代理出来,代理的管理已经由真实角色自己完成。
强制代理在设计模式中比较另类,为什么这样说呢?一般的思维都是通过代理找到真实的角色的,但是强制代理却是要"强制",你必须通过真实角色查找到代理角色,否则你不能访问.甭管你是通过代理类还是通过直接new一个主题角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说由真实角色管理代理角色.这么说吧,高层模块new了一个真实角色的对象,返回的确实代理角色,这好比你和一个明星比较熟,相互认识,有件事情你需要想她确认一下,于是你就直接拨通了明星的电话:
"喂,沙比呀,我要见一下XXX导演,你帮下忙!"
"不行呀衰哥,我这几天很忙呀,你找我的经纪人吧......"
郁闷了吧,你是想直接绕过她的代理,谁知道返回的还是她的代理,这就是强制代理,你可以不用知道代理存在,但是你的所作所为还是需要代理为你提供.我们修改一下IGamePlayer接口,增加一个getProxy的方法.
1) 强制代理的接口类
public interface IGamePlayer {
//登录游戏
public void login(String user,String password);
//杀怪,网络游戏的主要特色
public void killBoss();
//升级
public void upgrade();
//每个人都可以找一下自己的代理
public IGamePlayer getProxy();
}
2) 强制代理的真实角色
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 login(String user, String password) {
if(this.isProxy()){
System.out.println("登录名为"+user+"的用户"+this.name+"登录成功!");
}else{
System.out.println("请使用指定的代理访问");
}
}
//打怪
public void killBoss() {
if(this.isProxy()){
System.out.println(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;
}
}
}
3) 强制代理的代理类
public class GamePlayerProxy implements IGamePlayer{
private IGamePlayer gamePlayer = null;
//构造函数传递用户名
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
//代理的代理暂时还没有,就是自己
public IGamePlayer getProxy() {
return this;
}
//代练登录
public void login(String user, String password) {
this.gamePlayer.login(user, password);
}
//代练杀怪
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练升级
public void upgrade() {
this.gamePlayer.upgrade();
}
}
4) 场景类
public class Client {
public static void main(String[] args) {
//错误的访问方式
/*//定义一个游戏的角色
IGamePlayer player = new GamePlayer("张三");
player.login("zhangsan", "password");
player.killBoss();
player.upgrade();
请使用指定的代理访问
请使用指定的代理访问
请使用指定的代理访问*/
//正确的访问方式
IGamePlayer player = new GamePlayer("张三");
IGamePlayer proxy = player.getProxy();
proxy.login("zhangsan", "password");
proxy.killBoss();
proxy.upgrade();
}
}
OK,可以正常访问代理了.强制代理的概念就是要从真实角色查找到代理角色,不允许直接访问真实角色.高层模块只要调用getProxy就可以访问真实角色的所有方法,它根本就不需要一产生一个代理出来,代理的管理已经由真实的角色自己完成.
3. 代理是有个性的
一个类可以实现多个接口,完成不同的任务的整合.也就是说代理类不仅仅可以实现主体接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上做增强,这种增强的本质通常就是通过对目标对象的方法进行拦截和过滤.例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义.增加一个IProxy的接口,其作用就是计算代理的费用.
//代理类的接口
public interface IProxy {
//计算费用
public void count();
}
//代理类
public class GamePlayerProxy implements IGamePlayer,IProxy {
private IGamePlayer gamePlayer = null;
//通过构造方法传递要对谁进行代练
public GamePlayerProxy(IGamePlayer _gamePlayer) {
this.gamePlayer = _gamePlayer;
}
//代练杀怪
@Override
public void killBoss() {
this.gamePlayer.killBoss();
}
//代练登录
@Override
public void login(String user, String password) {
this.gameplayer.login(user, password);
}
//升级
@Override
public void upgrade() {
this.gamePlayer.upgrade();
this.count();
}
//计算费用
@Override
public void count() {
System.out.println("升级费用是:150元")
}
}
虚拟代理
虚拟代理(Virtual Proxy)听着很负责,其实很简单,我们只要吧代理模式的通用代码稍微修改一下就成了虚拟代理,修改后的代理类如下:
//虚拟代理类
public class Proxy implements Subject {
//要代理哪个实现类
private Subject subject;
//实现接口定义方法
@Override
public void request() {
//判断一下真实主题是否初始化
if (subject == null) {
subj = new RealSubject();
}
subject.request();
}
}
在需要的时候才初始化主题对象,可以避免被代理对下较多而引起的初始化缓慢的问题.其缺点是需要在每个方法中判断主题对喜爱那个是否被创建,这就是虚拟代理,非常简单.
五、 动态代理
动态代理是根据被代理的接口生成所有的方法,即给定一个接口,动态代理会宣称“已实现 该接口下的所有方法了”,但没有任何逻辑含义。那怎么办?可以通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有代理方法都由Invocationhandler接管实际的处理任务。(动态代理实现 的方法全部通过invoke方法调用!!!)
动态代理的作用:在不改变我们已有代码结构的情况下,增强或控制对象的行为。
1. 动态代理游戏
1) 产品类
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 + "又升了一级");
}
}
2) 动态代理类
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);
//如果是登录方法,则发送信息,当登录游戏时,就调用该方法
if(method.getName().equalsIgnoreCase("login")){
System.out.println("有人在用我的账号登录");
}
return result;
}
}
3) 动态代理的场景类
public class Client {
public static void main(String[] args) {
//定义一个玩家
IGamePlayer player = new GamePlayer("张三");
//定义一个handler
InvocationHandler handler = new GamePlayIH(player);
//获得类的class loader
ClassLoader cl = player.getClass().getClassLoader();
//动态产生一个代理者
IGamePlayer proxy = (IGamePlayer)Proxy.newProxyInstance(cl,new Class[]{IGamePlayer.class},handler);
//登录
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
}
}
2. 动态代理通用类
1) 抽象主题
public interface Subject {
//定义一个方法
public void doSomething(String str);
}
2) 真实主题
public class RealSubject implements Subject{
//业务操作
public void doSomething(String str) {
System.out.println("do something!---->"+str);
}
}
3) 动态代理的Handler类
/*
* 由InvocationHandler的实现 类实现 所有方法
* 由invoke方法接管所有方法实现
* (动态代理实现 的方法全部通过invoke方法调用)
*/
public class MyInvocationHandler implements InvocationHandler{
//被代理的对象
private Object obj = null;
//通过构造函数传递一个对象
public MyInvocationHandler(Object obj) {
this.obj = obj;
}
//代理方法
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
return method.invoke(this.obj, args);
}
}
4) 动态代理类
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
//寻找JoinPoint连接点,AOP框架使用元数据定义
if(true){
//执行一个前置通知
(new BeforeAdvice()).exec();
}
//执行目标,生成了一个对象,并送回结果,这个是核心方法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
return (T) Proxy.newProxyInstance(loader,interfaces,h);
}
}
5) 通知接口及实现
public interface IAdvice {
//通知只有一个方法,执行即可
public void exec();
}
public class BeforeAdvice implements IAdvice{
public void exec() {
System.out.println("我是前置通知,我被执行了");
}
}
6) 动态代理的场景类
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");
}
}
7) 具体业务的动态代理
import java.lang.reflect.InvocationHandler;
//具体业务的动态代理。
public class SubjectDynamicProxy extends DynamicProxy {
public static <T> T newProxyInstance(Subject subject){
ClassLoader classLoader = subject.getClass().getClassLoader();
Class<?>[] interfaces = subject.getClass().getInterfaces();
InvocationHandler handler = new MyInvocationHandler(subject);
T t = newProxyInstance(classLoader, interfaces, handler);
return t;
}
}
8)场景类
public class Client{
public static void main(String [] args){
Subject subject = new Subject();
Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);
proxy.doSomething("Finish");
}
}
注意
要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下前面的分析吧,当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方法.
再次说明,以上的动态代理只是一个通用的框架,如果你想设计自己的AOP框架,完全可以在此基础上扩展,我们设计的是一个通用代理,只要有一个接口,一个实现类,就而已使用该代理,完成代理的所有功效.