23种设计模式7_代理模式之一静态代理
1 基本介绍
代理模式:为其他对象提供一种代理以控制对这个对象的访问
代理模式也叫委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上都是在更加特殊的场合采用了代理模式。在日常开发应用种,代理模式能够提供非常好的访问控制。
代理可分为两种:一种是静态代理,由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的**.class**文件就已经存在了;另一种是动态代理:在程序运行时,运用反射机制动态创建而成。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
静态代理事先知道自己要代理的是什么,而动态代理不知道要代理什么,只有在运行时才知道。
动态代理参考:23种设计模式7_代理模式之二动态代理
代理模式的UML类图:
从上图可以看到代理模式有3种角色:
Subject抽象主题角色 抽象主题角色可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求。
RealSubject具体主题角色 也叫做被代理角色、被委托角色,它就是个冤大头,是业务逻辑的具体执行者。
Proxy代理角色 也叫做代理类、委托类,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色来实现,并且在真实主题角色处理业务逻辑前后做预处理和善后工作。
2 代理模式的通用代码
抽象主题角色
// 抽象主题角色
public interface Subject {
public void request();
}
具体主题角色
// 具体主题角色
public class RealSubject implements Subject{
@Override
public void request() { /*业务逻辑代码*/ }
}
代理角色
// 代理角色
public class Proxy implements Subject{
private Subject subject = null;
@Override
public void request() {
if (subject == null) subject = new RealSubject();
this.before();
subject.request();
this.after();
}
// 前置处理
private void before() {}
// 后置处理
private void after() {}
}
3 案例演示
3.1 需求
玩游戏打怪升级,找代练。
3.2 代码演示
抽象主题类:抽象玩家接口
public interface IGamePlayer {
// 登录
void login(String playName, String password);
// 打怪
void killMonster();
// 升级
void levelUp();
}
具体主题角色:游戏玩家
public class GamePlayer implements IGamePlayer{
// 游戏玩家名称
private String playName;
public GamePlayer(String playName) { this.playName = playName; }
@Override
public void login(String playName, String password) {
// 验证用户名密码,纯演示就省略了。。。。
System.out.println(this.playName + "登录游戏成功!");
}
@Override
public void killMonster() { System.out.println(this.playName + "正在打怪!"); }
@Override
public void levelUp() { System.out.println("恭喜" + this.playName + "又升了一级!"); }
}
代理角色:游戏代练
public class GamePlayerProxy implements IGamePlayer {
// 对谁进行代练
private IGamePlayer gamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer) { this.gamePlayer = gamePlayer; }
@Override
public void login(String playName, String password) { this.gamePlayer.login(playName,password); }
@Override
public void killMonster() { this.gamePlayer.killMonster(); }
@Override
public void levelUp() { this.gamePlayer.levelUp(); }
}
客户端演示
public class Client {
public static void main(String[] args) {
// 定义一个玩家
IGamePlayer gamePlayer = new GamePlayer("zhangsan");
// 定义一个代练,并指定对谁进行代练
IGamePlayer gamePlayerProxy = new GamePlayerProxy(gamePlayer);
// 代练登录玩家账号
gamePlayerProxy.login("zhangsan","123456");
// 代练帮玩家打怪
gamePlayerProxy.killMonster();
// 玩家的账号升级了
gamePlayerProxy.levelUp();
}
}
演示结果
这个叫“zhangsan”的玩家全程自己都没有登录游戏,全部都是代练在玩,这个玩家的游戏账号同样也升级了。自己不需要动手操作,别人帮你把活都干完了,这就是代理模式。
4 代理模式的应用
4.1 代理模式的优点
①***职责清晰*** 真实的角色就是实现实际的业务逻辑,不用关心其他非本职的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
②***高扩展性*** 具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱接口的控制,那么我们的代理类就可以在不做任何修改的情况下使用。
③智能化*** 上面的案例没有体现出来,这个优点会在动态代理*的演示种体现出来。
4.2 应用场景
开发中的使用场景非常多,比如Spring AOP就是典型的动态代理,生活中比如演员替身、律师帮你打官司等等。
5 代理模式的扩展
这里解释两个概念:普通代理和透明代理(强制代理)。
普通代理 用户要知道代理的存在,比如上述的GamePlayerProxy这个类,调用者需要知道它来进行访问
强制代理 调用者不需要知道代理是否存在,它直接调用真实角色,它的代理的产生是由真实角色自己决定的
5.1 普通代理
前面的案例中调用者是依赖被代理角色的,在这里就不能new一个GanmePlayer对象了,它必须由代理角色通过代理来访问了。修改类图如下:
对GamePlayer类进行修改:
// 具体主题角色:游戏玩家
public class GamePlayer implements IGamePlayer{
// 游戏玩家名称
private String playName;
public GamePlayer(GamePlayerProxy proxy, String playName) throws Exception {
// 必须通过代理
if (proxy == null) {
throw new Exception("不能创建角色");
} else {
this.playName = playName;
}
}
@Override
public void login(String playName, String password) {
// 验证用户名密码,纯演示就省略了。。。。
System.out.println(this.playName + "登录游戏成功!");
}
@Override
public void killMonster() { System.out.println(this.playName + "正在打怪!"); }
@Override
public void levelUp() { System.out.println("恭喜" + this.playName + "又升了一级!"); }
}
对GamePlayerProxy进行修改:
// 代理角色:游戏代练
public class GamePlayerProxy implements IGamePlayer {
// 对谁进行代练
private IGamePlayer gamePlayer;
public GamePlayerProxy(String playName) {
try {
gamePlayer = new GamePlayer(this, playName);
}catch(Exception e) {
// TODO ....
}
}
@Override
public void login(String playName, String password) { this.gamePlayer.login(playName,password); }
@Override
public void killMonster() { this.gamePlayer.killMonster(); }
@Override
public void levelUp() { this.gamePlayer.levelUp(); }
}
修改CLient:
public class Client {
public static void main(String[] args) {
// 定义一个代练,并指定对谁进行代练
IGamePlayer gamePlayerProxy = new GamePlayerProxy("zhangsan");
// 代练登录玩家账号
gamePlayerProxy.login("zhangsan","123456");
// 代练帮玩家打怪
gamePlayerProxy.killMonster();
// 玩家的账号升级了
gamePlayerProxy.levelUp();
}
}
演示结果:
分析 运行结果完全相同。在该模式下,调用者只知道代理而不知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,只要你实现了接口对应的方法,真实角色的修改不影响高层次模块的使用,该模式较适合对扩展性要求较高的场合。当然了,在实际开发中,一般都是用过约定来禁止new真实的角色。普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题类是可被重用和可维护的,使用代码约束的方式对后期维护也是非常不利的。
5.2 强制代理
强制代理就是调用者必须通过真实角色查找到代理角色,否则不能访问。不管你是通过代理类还是new一个真实角色类,都不能访问,只有通过真实角色指定的代理类才可以访问,也就是说真实角色管理代理角色。生活中的例子:找明星,得找他的经纪人吧,找他自己或者找别的经纪人都没有用,只有找他指定的经纪人才行;有时候找老板也是的,得找他的秘书。。。
UML类图如下:
对IGamaPlayer进行修改:
public interface IGamePlayer {
// 登录
void login(String playName, String password);
// 打怪
void killMonster();
// 升级
void levelUp();
// 强制代理,必须找自己的代理
IGamePlayer getProxy();
}
对GamePlayer进行修改:
public class GamePlayer implements IGamePlayer{
// 游戏玩家名称
private String playerName;
private IGamePlayer gamePlayerProxy;
public GamePlayer(String playerName) {
this.playerName = playerName;
}
// 指定自己的代理
@Override
public IGamePlayer getProxy() {
gamePlayerProxy = new GamePlayerProxy(this);
return gamePlayerProxy;
}
@Override
public void login(String playName, String password) {
if (isMyProxy()) {
System.out.println(this.playerName + "登录游戏成功!");
}
}
@Override
public void killMonster() {
if (isMyProxy()) {
System.out.println(this.playerName + "正在打怪!");
}
}
@Override
public void levelUp() {
if (isMyProxy()) {
System.out.println("恭喜" + this.playerName + "又升了一级!");
}
}
private boolean isMyProxy(/*IGamePlayer proxy*/) {
// 按照逻辑还需要判断下是否是自己返回的那个代理类,即proxy == gamePlayerProxy?我这里纯演示就不写了
if (this.gamePlayerProxy != null) {
return true;
}
System.out.println("请联系我的代理");
return false;
}
}
对GamePlayerProxy进行修改:
public class GamePlayerProxy implements IGamePlayer {
// 对谁进行代练
private IGamePlayer gamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer) {
this.gamePlayer = gamePlayer;
}
// 指定自己的代理
@Override
public IGamePlayer getProxy() {
// 代理的代理,这里就是自己
return this;
}
@Override
public void login(String playName, String password) { this.gamePlayer.login(playName,password); }
@Override
public void killMonster() { this.gamePlayer.killMonster(); }
@Override
public void levelUp() { this.gamePlayer.levelUp(); }
}
情景一:没有通过代理,直接new对象访问,演示结果如下:
情景二:调用者自己new的代理,不是自己指定的代理,演示结果如下:
情景三:通过真实角色指定的代理,演示结果如下:
5.3 有个性的代理
一个类可以实现多个接口,完成不同任务的整合。即代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,或者理解为对目标对象的方法进行拦截和过滤。从上面的案例,我们修改下需求:并不是玩家找我代练就可以了,你得付钱我才帮你代练。
修改UML类图:
添加代理类的过滤接口IProxy:
// 代理类的控制接口
public interface IProxy {
boolean pay();
}
修改代理类GamePlayerProxy:
public class GamePlayerProxy implements IGamePlayer,IProxy {
// 对谁进行代练
private IGamePlayer gamePlayer;
public GamePlayerProxy(IGamePlayer gamePlayer){
if (pay()) {
this.gamePlayer = gamePlayer;
} else {
// 需要添加未付款时gamePlayer为null的处理
System.out.println("付钱。。。");
}
}
// 指定自己的代理
@Override
public IGamePlayer getProxy() {
// 代理的代理,这里就是自己
return this;
}
@Override
public void login(String playName, String password) { this.gamePlayer.login(playName,password); }
@Override
public void killMonster() { this.gamePlayer.killMonster(); }
@Override
public void levelUp() { this.gamePlayer.levelUp(); }
@Override
public boolean pay() {
System.out.println("代练共收费500元,已付款。");
return true;// 默认就算受过费用了,对是否收费可以自行实现
}
}
演示结果如下: