代理模式是常用的结构型设计模式之一,当无法直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。根据代理模式的使用目的不同,代理模式又可以分为多种类型,例如保护代理、远程代理、虚拟代理、缓冲代理等,它们应用于不同的场合,满足用户的不同需求。
15.1 代理模式概述
近年来,代购已逐步成为电子商务的一个重要分支。何谓代购,简单来说就是找人帮忙购买所需要的商品,当然你可能需要向实施代购的人支付一定的费用。代购通常分为两种类型:一种是因为在当地买不到某件商品,又或者是因为当地这件商品的价格比其他地区的贵,因此托人在其他地区甚至国外购买该商品,然后通过快递发货或者直接携带回来;还有一种代购,由于消费者对想要购买的商品相关信息的缺乏,自已无法确定其实际价值而又不想被商家宰,只好委托中介机构帮其讲价或为其代买。代购网站为此应运而生,它为消费者提供在线的代购服务,如果看中某国外购物网站上的商品,可以登录代购网站填写代购单并付款,代购网站会帮助进行购买然后通过快递公司将商品发送给消费者。商品代购过程如图15-1所示:
图15-1 商品代购示意图
在软件开发中,也有一种设计模式可以提供与代购网站类似的功能。由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。
代理模式是一种应用很广泛的结构型设计模式,而且变化形式非常多,常见的代理形式包括远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等,后面将学习这些不同的代理形式。
代理模式定义如下:
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。 Proxy Pattern: Provide a surrogate or placeholder for another object to control access to it. |
代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务。
15.2 代理模式结构与实现
15.2.1 模式结构
代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层,代理模式结构如图15-2所示:
图15-2 代理模式结构图
由图15-2可知,代理模式包含如下三个角色:
(1) Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
(2) Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
(3) RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
15.2.2 模式实现
代理模式的结构图比较简单,但是在真实的使用和实现过程中要复杂很多,特别是代理类的设计和实现。
抽象主题类声明了真实主题类和代理类的公共方法,它可以是接口、抽象类或具体类,客户端针对抽象主题类编程,一致性地对待真实主题和代理主题,典型的抽象主题类代码如下:
abstract class Subject
{
public abstract void Request();
}
真实主题类继承了抽象主题类,提供了业务方法的具体实现,其典型代码如下:
class RealSubject : Subject
{
public override void Request()
{
//业务方法具体实现代码
}
}
代理类也是抽象主题类的子类,它维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法,在调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束,最简单的代理类实现代码如下:
class Proxy : Subject
{
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用
public void PreRequest()
{
…...
}
public override void Request()
{
PreRequest();
realSubject.Request(); //调用真实主题对象的方法
PostRequest();
}
public void PostRequest()
{
……
}
}
在实际开发过程中,代理类的实现比上述代码要复杂很多,代理模式根据其目的和实现方式不同可分为很多种类,其中常用的几种代理模式简要说明如下:
(1) 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
(2) 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
(3) 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
(4) 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
(5) 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。
namespace 代理模式
{
class Program
{
static void Main(string[] args)
{
GirlMM mm = new GirlMM();
mm.Name = "夏花";
Proxy proxy = new Proxy(mm);
proxy.GiveDolls();
proxy.GiveFlowers();
proxy.GiveChocolate();
Console.ReadLine();
}
}
/// <summary>
/// 被代理对象执行的操作接口
/// </summary>
interface IGiveGift
{
void GiveDolls();
void GiveFlowers();
void GiveChocolate();
}
/// <summary>
/// 被代理者希望代理执行操作的类
/// </summary>
class GirlMM
{
private string name;
public string Name
{
get { return name; }
set {name = value;}
}
}
/// <summary>
/// 被代理者
/// </summary>
class Pursuit: IGiveGift
{
GirlMM mm;
public Pursuit(GirlMM mm)
{
this.mm = mm;
}
public void GiveDolls()
{
Console.WriteLine(mm.Name+"送你洋娃娃");
}
public void GiveFlowers()
{
Console.WriteLine(mm.Name + "送你鲜花");
}
public void GiveChocolate()
{
Console.WriteLine(mm.Name + "送你巧克力");
}
}
class Proxy:IGiveGift
{
Pursuit gg;
public Proxy(GirlMM mm)
{
gg = new Pursuit(mm);
}
public void GiveDolls()
{
gg.GiveDolls();
}
public void GiveFlowers()
{
gg.GiveFlowers();
}
public void GiveChocolate()
{
gg.GiveChocolate();
}
}
}
委托与代理:
委托的实例:
class Program
{
static void Main(string[] args)
{
Teacher Tc = new Teacher("赵老师"); //实例化老师和学生
Student St1 = new Student("张三");
Student St2 = new Student("李四");
//把学生的行为交给 委托
Tc.TeacherCome += new Teacher.EventHandler(St1.StopCopyWork);
Tc.TeacherCome += new Teacher.EventHandler(St2.StopWhisper);
Tc.Come();
}
}
class Teacher
{
private string name;
public Teacher(string name)
{
this.name = name;
}
public delegate void EventHandler(); //声明一个委托EventHandler
public event EventHandler TeacherCome; //声明事件名称,事件类型为EventHandler
public void Come() //老师的行为
{
Console.WriteLine("{0}来了", name);
if (TeacherCome != null) //如果需要委托,则执行下面程序
{
TeacherCome();
}
}
}
class Student
{
private string name;
public Student(string name)
{
this.name = name;
}
public void StopWhisper()
{
Console.WriteLine("老师来了,{0}停止说话",name);
}
public void StopCopyWork()
{
Console.WriteLine("老师来了,{0}停止抄作业",name);
}
}
运行结果:
代理模式:
为其他对象提供一种代理,来帮助达到目的。
代理模式实例:
class Program
{
static void Main(string[] args)
{
SchoolGirl mm = new SchoolGirl();
mm.Name = "jiaojiao";
Proxy daili = new Proxy(mm);
daili.GiveDolls();
daili.GiveFlowers();
daili.GiveChocolate();
}
}
class SchoolGirl //定义 女孩类
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
interface IGiveGift //创建一个送礼物的接口
{
void GiveDolls();
void GiveFlowers();
void GiveChocolate();
}
class Pursuit:IGiveGift //追求者类,使用送礼物的接口
{
SchoolGirl mm; //要追求女孩的名字
public Pursuit(SchoolGirl mm)
{
this.mm = mm;
}
public void GiveDolls()
{
Console.WriteLine(mm.Name+"送你洋娃娃");
}
public void GiveFlowers()
{
Console.WriteLine(mm.Name + "送你花");
}
public void GiveChocolate()
{
Console.WriteLine(mm.Name + "送你巧克力");
}
}
class Proxy : IGiveGift //定义代理模式,也用送礼物的接口
{
Pursuit gg;
public Proxy(SchoolGirl mm) //区别在于,代替追求者 送礼物
{
gg=new Pursuit(mm);
}
public void GiveDolls()
{
gg.GiveDolls();
}
public void GiveFlowers()
{
gg.GiveFlowers();
}
public void GiveChocolate()
{
gg.GiveChocolate();
}
}
运行结果:
委托与代理的比较
代理:是把一些事情交给某人帮忙去完成。
委托:是当某件事情发生的时候,顺便干某件事情。委托就相当于一个触发器罢了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 代理模式
{
public abstract class Subject
{
public abstract void Request();
}
public class RealSubject : Subject
{
public override void Request()
{
Console.WriteLine("真实的请求!!!");
}
}
class Proxy : Subject
{
private RealSubject realSubject;
public override void Request()
{
if(realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Request();
}
}
}
客户端代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 代理模式
{
class Program
{
static void Main(string[] args)
{
Proxy proxy = new Proxy();
proxy.Request();
Console.ReadKey();
}
}
}
java:
实现
我们将创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。
ProxyPatternDemo,我们的演示类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。
步骤 1
创建一个接口。
public interface Image {
void display();
}
步骤 2
创建实现接口的实体类。
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
步骤 3
当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("");
// 图像不需要从磁盘加载
image.display();
}
}