设计模式系列-代理模式
今天下班,同事们无聊中又聊到了食堂(怎么写食堂?吃货啊!),办公区离食堂远的同事老阮在大家你一句我一句的诱惑下,终于忍不住了决定不管在远也要找时间去尝尝,但是因为上班不顺路也不是很方便,就委托我们宿舍的老李同志帮忙第二天先去开卡充值,热心肠的老李当然不会拒绝喽。
1、模拟场景
有了这个前奏今天的主题就当然又有了,那么首先我们用代码来实现上面的功能,首先来分析一下上面的场景:
①我们需要对卡片充值,所以需要一个卡片类,代码如下:
/// 卡片类
/// </summary>
public class Card
{
//卡片名称
private string cardName = string.Empty;
public string CardName
{
get
{
return cardName;
}
set
{
cardName = value;
}
}
//构造方法
public Card() { }
public Card(string cName)
{
cardName = cName;
}
}
②接下来就是路远的老阮,他的工卡需要去激活,代码如下:
/// 老阮
/// </summary>
public class MrRuan
{
//他有一个工卡
private Card myCard = new Card("mrRuan");
public Card MyCard
{
get
{
return myCard;
}
}
}
③ 接下来就是激活卡片当然要去柜台了,记得在单例模式中已经将这个柜台类实现了,那么我们就拿来用现成的吧,代码如下:
/// 柜台类
/// </summary>
public sealed class Counter
{
//在第一次调用类成员时,初始化唯一实例
private static readonly Counter instance = new Counter();
private Counter()
{
}
//返回类型实例属性
public static Counter Instance
{
get
{
return instance;
}
}
//激活工卡方法
public void ActivationCard(Card card)
{
//激活工卡的过程
Console.WriteLine("{0}的工卡激活成功!",card.CardName);
}
}
④ 再就是帮助老软第二天去激活卡片的老李,因为老李需要去激活卡片,所以包含激活卡片的操作,代码如下:
/// 热心肠的老李
/// </summary>
public class MrLi
{
//激活工卡方法
public void ActivationCard(Card card)
{
Counter.Instance.ActivationCard(card);
}
}
⑤ 类型都抽象出来之后当然就是第二天去激活卡片啦,主函数调用代码如下:
{
//实例化老阮
MrRuan ruan = new MrRuan();
//实例化老李
MrLi li = new MrLi();
//将卡片给老李,老李负责去激活
li.ActivationCard(ruan.MyCard);
}
仔细想了一下,这样一来老李也就成了柜台开卡的代理人了,以后别人也可以委托他去帮忙激活卡片了,这不就是活生生的代理模式吗?
这时你要问了,那代理模式具体的定义是什么呀?到底什么是代理模式下?别着急继续往下看吧。
2、代理模式
代理(Proxy)模式定义:为其他对象提供一种代理以控制对这个对象的访问。 类图如下:
这时我们发现了多了一个接口ICounter,那么实现了接口的代理类与具体类的代码如下:
/// 柜台类也就是具体类提供的功能规则
/// </summary>
public interface ICounter
{
void ActivationCard(Card card);
}
/// <summary>
/// 热心肠的老李
/// </summary>
public class MrLi : ICounter
{
//激活工卡方法
public void ActivationCard(Card card)
{
Counter.Instance.ActivationCard(card);
}
}
/// <summary>
/// 柜台类
/// </summary>
public sealed class Counter : ICounter
{
//在第一次调用类成员时,初始化唯一实例
private static readonly Counter instance = new Counter();
private Counter()
{
}
//返回类型实例属性
public static Counter Instance
{
get
{
return instance;
}
}
//激活工卡方法
public void ActivationCard(Card card)
{
//激活工卡的过程
Console.WriteLine("{0}的工卡激活成功!",card.CardName);
}
}
那么这个接口有什么用呢?
在面向对象设计中,对象之间需要进行交互和通信。例如:上面的代理类MrLi调用了具体类柜台类counter的激活卡片的方法(ActiviationCard),那么这个时候代理类MrLi不在代理counter柜台的激活卡片功能了,而是去另一个counterNew的柜台去激活,但是counterNew柜台激活卡片的方法是(CounterActiviationCard),怎么办?我们需要去修改调用counter的类,那么如何降低耦合性呢?当然就是将接口和实现分离开来,这样组件间和日志对象之间的依赖就是基于接口,而不是实现!
例如:目前MrLi与counter之间的调用如下:
/// 热心肠的老李
/// </summary>
public class MrLi
{
//激活工卡方法
public void ActivationCard(Card card)
{
Counter.Instance.ActivationCard(card);
}
}
那么如果需要加入新的柜台类(counterNew),它的激活方法叫(CounterActivationCard),代码如下:
/// 新的柜台类
/// </summary>
public sealed class NewCounter
{
//在第一次调用类成员时,初始化唯一实例
private static readonly NewCounter instance = new NewCounter();
private NewCounter()
{
}
//返回类型实例属性
public static NewCounter Instance
{
get
{
return instance;
}
}
//激活工卡方法
public void CounterActivationCard(Card card)
{
//激活工卡的过程
Console.WriteLine("{0}的工卡激活成功!", card.CardName);
}
}
这样两个类就紧密的耦合在一起了,若Counter类改变,那么MrLi类也不得不改变,这时我们如果想使用新的柜台类(NewCounter),也需要修改调用者本身。
所以我们需要使用接口分离实现。代码如下:
/// 柜台类也就是具体类提供的功能规则
/// </summary>
public interface ICounter
{
void ActivationCard(Card card);
}
/// <summary>
/// 热心肠的老李
/// </summary>
public class MrLi : ICounter
{
//激活工卡方法
public void ActivationCard(Card card)
{
Counter.Instance.ActivationCard(card);
}
}
/// <summary>
/// 柜台类
/// </summary>
public sealed class Counter : ICounter
{
//在第一次调用类成员时,初始化唯一实例
private static readonly Counter instance = new Counter();
private Counter()
{
}
//返回类型实例属性
public static Counter Instance
{
get
{
return instance;
}
}
//激活工卡方法
public void ActivationCard(Card card)
{
//激活工卡的过程
Console.WriteLine("{0}的工卡激活成功!",card.CardName);
}
}
/// <summary>
/// 新的柜台类
/// </summary>
public sealed class NewCounter : ICounter
{
//在第一次调用类成员时,初始化唯一实例
private static readonly NewCounter instance = new NewCounter();
private NewCounter()
{
}
//返回类型实例属性
public static NewCounter Instance
{
get
{
return instance;
}
}
//激活工卡方法
public void ActivationCard(Card card)
{
//激活工卡的过程
Console.WriteLine("{0}的工卡激活成功!", card.CardName);
}
}
有了接口的约束,所有柜台类都遵循了这个约束将激活卡片的方法统一成(ActivationCard),那么在将来切换对象的时候都可以以一种统一的调用方式去无缝的却换。
这时细心的朋友还会说,Mr.Li的ActivationCard方法中调用的是具体的counter类型如果换成Newcounter,还是要去修改它的代码,你现在只能是不用去修改调用方法了而已,想的好,还记得我们当时的工厂模式吗?它的好处不就是降低耦合吗?为什么不用?
那么加入工厂类(CounterProvider),在修改一下MrLi的调用使它的调用依赖于抽象接口而不是具体的实现,代码如下:
/// 热心肠的老李
/// </summary>
public class MrLi : ICounter
{
//激活工卡方法
public void ActivationCard(Card card)
{
ICounter counter = CounterProvider.GetCounter();
counter.ActivationCard(card);
}
}
/// <summary>
/// 柜台类工厂
/// </summary>
public class CounterProvider
{
public static ICounter GetCounter()
{
ICounter counter = null;
//从配置文件确定实例化那个柜台类
if (ReadConfig)
{
counter = Counter.Instance;
}
else
{
counter = NewCounter.Instance;
}
}
}
这样我们就彻底的实现了代理模式,并且诠释了如何使用接口的好处和灵活组合模式与灵活理解模式与使用。
3、应用场景
那么代理模式的几种使用场景我们来看一看:
① 远程代理:为一个对象在不同地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实,例如:老阮(MrRuan)在地点A,老李在地点B,餐厅柜台也在地点B,那么老李和老软住在一起(都在地点A住),那么老李就是餐厅(地点B)在老软与老李住处(地点A)的代表。
② 虚拟代理:是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真是对象,例如:老阮(MrRuan)在地点A,到餐厅柜台(地点B),因为距离远却是很费劲,而老李刚好在这里(地点B)上班,所以让老李去办是很可行的办法。(不太恰当)
③ 安全代理:用来控制真是对象访问时的权限,例如:老阮跟餐厅的柜台MM刚分手不方便去办理,所以需要借助老李去完成事项的办理。
④ 智能代理:是指当调用真是的对象时,代理去处理另外一些事情,例如:老李帮助老阮办理卡片激活时,顺便说说老阮的好话,让她俩能够和好。