情景分析
在魔兽世界的广为流传着这样一句话 , 当你满级的时候 , 这个游戏开算开始 . 然而为令人头疼的就是练级 , 想想现在满级已经120级了 , 要是申请个新号 , 这得练到什么时候 . 所以果断去找代练 , 将我们的账号交给他们 , 由他们去帮我升级 , 打怪 .于是我们得到下面的类图 :
类图中的GamePlayerProxy类表示游戏的代练者 , 他也不能作弊 , 游戏代练者也是动手打怪 , 因此同样继承IGamePlayer接口(登录 , 杀怪 , 升级)
代码实现 :
#include<iostream>
#include<string>
//#include<vld.h>
using namespace std;
//代理模式
#if 0
//游戏者接口
class IGamePlayer
{
public:
//登陆游戏
virtual void login(string user,string passwrd) = 0;
//杀怪 , 网络游戏的主要特色
virtual void killBoss() = 0;
//升级
virtual void upgrade() = 0;
//不将析构函数定义成虚函数 , 会造成内存泄漏
virtual ~IGamePlayer()
{}
};
//游戏者
class GamePlayer : public IGamePlayer
{
public:
//通过构造函数传递名称
GamePlayer(string name)
{
_name = name;
}
//打怪 , 最期望的就是杀老怪
void killBoss()
{
cout<<_name+" 在打怪!"<<endl;
}
//打游戏之前你肯定登录吧 , 这是一个必须要条件
void login(string user , string password)
{
cout<<"登录名为"+user+"的用户: "+_name+" 登录成功!"<<endl;
}
//升级 , 升级有很多种方法 , 花钱买是一种, 做任务也是一种
void upgrade()
{
cout<<_name+" 又升了一级!"<<endl;
}
private:
string _name;
};
//代练者
class GamePlayerProxy : public IGamePlayer
{
public:
//通过构造函数传递要对谁进行代练
GamePlayerProxy(IGamePlayer* gamePlayer = nullptr)
{
_gamePlayer = gamePlayer;
}
//代练杀怪
void killBoss()
{
_gamePlayer->killBoss();
}
//代练登录
void login(string user , string password)
{
_gamePlayer->login(user,password);
}
//代练升级
void upgrade()
{
_gamePlayer->upgrade();
}
private:
IGamePlayer* _gamePlayer;
};
int main()
{
//定义一个痴迷的玩家
IGamePlayer* player = new GamePlayer("张三");
//然后再定义一个代练者
IGamePlayer* proxy = new GamePlayerProxy(player);
//开始打游戏 , 记下时间戮
cout<<"开始时间是 : 2019-5-30 15:42"<<endl;
proxy->login("zhangsan","password");
//开始杀怪
proxy->killBoss();
//升级
proxy->upgrade();
//记录结束时间
cout<<"结束时间是 : 2019-5-31 12:00"<<endl;
delete player;
delete proxy;
return 0;
}
#endif
运行结果 :
代理模式的定义
定义 : 为其他对象提供一种代理以控制对这个对象的访问
代理模式的通用类图 :
代理模式也叫做委托模式,它是一项基本设计技巧 . 许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制 . 在一些著名开源软件中也经常见到它的身影,如Struts2的Form元素映射就采用了代理模式(准确地说是动态代理模式) . 我们先看一下类图中的三个角色的定义:
- Subject抽象主题角色
抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求 . - RealSubject具体主题角色
也叫做被委托角色、被代理角色 . 它才是冤大头,是业务逻辑的具体执行者 . - Proxy代理主题角色
也叫做委托类、代理类 . 它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作 .
代理模式的应用
代理模式的优点
- 职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰 . - 高扩展性
具体主题角色是随时都会发生变化的,只要它实现了接口,甭管它如何变化,都逃不脱如来佛的手掌(接口),那我们的代理类完全就可以在不做任何修改的情况下使用 . - 智能化
有兴趣的读者也可以看看Struts是如何把表单元素映射到对象上的 .
代理模式的使用场景
我相信第一次接触到代理模式的读者肯定很郁闷,为什么要用代理呀?想想现实世界吧,打官司为什么要找个律师?因为你不想参与中间过程的是是非非,只要完成自己的答辩就成,其他的比如事前调查、事后追查都由律师来搞定,这就是为了减轻你的负担 .
代理模式的扩展
普通代理
我们设计模式中的普通代理和强制代理也是类似的一种结构,普通代理就是我们要知道代理的存在,也就是类似的GamePlayerProxy这个类的存在,然后才能访问;强制代理则是调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的 .
普通代理,它的要求就是客户端只能访问代理角色,而不能访问真实角色 . 我们以上面的例子作为扩展,我自己作为一个游戏玩家,我肯定自己不练级了,也就是场景类(main())不能再直接new一个GamePlayer对象了,它必须由GamePlayerProxy来进行模拟场景,类图修改如图:
仅仅修改了两个实现类的构造函数,GamePlayer的构造 函数增加了_gamePlayer参数,而代理角色则只要传人代理者名字即可,而不需要说是替哪个对象做代理 .
实现代码 :
#include<iostream>
#include<string>
#include<vld.h>
using namespace std;
//普通代理模式
#if 0
//游戏者接口
class IGamePlayer
{
public:
//登陆游戏
virtual void login(string user,string passwrd) = 0;
//杀怪 , 网络游戏的主要特色
virtual void killBoss() = 0;
//升级
virtual void upgrade() = 0;
//不将析构函数定义成虚函数 , 会造成内存泄漏
virtual ~IGamePlayer()
{}
};
//普通代理的游戏者
class GamePlayer : public IGamePlayer
{
public:
//通过构造函数传递名称
GamePlayer(IGamePlayer* _gamePlayer , string name)
{
if(_gamePlayer == nullptr)
{
cout<<"不能创建真实角色!"<<endl;
}
else
{
_name = name;
}
}
//打怪 , 最期望的就是杀老怪
void killBoss()
{
cout<<_name+" 在打怪!"<<endl;
}
//打游戏之前你肯定登录吧 , 这是一个必须要条件
void login(string user , string password)
{
cout<<"登录名为"+user+"的用户: "+_name+" 登录成功!"<<endl;
}
//升级 , 升级有很多种方法 , 花钱买是一种, 做任务也是一种
void upgrade()
{
cout<<_name+" 又升了一级!"<<endl;
}
private:
string _name;
};
//普通代理的代练者
class GamePlayerProxy : public IGamePlayer
{
public:
//通过构造函数传递要对谁进行代练
GamePlayerProxy(string name)
{
try
{
_gamePlayer = new GamePlayer(this,name);
}
catch(exception e)
{}
}
~GamePlayerProxy()
{
delete _gamePlayer;
}
//代练杀怪
void killBoss()
{
_gamePlayer->killBoss();
}
//代练登录
void login(string user , string password)
{
_gamePlayer->login(user,password);
}
//代练升级
void upgrade()
{
_gamePlayer->upgrade();
}
private:
IGamePlayer* _gamePlayer ;
};
int main()
{
//然后再定义一个代练者
IGamePlayer* proxy = new GamePlayerProxy("张三");
//开始打游戏 , 记下时间戮
cout<<"开始时间是 : 2019-5-30 15:42"<<endl;
proxy->login("zhangsan","password");
//开始杀怪
proxy->killBoss();
//升级
proxy->upgrade();
//记录结束时间
cout<<"结束时间是 : 2019-5-31 12:00"<<endl;
delete proxy;
return 0;
}
#endif
运行结果 :
运行结果完全相同 . 在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响,真实的主题角色想怎么修改就怎么修改,对高层次的模块没有任何的影响,只要你实现了接口所对应的方法,该模式非常适合对扩展性要求较高的场合 . 当然,在实际的项目中,一般都是通过约定来禁止new一个真实的角色,这也是一个非常好的方案 .
注意 : 普通代理模式的约束问题,尽量通过团队内的编程规范类约束,因为每一个主题类是可被重用的和可维护的,使用技术约束的方式对系统维护是一种非常不利的因素 .
强制代理
一般的思维都是通过代理找到真实的角色 , 但是强制代理却是要" 强制 " , 你必须通过真实角色查找到代理角色 , 否则你不能访问 . 只有通过真实角色指定的代理类才可以访问 , 也就是说由真实角色管理代理角色 .
强制代理类图 :
代码实现 :
#include<iostream>
#include<string>
#include<vld.h>
using namespace std;
//游戏者接口
class IGamePlayer
{
public:
//登陆游戏
virtual void login(string user,string passwrd) = 0;
//杀怪 , 网络游戏的主要特色
virtual void killBoss() = 0;
//升级
virtual void upgrade() = 0;
//每个人可以找一下自己的代理
virtual IGamePlayer* getProxy() = 0;
//不将析构函数定义成虚函数 , 会造成内存泄漏
virtual ~IGamePlayer()
{}
};
//强制代理的代练者
class GamePlayerProxy : public IGamePlayer
{
public:
//通过构造函数传递要对谁进行代练
GamePlayerProxy(IGamePlayer* gamePlayer = nullptr)
{
_gamePlayer = gamePlayer;
}
//代练杀怪
void killBoss()
{
_gamePlayer->killBoss();
}
//代练登录
void login(string user , string password)
{
_gamePlayer->login(user,password);
}
//代练升级
void upgrade()
{
_gamePlayer->upgrade();
}
//代理的代理暂时还没有 , 就是自己
IGamePlayer* getProxy()
{
return this;
}
private:
IGamePlayer* _gamePlayer ;
};
//强制代理的真实角色
class GamePlayer : public IGamePlayer
{
public:
//通过构造函数传递名称
GamePlayer(string name)
: _proxy(nullptr)
{
_name = name;
}
//找到自己的代理
IGamePlayer* getProxy()
{
_proxy = new GamePlayerProxy(this);
return _proxy;
}
//打怪 , 最期望的就是杀老怪
void killBoss()
{
if(isProxy())
{
cout<<_name+" 在打怪!"<<endl;
}
else
{
cout<<"请使用指定的代理访问"<<endl;
}
}
//打游戏之前你肯定登录吧 , 这是一个必须要条件
void login(string user , string password)
{
if(isProxy())
{
cout<<"登录名为"+user+"的用户: "+_name+" 登录成功!"<<endl;
}
else
{
cout<<"请使用指定的代理访问"<<endl;
}
}
//升级 , 升级有很多种方法 , 花钱买是一种, 做任务也是一种
void upgrade()
{
if(isProxy())
{
cout<<_name+" 又升了一级!"<<endl;
}
else
{
cout<<"请使用指定的代理访问"<<endl;
}
}
private:
string _name;
IGamePlayer* _proxy;
bool isProxy()
{
if(_proxy == nullptr)
{
return false;
}
else
{
return true;
}
}
};
我们先按照常规思路来运行一下 , 直接new一个真实的角色
//强制代理的错误场景1
#if 0
int main()
{
//定义一个痴迷的玩家
IGamePlayer* player = new GamePlayer("张三");
//开始打游戏 , 记下时间戮
cout<<"开始时间是 : 2019-5-30 15:42"<<endl;
player->login("zhangsan","password");
//开始杀怪
player->killBoss();
//升级
player->upgrade();
//记录结束时间
cout<<"结束时间是 : 2019-5-31 12:00"<<endl;
delete player;
return 0;
}
#endif
运行结果 :
它要求你必须通过代理来访问 , 你想要直接访问它 , 门儿都没有 . 那我们接着通过代理来访问 , 那就产生一个代理
//强制代理的错误场景2
#if 0
int main()
{
//定义一个痴迷的玩家
IGamePlayer* player = new GamePlayer("张三");
//再定义一个代练者
IGamePlayer* proxy = new GamePlayerProxy(player);
//开始打游戏 , 记下时间戮
cout<<"开始时间是 : 2019-5-30 15:42"<<endl;
proxy->login("zhangsan","password");
//开始杀怪
proxy->killBoss();
//升级
proxy->upgrade();
//记录结束时间
cout<<"结束时间是 : 2019-5-31 12:00"<<endl;
delete player;
delete proxy;
return 0;
}
#endif
运行结果 :
还是不能访问,为什么呢? 它不是真实角色指定的对象,这个代理对象是你自己new出来的,当然真实对象不认了,这就好比是那个明星,人家已经告诉你去找她的代理人了,你随便找个代理人能成吗?你必须去找她指定的代理才成! 那我们再来修改一下
int main()
{
//定义一个痴迷的玩家
IGamePlayer* player = new GamePlayer("张三");
//注意! 获得指定的代理
IGamePlayer* proxy = player->getProxy();
//开始打游戏 , 记下时间戮
cout<<"开始时间是 : 2019-5-30 15:42"<<endl;
proxy->login("zhangsan","password");
//开始杀怪
proxy->killBoss();
//升级
proxy->upgrade();
//记录结束时间
cout<<"结束时间是 : 2019-5-31 12:00"<<endl;
delete player;
delete proxy;
return 0;
}
运行结果 :
OK , 可以正常访问了 . 强制代理的概念就是要从真实角色查找到代理角色 , 不允许直接访问真实角色.
参考书籍 :
<<设计模式之禅 第二版>>
<<设计模式>>