什么是代理模式
代理模式为其他对象提供了一种代理以控制对这个对象的访问,是一个使用率非常高的模式。
在我们的生活中,明星的经纪人,游戏的代练,其实都是代理模式的一种表现。简单来说,代理模式能帮被代理对象省掉不少麻烦,也可以为被代理对象提供保护,避免他人直接访问。
保护代理
保护代理用于控制不同权限的对象对目标对象的访问,但在 JavaScript 并不容易实现保护代理,因为我们无法判断谁访问了某个对象
虚拟代理
简单来说,就是把一些开销大的操作延迟到真正需要的时候才去执行。常见的虚拟代理有:
合并HTTP请求、图片预加载、惰性加载等。
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果
可以用于ajax异步请求数据,例如将分页的数据缓存下来。
动态代理
动态代理是在实现阶段不用关心代理谁, 而在运行阶段才指定代理哪一个对象。 相对来说, 自己写代理类的方式就是静态代理。
为什么需要代理模式
在上述定义的介绍中,其实就大体说明了为什么需要代理模式。
总结为如下几点:
- 需要控制访问权限
- 需要减少开销
如何实现代理模式
JavaScript
虚拟代理
图片预加载
// 被代理对象
let myImage = (function(){
let imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){
imgNode.src = src;
}
}
})();
// 代理对象
let proxyImage = (function(){
let img = new Image;
img.onload = function(){
// 当img具有src的时候才执行
myImage.setSrc(this.src);
};
return {
setSrc: function( src ){
myImage.setSrc( './loading.gif' );
// 触发onload事件,实现替换图片
img.src = src;
}
}
})();
proxyImage.setSrc( 'https://qzonestyle.gtimg.cn/qzone/qzactStatics/imgs/20171122191630_ff8fef.jpg' );
合并HTTP请求
说白了就是设置一个定时器,过多少秒去把操作的数据以集合的形式发请求,而不是操作一次发一次。
代码模拟的是checkbox的选中。
var synchronousFile = function( id ){
console.log( '开始同步文件, id 为: ' + id );
};
var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function( id ){
cache.push( id );
if ( timer ){ // 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的 ID 集合
clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};
缓存代理
有点动态规划的味道。核心是将之前的结果和参数存储起来,在执行前先进行查询,如果之前有匹配的目标值,则直接返回,否则执行函数。
代码模拟乘积
let mult = function(){
let a = 1;
for(let i = 0; i < arguments.length; i++){
a = a * arguments[i];
}
return a;
}
let proxyMult = (function(){
const cache = {};
return function(){
// 处理参数,为`,`拼接的字符串
let args = Array.prototype.join.call( arguments, ',' );
// 在缓存中查找,若有该key值,返回其value
if( args in cache ){
return cache[ args];
}
// 否则重新计算
return cache[args] = mult.apply( this, arguments );
}
})
动态代理
通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理
/**************** 计算乘积 *****************/
var mult = function(){
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
/**************** 计算加和 *****************/
var plus = function(){
var a = 0;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a + arguments[i];
}
return a;
};
/**************** 创建缓存代理的工厂 *****************/
var createProxyFactory = function( fn ){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments );
}
};
var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出: 24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出: 24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出: 10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出: 10
Java(非主要方向,就不展开了)
强制代理
所谓强制代理,就是要求我们通过真实的角色找到代理角色。否则不能访问。
就好比,你和一领导挺熟去找他,然后领导说:“不行啊,这得按规矩来,这样吧,你去找下小代,让他那边过了我这边再给你处理。”本来绕过了小代,最后又得去找小代。这就叫强制代理。
还以代练这个代理为例:
// 接口类
public interface IGamePlayer {
//登录游戏
public void login(String user,String password);
//杀怪
public void killBoss();
//升级
public void upgrade();
//每个人都可以找一下自己的代理
public IGamePlayer getProxy();
}
// ===========真实角色===========
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;
}
}
}
// ===========真实角色===========
// ===========代理角色===========
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;
}
}
// ===========代理角色===========
// ===========场景类============
public class Client {
public static void main(String[] args) {
//定义一个游戏的角色
IGamePlayer player = new GamePlayer("张三");
//获得指定的代理
IGamePlayer proxy = player.getProxy();
proxy.login("zhangSan", "password");
//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
}
}
// ===========场景类============
JavaScript
强制代理
ES6 Class实现
class GamePlayer{ proxy = null constructor(name) { this.name = name; } login (user,password) { if (this.isProxy()) { console.log("登录名为" + user + "的用户" + this.name + "登录成功!") } else { console.log("请使用指定的代理访问"); } } upgrade () { if (this.isProxy()) { console.log(this.name + " 又升了一级! "); } else { console.log("请使用指定的代理访问"); } } killBoss () { if (this.isProxy()) { console.log(this.name + "在打怪! "); } else { console.log("请使用指定的代理访问"); } } getProxy () { this.proxy = new GamePlayerProxy(this); return this.proxy; } isProxy () { if (this.proxy == null) { return false; } else { return true; } } } class GamePlayerProxy{ gamePlayer = null; constructor(gamePlayer) { this.gamePlayer = gamePlayer; } killBoss () { this.gamePlayer.killBoss(); } login (user, password) { this.gamePlayer.login(user, password); } upgrade () { this.gamePlayer.upgrade(); } getProxy () { return this; } } let player = new GamePlayer("张三"); player.getProxy().login("zhangsan", "password"); player.getProxy().killBoss(); player.getProxy().upgrade();
js在使用class这种语法糖的时候,一些属性不太好私有化,感觉上还是用闭包会好点?
闭包实现
let gamePlayer = (function () { // ======私有属性、方法======= let name = ''; let proxy = null; let isProxy = function () { if (proxy == null) { return false; } else { return true; } } // ======私有属性、方法======= return function (_name) { name = _name; this.login = function (user, password) { if (isProxy()) { console.log("登录名为" + user + "的用户" + name + "登录成功!") } else { console.log("请使用指定的代理访问"); } } this.upgrade = function () { if (isProxy()) { console.log(name + " 又升了一级! "); } else { console.log("请使用指定的代理访问"); } } this.killBoss = function () { if (isProxy()) { console.log(name + "在打怪! "); } else { console.log("请使用指定的代理访问"); } } this.getProxy = function () { proxy = new GamePlayerProxy(this); return proxy; } } })() let GamePlayerProxy = (function () { // ======私有属性、方法======= let gamePlayer = null; // ======私有属性、方法======= return function (_gamePlayer) { gamePlayer = _gamePlayer; this.killBoss = function () { gamePlayer.killBoss(); }; this.login = function (user, password) { gamePlayer.login(user, password); }; this.upgrade = function () { gamePlayer.upgrade(); }; this.getProxy = function () { return this; }; } })() let player = new gamePlayer("张三"); // 直接访问 player.login("zhangsan", "password"); player.killBoss(); player.upgrade(); // 代理访问 player.getProxy().login("zhangsan", "password"); player.getProxy().killBoss(); player.getProxy().upgrade();
存在一个bug就是,getProxy()执行后,直接用player访问也行了。所以仅当做一个实现效果的参考即可。
动态代理
// ============动态代理类===========
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;
}
}
// ============动态代理类===========
// ============场景类===========
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,GamePlayer.class.getInterfaces(),handler);
//登录
proxy.login("zhangSan", "password");//开始杀怪
proxy.killBoss();
//升级
proxy.upgrade();
//记录结束游戏时间
System.out.println("结束时间是: 2009-8-26 03:40");
}
}
// ============场景类===========
代理模式的优劣总结
优点
-
职责清晰
-
高扩展性
基本上只需要更改代理者即可实现扩展。
-
智能化
总结
代理模式包括许多小分类,在 JavaScript 开发中最常用的是虚拟代理和缓存代理。在java中实现代理模式的小类会更多一点。