代理(Proxy)
[TOC]
定义
代理模式主要的实现分为两种,一类是静态代理,一类是动态代理,无论是静态还是动态,只是他们的实现方式不一样,实则核心思想是一致的:
Provide a surrogate or placeholder for another object to control access to it.(为其他对象提供一种代理以控制对这个对象的访问)
代理模式的写法和他的思想一样简单,主要为下面三点
- 业务接口
- 业务接口的实现者
- 业务接口的代理者
其中代理者和实现者实现的接口是一样的,只不过在调用实际能力的时候,是由实现者来完成具体的工作
简单场景使用
惯例英雄联盟背景~
代理
游戏上来讲就是代练
,这个解释简直形象的亚P,我高中时候玩这款游戏本来是电四的,后来上大学了,大家都是网通,所以我又转网通区了,可是转了网通后得重新练号,不然没法打排位,而升到30级简直艰难的亚P,我希望自己的网通区账号能够尽快达到排位的门槛,这时候代练的作用就来咧并且在这期间,我希望我自己想打两把人机快乐快乐的时候,我能自己上号,我不玩的时候,代练兄弟能上号升级,美滋滋
coding
首先我们先定义一波最基本的能力,就是升级(手动笑哭)
public interface IPlayer {
/**
* 登录
*
* @param userName
* @param password
*/
void login(String userName, String password);
/**
* 游戏中
*/
void gaming();
/**
* 升级咧
*/
void upgrade();
}
接下来是游戏业务的实现类
public class PlayerImpl implements IPlayer {
private String mName;
private String mUserName;
public PlayerImpl(String name) {
this.mName = name;
}
@Override
public void login(String userName, String password) {
this.mUserName = userName;
System.out.println(generateName() + "登录了游戏...");
}
@Override
public void gaming() {
System.out.println(generateName() + "游戏中...");
}
@Override
public void upgrade() {
System.out.println(generateUser() + "升级了...");
}
private String generateName() {
return "玩家 [" + mName + "]";
}
private String generateUser() {
return "账号 [" + mUserName + "]";
}
}
然后就是咱们的代练实现
public class PlayerProxy implements IPlayer {
private String mUserName;
private IPlayer mRealPlayer;
public PlayerProxy(IPlayer player) {
this.mRealPlayer = player;
}
@Override
public void login(String userName, String password) {
mUserName = userName;
checkMoney(userName);
mRealPlayer.login(userName, password);
}
@Override
public void gaming() {
mRealPlayer.gaming();
}
@Override
public void upgrade() {
mRealPlayer.upgrade();
checkOrder();
}
private void checkMoney(String userName) {
System.out.println(generateUser(userName) + "钱够,给他练级");
}
private void checkOrder() {
System.out.println(generateUser(mUserName) + "完成任务,扣他账户余额");
}
private String generateUser(String userName) {
return "账号 [" + userName + "]";
}
}
main里面模拟一下我自己登号和代练等号的场景
public static void main(String[] args) {
PlayerImpl player = new PlayerImpl("Done");
executeGame(player);
System.out.println();
PlayerImpl player1 = new PlayerImpl("工具人1号");
PlayerProxy proxy1 = new PlayerProxy(player1);
executeGame(proxy1);
}
private static void executeGame(IPlayer player) {
String username = "929891705";
String password = "admin";
player.login(username, password);
player.gaming();
player.upgrade();
}
//LOG
//玩家 [Done]登录了游戏...
//玩家 [Done]游戏中...
//账号 [929891705]升级了...
//账号 [929891705]钱够,给他练级
//玩家 [工具人1号]登录了游戏...
//玩家 [工具人1号]游戏中...
//账号 [929891705]升级了...
//账号 [929891705]完成任务,扣他账户余额
可以看到,代练在给我升级之前,会先check一遍我账号的钱够不够,不够就不给我升级了,而游戏能力只需要一个通用的实现即可,非常方便,对上层及其友好,符合迪米特法则
动态代理
动态代理其实是借助了java语言的InvocationHandler接口来进行实现,他其实是通过classLoader的去创建实例对象从而完成对代理对象的创建,实际解决的方案则是AOP的手段来完成对扩展开放的能力,这里还是借助上面的场景来完成动态代理的使用
coding
首先代练的兄弟需要检测玩家是否正在进行游戏,如果正在进行的话,就不抢登了,等到玩家下线,咱们代理在上线,如果我们既希望代理能完成这项操作,又希望原有的代练代码不做改动,那可以考虑使用动态代理的方式来实现
实现动态代理主要有两个步骤
- 实现
InvocationHandler
接口 - 通过Proxy.newProxyInstance实例化代理对象
public class InvocationPlayer implements InvocationHandler {
private IPlayer mPlayer;
private boolean mLogin;
private static final String sName = "Done";
public InvocationPlayer(IPlayer player, boolean isLogin) {
mPlayer = player;
mLogin = isLogin;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
String nickName = mPlayer.getNickName();
if (!sName.equals(nickName)) {
if (mLogin) {
System.out.println("玩家[" + sName + "]登录了游戏,一会再登吧");
} else {
System.out.println("玩家[" + nickName + "]没有登录游戏,可以登录代练");
invoke = method.invoke(mPlayer, args);
}
} else {
invoke = method.invoke(mPlayer, args);
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("哦豁,崩逑~");
}
return invoke;
}
}
实例化代理对象,这里为了验证强登情况,实现两个,一个验证玩家登陆,一个验证代理登陆
PlayerImpl player2 = new PlayerImpl("工具人2号");
PlayerProxy proxy2 = new PlayerProxy(player2);
IPlayer playerDyn2 = (IPlayer) Proxy.newProxyInstance(proxy2.getClass().getClassLoader(),
proxy2.getClass().getInterfaces(),
new InvocationPlayer(proxy2, false));
executeGame(playerDyn2);
System.out.println();
PlayerImpl player3 = new PlayerImpl("工具人3号");
PlayerProxy proxy3 = new PlayerProxy(player3);
IPlayer playerDyn3 = (IPlayer) Proxy.newProxyInstance(proxy3.getClass().getClassLoader(),
proxy3.getClass().getInterfaces(),
new InvocationPlayer(proxy3, true));
executeGame(playerDyn3);
//玩家[工具人2号]没有登录游戏,可以登录代练
//账号 [929891705]钱够,给他练级
//玩家 [工具人2号]登录了游戏...
//玩家[工具人2号]没有登录游戏,可以登录代练
//玩家 [工具人2号]游戏中...
//玩家[工具人2号]没有登录游戏,可以登录代练
//账号 [929891705]升级了...
//账号 [929891705]完成任务,扣他账户余额
//玩家[Done]登录了游戏,一会再登吧
//玩家[Done]登录了游戏,一会再登吧
//玩家[Done]登录了游戏,一会再登吧
实际场景使用
最出名的莫过于JakeWharton大佬的retrofit了,感兴趣的同学可以参考下~