定义
为其他对象提供一种代理以控制对这个对象的访问。
代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。
代理类负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
基本上可以理解为:代理类持有实际操作对象的引用,通过公开方法将这些引用的方法提供给其它类调用。
和其它模式的区别
- 和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理迷失不能改变所代理类的接口。
- 和装饰模式的区别:装饰模式为了增强功能,而代理模式是为了加以控制。
UML
优点
- 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。
- 具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用。
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
应用场景
远程代理
为一个对象在不同的地址空间提供局部代表。
虚代理
根据需要创建开销很大的对象。比如浏览网页时,图片可以使用代理先按宽高进行占位,下载好再进行显示。
保护代理
控制对原始对象的访问。保护代理用于对象应该有不同的访问权限的时候。
智能指针
取代了简单的指针,它在访问对象时执行一些附加操作。
它的典型用途包括:
- 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它。
- 当第一次引用一个持久对象时,将它装入内存。
- 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
示例
游戏玩家(被代练对象)和游戏代练者(代理对象)的示例,其中如果游戏玩家升级则游戏代练则要进行收费。
C++
C#
1 using System; 2 3 namespace DesignPattern 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 //李雷自己打游戏 10 GamePlayer liLei = new GamePlayer("李雷"); 11 liLei.Login(); 12 liLei.KillMonster(); 13 liLei.Upgrade(); 14 15 Console.WriteLine(); 16 17 //李雷花钱请游戏代练帮其升级 18 Leveling leveling = new Leveling(liLei); 19 leveling.Login(); 20 leveling.KillMonster(); 21 leveling.Upgrade(); 22 23 Console.Read(); 24 } 25 } 26 27 /// <summary> 28 /// 游戏玩家接口. 29 /// </summary> 30 public interface IGamePlayer 31 { 32 /// <summary> 33 /// 登录. 34 /// </summary> 35 void Login(); 36 37 /// <summary> 38 /// 打怪. 39 /// </summary> 40 void KillMonster(); 41 42 /// <summary> 43 /// 升级. 44 /// </summary> 45 void Upgrade(); 46 } 47 48 /// <summary> 49 /// 玩家类. 50 /// </summary> 51 public class GamePlayer : IGamePlayer 52 { 53 private string _name; 54 55 public GamePlayer(string name) 56 { 57 _name = name; 58 } 59 60 public void Login() 61 { 62 Console.WriteLine("玩家"" + _name + ""登录游戏。"); 63 } 64 65 public void KillMonster() 66 { 67 Console.WriteLine("玩家"" + _name + ""开始打怪。"); 68 } 69 70 public void Upgrade() 71 { 72 Console.WriteLine("玩家"" + _name + ""等级提升一级。"); 73 } 74 } 75 76 /// <summary> 77 /// 游戏代练类. 78 /// </summary> 79 public class Leveling : IGamePlayer 80 { 81 private GamePlayer _gamePlayer; 82 83 public Leveling(GamePlayer gamePlayer) 84 { 85 _gamePlayer = gamePlayer; 86 } 87 88 public void Login() 89 { 90 _gamePlayer.Login(); 91 } 92 93 public void KillMonster() 94 { 95 _gamePlayer.KillMonster(); 96 } 97 98 public void Upgrade() 99 { 100 _gamePlayer.Upgrade(); 101 102 Console.WriteLine("游戏代练者收费。"); 103 } 104 } 105 }
Java
Java的示例使用延迟代理和动态代理查询天气的例子。
1 public class Main 2 { 3 public static void main(String[] args) 4 { 5 //不使用代理 6 IWeather weather1 = new ChinaWeather(); 7 System.out.println(weather1.getWeatherByCity("上海")); 8 9 //延迟代理 10 IWeather weather2 = new ChinaWeatherDelayProxy(); 11 System.out.println(weather2.getWeatherByCity("北京")); 12 13 //动态代理 14 IWeather weather3 = new WeatherDynamicProxy(new InternationalWeather()); 15 System.out.println(weather3.getWeatherByCity("北京")); 16 } 17 18 /** 19 * 天气查询接口 20 */ 21 public interface IWeather 22 { 23 /** 24 * 获取指定城市的天气情况 25 */ 26 String getWeatherByCity(String city); 27 28 /** 29 * 请求查询 30 */ 31 void request(); 32 } 33 34 /** 35 * 使用中国的天气服务器查询天气情况,只能查询到中国的天气 36 */ 37 public static class ChinaWeather implements IWeather 38 { 39 public ChinaWeather() 40 { 41 this.request(); 42 } 43 44 @Override 45 public String getWeatherByCity(String city) 46 { 47 if(city.equals("北京")) 48 { 49 return "晴 28度 PM2.5 20"; 50 } 51 if(city.equals("上海")) 52 { 53 return "晴 33度 PM2.5 10"; 54 } 55 return "未知"; 56 } 57 58 @Override 59 public void request() 60 { 61 System.out.println("请求中国的天气服务器,解析其格式得到信息"); 62 } 63 } 64 65 /** 66 * 使用国际的天气服务器查询天气情况,当然也可以查询到中国的天气了 67 */ 68 public static class InternationalWeather implements IWeather 69 { 70 public InternationalWeather() 71 { 72 this.request(); 73 } 74 75 @Override 76 public String getWeatherByCity(String city) 77 { 78 if(city.equals("北京")) 79 { 80 return "晴 29度 PM2.5 500+"; 81 } 82 if(city.equals("上海")) 83 { 84 return "晴 32度 PM2.5 500+"; 85 } 86 return "未知"; 87 } 88 89 @Override 90 public void request() 91 { 92 System.out.println("请求国际的天气服务器,解析其格式得到信息"); 93 } 94 } 95 96 /** 97 * 延迟代理 98 */ 99 public static class ChinaWeatherDelayProxy implements IWeather 100 { 101 private IWeather weather; 102 103 private IWeather getWeather() 104 { 105 if(weather == null) 106 { 107 weather = new ChinaWeather(); 108 } 109 return weather; 110 } 111 112 @Override 113 public String getWeatherByCity(String city) 114 { 115 return getWeather().getWeatherByCity(city); 116 } 117 118 @Override 119 public void request() 120 { 121 getWeather().request(); 122 } 123 } 124 125 /** 126 * 动态代理 127 */ 128 public static class WeatherDynamicProxy implements IWeather 129 { 130 private IWeather weather; 131 132 public WeatherDynamicProxy(IWeather weather) 133 { 134 this.weather = weather; 135 } 136 137 @Override 138 public String getWeatherByCity(String city) 139 { 140 return weather.getWeatherByCity(city); 141 } 142 143 @Override 144 public void request() 145 { 146 weather.request(); 147 } 148 } 149 }
AS3
我的经验总结
在PureMVC框架中,其Model层使用了Proxy的设计模式。
我们先看看Model层的主要功能:
- 保存程序数据;
- 远程消息发送及请求。
在我们的游戏中,分别存在UserData及SocketConnection这两个对象,其中UserData用来存储用户数据,SocketConnection用来处理所有远程消息的接收和发送,而每个模块的Proxy都是对这两个对象的代理,其提供该模块需要的数据和操作接口给该模块,比如技能模块中,Proxy会提供技能相关的数据和协议给到该模块的View层。代理类为模块提供了屏蔽不需要的接口的功能。
然而在PureMVC的升级版框架RobotLegs中,其定义的是MVCS的框架,Model层直接设计为Model+Services的组合,没有使用代理模式,由于没有代理模式,所以中介类中就不是直接操作代理类,而一般情况是通过发送Command,在Command中进行处理。