我是如何学习设计模式的三:模式二:代理模式

模式二:代理模式

来源于生活
一定要理解一句话

服务提供者对象仅提供它自己的特定功能

 计数代理模式在客户对象调用服务提供者对象上方法的前后执行诸如日志(logging)和计数(counting)一系列附加功能时很有用。计数代理模式建议把这些附加功能封装在一个单独的对象,这个对象就是指计数代理对象,而不是把这些附加的功能实现放到服务提供者的内部。良好的对象设计的一个特征就是对象要专注于提供特定的功能。换句话说,理想的对象不应该做各种不相干的事情。把诸如日志(logging)和计数(counting)等类似的功能封装为一个单独的对象,而让服务提供者对象仅提供它自己的特定功能。也就是说,只允许服务提供者对象执行定义良好、特定的任务。

  计数代理被设计成可以被客户访问的与服务提供者具有相同接口的对象。客户对象不是直接访问服务提供者,而是调用计数代理对象上的方法,计数代理执行必要的纪录日志(logging)和计数(counting)功能后,再把方法调用传递给服务提供着对象。如图1


Figure1: Generic Class Association When the Counting Proxy Pattern Is Applied



  下面的例子说明了如何在应用程序中利用计数代理。

  例子:

  让我们设计一个Order类,类层次如图2OrderIF接口声明了getAllOrders读取数据库中所有订单的简单方法。


Figure2: Order Class Hierarchy

 

public interface OrderIF {
public Vector getAllOrders();
}


  作为getAllOrders方法实现的一部分,Order类实用了FileUtil工具类从order.txt文件中读取订单项。

public class Order implements OrderIF {
 public Vector getAllOrders() {
  FileUtil fileUtil = new FileUtil();
  Vector v = fileUtil.fileToVector("orders.txt");
  return v;
 }
}


  让我们假定在调用getAllOrders()时,需要把取数据文件所花费的时间和记录条数要记录的log日志文件中。

  这个附加的功能可以设计一个单独的OrderProxy类来实现,它与真实对象Order一样实现OrderIF接口。这样保证了OrderProxy对象提供给客户与真实对象Order一样的接口。如图3


Figure3: Order Class Hierarchy with the Counting Proxy

 

public class OrderProxy implements OrderIF {
 private int counter = 0;
 public Vector getAllOrders() {
  Order order = new Order();
  counter++;
  long t1 = System.currentTimeMillis ();
  Vector v = order.getAllOrders();
  long t2 = System.currentTimeMillis();
  long timeDiff = t2 ? t1;
  String msg = "Iteration=" + counter + "::Time=" + timeDiff + "ms";
  //log the message
  FileUtil fileUtil = new FileUtil();
  fileUtil.writeToFile("log.txt”,msg, true, true);
  return v;
 }
}


  客户对象MainApp就想调用真实对象Order一样调用OrderProxy对象上的getAllOrders()方法,OrderProxy对象传递这个调用给真实对象Order,计算读取所有订单所花费的时间并使用FileUtil帮助类将其纪录的log日志文件中。在这个过程中,OrderProxy扮演者计数代理的角色。

public class MainApp {
 public static void main(String[] args) {
  OrderIF order = new OrderProxy();
  Vector v = order.getAllOrders();
  v = order.getAllOrders();
  v = order.getAllOrders();
  v = order.getAllOrders();
 }
}

 

2在软件系统中,有些对象有时候由于跨越网络或者其他的障碍,而不能够或者不想直接访问另一个对象,如果直接访问会给系统带来不必要的复杂性,这时候可以在客户程序和目标对象之间增加一层中间层,让代理对象来代替目标对象打点一切。这就是本文要说的Proxy模式。

在软件系统中,我们无时不在跨越障碍,当我们访问网络上一台计算机的资源时,我们正在跨越网络障碍,当我们去访问服务器数据库时,我们又在跨越数据库访问障碍,同时还有网络障碍。跨越这些障碍有时候是非常复杂的,如果我们更多的去关注处理这些障碍问题,可能就会忽视了本来应该关注的业务逻辑问题,Proxy模式有助于我们去解决这些问题。

3猪八戒 高老庄

 六、高老庄悟空降八戒

  尽管那时候八戒还不叫八戒,但为了方便,这里仍然这样称呼他。

  高老庄的故事

  却说那春融时节,悟空牵着白马,与唐僧赶路西行。忽一日天色将晚,远远地望见一村人,这就是高老庄,猪八戒的丈人高太公家。为了将高家三小姐解救出八戒的魔掌,悟空决定扮做高小姐,会一会这个妖怪:

  "行者却弄神通,摇身一变,变得就如那女子一般,独自个坐在房里等那妖精。不多时,一阵风来,真个是走石飞砂……那阵狂风过处,只见半空里来了一个妖精,果然生得丑陋:黑脸短毛,长喙大耳,穿一领青不青、蓝不蓝的梭布直裰,系一条花布手巾……走进房,一把搂住,就要亲嘴……"

  高家三小姐的神貌和本人

  悟空的下手之处是将高家三小姐的神貌和她本人分割开来,这和"开一闭"原则有异曲同工之妙。这样一来,"高家三小姐本人"也就变成了"高家三小姐神貌"的具体实现,而"高家三小姐神貌"则变成了抽象角色,如下图所示。

  悟空扮演并代替高家三小姐

  悟空巧妙地实现了"高家三小姐神貌",也就是说同样变成了"高家三小姐神貌"的子类。悟空可以扮演高家三小姐,并代替高家三小姐会见八戒,其静态结构图如下图所示。

  悟空代替"高家三小姐本人"去会见猪八戒。显然这就是代理模式的应用。具体地讲,这是保护代理模式的应用。只有代理对象认为合适时,才会将客户端的请求传递给真实主题对象。

 八戒分辨不出真假老婆

  从《西游记》的描述可以看出,猪八戒根本份辨不出悟空扮演的"高家三小姐替身" "高家三小姐本人"。客户端分辨不出代理主题对象与真实主题对象,这是代理模式的一个

  重要用意。

  悟空代替高家三小姐会见八戒的对象图如下图所示。

4解耦合 实现对业务的装饰

我们可以实现一个功能, 这个功能是一个类,这个类很专业,只需关注 如何把自己做的更好.

但是我们在应用这个功能的时候,要有前期的准备工作,或者使用完这个功能后要做一些善后工作,我们不需要这个功能类把所有的问题都实现,这样,这个功能类只需要负责自己,至于其他工作 都交给代理

   代理模式和修饰模式(Decorator Pattern)有一定的相似之处。两个模式又使用了代理将方法调用传递给另一个对象,该对象被称为真实对象(Real Subject)。代理模式和修饰模式的不同之处在于:在代理模式中,代理和真实对象之间的关系在程序被编译的时候就确定下来了,而修饰模式则是在运行时递归地创建。

    Proxy代理模式是一种结构型设计模式,主要解决的问题是:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。如下图:
   

    比如说CA不在一个服务器上,A要频繁的调用C,我们可以在A上做一个代理类Proxy,把访问C的工作交给Proxy,这样对于A来说,就好像在直接访问C的对象。在对A的开发中我们可以把注意力完全放在业务的实现上。

场景

  代理模式非常常用,大致的思想就是通过为对象加一个代理来降低对象的使用复杂度、或是提升对象使用的友好度、或是提高对象使用的效率。在现实生活中也有很多代理的角色,比如明星的经纪人,他就是一种代理,经纪人为明星处理很多对外的事情,目的是为了节省被代理对象也就是明星的时间。保险代理人帮助投保人办理保险,目的降低投保的复杂度。

  在开发中代理模式也因为目的不同效果各不相同。比如,如果我们的网站程序是通过.NET Remoting来访问帐号服务的。在编写代码的时候可能希望直接引用帐号服务的DLL,直接实例化帐号服务的类型以方便调试。那么,我们可以引入Proxy模式,做一个帐号服务的代理,网站只需要直接调用代理即可。在代理内部实现正式和测试环境的切换,以及封装调用.NET Remoting的工作。

  示例代码

以下是引用片段:
  using System;
  using System.Collections.Generic;
  using System.Text;
  namespace ProxyExample
  {
  class Program
  {
  static void Main(string[] args)
  {
  AccountProxy ap = new AccountProxy();
  ap.Register();
  }
  }
  interface IAccount
  {
  void Register();
  }
  class Account : IAccount
  {
  public void Register()
  {
  System.Threading.Thread.Sleep(1000);
  Console.WriteLine("Done");
  }
  }
  class AccountProxy : IAccount
  {
  readonly bool isDebug = true;
  IAccount account;
  public AccountProxy()
  {
  if (isDebug)
  account = new Account();
  else
  account = (IAccount)Activator.GetObject(typeof(IAccount), "uri");
  }
  public void Register()
  {
  Console.WriteLine("Please wait...");
  account.Register();
  }
  }
  }

  代码说明

  IAccount就是抽象主题角色。代理对象和被代理对象都遵循这个接口,这样代理对象就能替换被代理对象。

  AccountProxy就是代理主题角色。代理主题通常会存在一些逻辑或预处理或后处理操作,不会仅仅是对操作的转发。

  Account就是真实主题角色。

  何时采用

  代理模式应用非常广泛,如果你希望降低对象的使用复杂度、或是提升对象使用的友好度、或是提高对象使用的效率都可以考虑代理模式。

  实现要点

  代理对象和被代理对象都遵循一致的接口。

  在某些情况下,可以不必保持接口一致性,如果封装确实需要损失一些透明度,那么也可以认为是Proxy

  注意事项

  ProxyFacade以及Adapter可能都是对对象的一层封装,侧重点不同。

场景

  代理模式非常常用,大致的思想就是通过为对象加一个代理来降低对象的使用复杂度、或是提升对象使用的友好度、或是提高对象使用的效率。在现实生活中也有很多代理的角色,比如明星的经纪人,他就是一种代理,经纪人为明星处理很多对外的事情,目的是为了节省被代理对象也就是明星的时间。保险代理人帮助投保人办理保险,目的降低投保的复杂度。

  在开发中代理模式也因为目的不同效果各不相同。比如,如果我们的网站程序是通过.NET Remoting来访问帐号服务的。在编写代码的时候可能希望直接引用帐号服务的DLL,直接实例化帐号服务的类型以方便调试。那么,我们可以引入Proxy模式,做一个帐号服务的代理,网站只需要直接调用代理即可。在代理内部实现正式和测试环境的切换,以及封装调用.NET Remoting的工作。

  示例代码

以下是引用片段:
  using System;
  using System.Collections.Generic;
  using System.Text;
  namespace ProxyExample
  {
  class Program
  {
  static void Main(string[] args)
  {
  AccountProxy ap = new AccountProxy();
  ap.Register();
  }
  }
  interface IAccount
  {
  void Register();
  }
  class Account : IAccount
  {
  public void Register()
  {
  System.Threading.Thread.Sleep(1000);
  Console.WriteLine("Done");
  }
  }
  class AccountProxy : IAccount
  {
  readonly bool isDebug = true;
  IAccount account;
  public AccountProxy()
  {
  if (isDebug)
  account = new Account();
  else
  account = (IAccount)Activator.GetObject(typeof(IAccount), "uri");
  }
  public void Register()
  {
  Console.WriteLine("Please wait...");
  account.Register();
  }
  }
  }

  代码说明

  IAccount就是抽象主题角色。代理对象和被代理对象都遵循这个接口,这样代理对象就能替换被代理对象。

  AccountProxy就是代理主题角色。代理主题通常会存在一些逻辑或预处理或后处理操作,不会仅仅是对操作的转发。

  Account就是真实主题角色。

  何时采用

  代理模式应用非常广泛,如果你希望降低对象的使用复杂度、或是提升对象使用的友好度、或是提高对象使用的效率都可以考虑代理模式。

  实现要点

  代理对象和被代理对象都遵循一致的接口。

  在某些情况下,可以不必保持接口一致性,如果封装确实需要损失一些透明度,那么也可以认为是Proxy

  注意事项

  ProxyFacade以及Adapter可能都是对对象的一层封装,侧重点不同。

 

一些可以使用代理模式(Proxy)的情况

一个对象,比如一幅很大的图像,需要载入的时间很长。

- Remote proxy
:远程代理。

该代理可以让客户端透明地引用一个存在于不同地址空间(远程或本地)的对象。

 

u 一个存在于远程计算机上的对象,需要通过网络载入这个远程对象则需要很长时间,特别是在网络传输高峰期。

- Virtual proxy:虚拟代理。

该代理允许一个对象只有在真正被用到时才被创建。

根据需要创建开销很大的对象。在动机一节描述的I m a g e P r o x y 就是这样一种代理的例子。 例如图片加载

- Copy-on-write proxy:对象拷贝延迟代理。

该代理可以延迟一个对象的拷贝(clone)操作到客户调用里,它是virtual proxy模式的一种特殊情况。一般来说,对象的深度克隆是一个高开销的动作,该代理可以让这个动作延迟,只有对象被用到的时候才被克隆。

- Protection (access) proxy:访问保护代理。

该代理可以在访问一个对象时附加一些检查操作,比如权限验证等。

一个对象只有有限的访问权限,代理模式(Proxy)可以验证用户的权限

- Cache proxy:缓存代理。

主要为那些创建时高开销的对象提供缓存,以供多客户端共享。

- Firewall proxy:防火墙代理。

保护目标对象不受某些不良客户端的侵害。

-Synchronization proxy:为非同步的目标对象提供并发控制。

- Smart reference proxy:当一个对象被引用时提供某些额外的操作

·         智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括:

·         对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为S m a r tP o i n t e r s[ E d e 9 2 ] )

·         当第一次引用一个持久对象时,将它装入内存。

·         在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

 

-一个需要很长时间才可以完成的计算结果,并且需要在它计算过程中显示中间结果

-代理模式(Proxy)也可以被用来区别一个对象实例的请求和实际的访问,例如:在程序初始化过程中


可能建立多个对象,但并不都是马上使用,代理模式(Proxy)可以载入需要的真正的对象。例如 :初始化的时候 ,加载很多对象,但由于对象在远程,或者需要计算才能加载好,这样我们可以用代理加载 代理对象,当真正的对象被完成后才去替换

(1)如果那个对象是一个是很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,打开文档必须很迅速,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片.

 

(2)如果那个对象在Internet的某个远端服务器上,直接操作这个对象因为网络速度原因可能比较慢,那我们可以先用Proxy来代替那个对象.

理论

所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代理的目标类交互,而代理一般在交互的过程中(交互前后),进行某些特别的处理。

实例
1远程调用一个函数

概述

在软件系统中,有些对象有时候由于跨越网络或者其他的障碍,而不能够或者不想直接访问另一个对象,如果直接访问会给系统带来不必要的复杂性,这时候可以在客户程序和目标对象之间增加一层中间层,让代理对象来代替目标对象打点一切。这就是本文要说的Proxy模式。

意图

为其他对象提供一种代理以控制对这个对象的访问。

结构图

1 Proxy模式结构图

 

生活中的例子

代理模式提供一个中介以控制对这个对象的访问。一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。

 

2 使用银行存单例子的Proxy模式对象图

Proxy模式解说

在软件系统中,我们无时不在跨越障碍,当我们访问网络上一台计算机的资源时,我们正在跨越网络障碍,当我们去访问服务器数据库时,我们又在跨越数据库访问障碍,同时还有网络障碍。跨越这些障碍有时候是非常复杂的,如果我们更多的去关注处理这些障碍问题,可能就会忽视了本来应该关注的业务逻辑问题,Proxy模式有助于我们去解决这些问题。我们以一个简单的数学计算程序为例,这个程序只负责进行简单的加减乘除运算:

/**//// <summary>

/// Author : Terrylee

/// From : http://terrylee.cnblogs.com

/// </summary>

public class Math
{
    
public double Add(double x,double y)
     {
        
return x + y;
    }

    
public double Sub(double x,double y)
     {
        
return x - y;
    }

    
public double Mul(double x,double y)
     {
        
return x * y;
    }

    
public double Dev(double x,double y)
     {
        
return x / y;
    }
}

如果说这个计算程序部署在我们本地计算机上,使用就非常之简单了,我们也就不用去考虑Proxy模式了。但现在问题是这个Math类并没有部署在我们本地,而是部署在一台服务器上,也就是说Math类根本和我们的客户程序不在同一个地址空间之内,我们现在要面对的是跨越Internet这样一个网络障碍:

3

这时候调用Math类的方法就没有下面那么简单了,因为我们更多的还要去考虑网络的问题,对接收到的结果解包等一系列操作。

/**//// <summary>

/// Author : Terrylee

/// From : http://terrylee.cnblogs.com

/// </summary>

public class App
{
    
public static void Main()
     {
        Math math = 
new Math();

        
// 对接收到的结果数据进行解包

        
double addresult = math.Add(2,3);

        
double subresult = math.Sub(2,3);

        
double mulresult = math.Mul(2,3);

        
double devresult = math.Dev(2,3);
    }
}

为了解决由于网络等障碍引起复杂性,就引出了Proxy模式,我们使用一个本地的代理来替Math类打点一切,即为我们的系统引入了一层间接层,示意图如下

4

我们在MathProxy中对实现Math数据类的访问,让MathProxy来代替网络上的Math类,这样我们看到MathProxy就好像是本地Math类,它与客户程序处在了同一地址空间内:

/**//// <summary>

/// Author : Terrylee

/// From : http://terrylee.cnblogs.com

/// </summary>

public class MathProxy
{
    
private Math math = new Math();

    
// 以下的方法中,可能不仅仅是简单的调用Math类的方法

    
public double Add(double x,double y)
     {
        
return math.Add(x,y);
    }

    
public double Sub(double x,double y)
     {
        
return math.Sub(x,y);
    }

    
public double Mul(double x,double y)
     {
        
return math.Mul(x,y);
    }

    
public double Dev(double x,double y)
     {
        
return math.Dev(x,y);
    }
}

现在可以说我们已经实现了对Math类的代理,存在的一个问题是我们在MathProxy类中调用了原实现类Math的方法,但是Math并不一定实现了所有的方法,为了强迫Math类实现所有的方法,另一方面,为了我们更加透明的去操作对象,我们在Math类和MathProxy类的基础上加上一层抽象,即它们都实现与IMath接口,示意图如下:

示意性代码如下:

/**//// <summary>

/// Author : Terrylee

/// From : http://terrylee.cnblogs.com

/// </summary>

public interface IMath
{
    double Add(double x,double y);

    double Sub(double x,double y);

    double Mul(double x,double y);

    double Dev(double x,double y);
}

Math
类和MathProxy类分别实现IMath接口:

public class MathProxy : IMath
{
    //
}

public class Math : IMath
{
    //
}

此时我们在客户程序中就可以像使用Math类一样来使用MathProxy类了:

/**//// <summary>

/// Author : Terrylee

/// From : http://terrylee.cnblogs.com

/// </summary>

public class App
{
    public static void Main()
     {
        MathProxy proxy = new MathProxy();

        double addresult = proxy.Add(2,3);

        double subresult = proxy.Sub(2,3);

        double mulresult = proxy.Mul(2,3);

        double devresult = proxy.Dev(2,3);
    }
}

到这儿整个使用Proxy模式的过程就完成了,回顾前面我们的解决方案,无非是在客户程序和Math类之间加了一个间接层,这也是我们比较常见的解决问题的手段之一。另外,对于程序中的接口Imath,并不是必须的,大多数情况下,我们为了保持对对象操作的透明性,并强制实现类实现代理类所要调用的所有的方法,我们会让它们实现与同一个接口。但是我们说代理类它其实只是在一定程度上代表了原来的实现类,所以它们有时候也可以不实现于同一个接口。

效果及实现要点

Proxy模式根据种类不同,效果也不尽相同:

1.远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中。远程代理又叫做大使(Ambassador)。好处是系统可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的对象是局域的而不是远程的,而代理对象承担了大部份的网络通讯工作。由于客户可能没有意识到会启动一个耗费时间的远程调用,因此客户没有必要的思想准备。

2.虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。使用虚拟代理模式的好处就是代理对象可以在必要的时候才将被代理的对象加载;代理可以对加载的过程加以必要的优化。当一个模块的加载十分耗费资源的情况下,虚拟代理的好处就非常明显。

3Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。

4.保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。保护代理的好处是它可以在运行时间对用户的有关权限进行检查,然后在核实后决定将调用传递给被代理的对象。

5Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

6防火墙Firewall)代理:保护目标,不让恶意用户接近。

7.同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。

8.智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。

总结

在软件系统中,增加一个中间层是我们解决问题的常见手法,这方面Proxy模式给了我们很好的实现。

2 远程加载一个图片

我们可以在客户端放一个很不占资源的图片 ,代表客户端,等与服务器端交互好了再替换

,这样不会出现,页面一直处于空白的情况,这种思想非常棒!!!

这是一个需要载入和显示一幅很大的图像的程序,当程序启动时,就必须确定要显示的图像,但是实际的图像只能在完全载入后才可以显示!这时我们就可以使用代理模式(Proxy)



这个代理模式(Proxy)可以延迟实际图像的载入,直到它接收到一个paint请求。在实际图像的载入期间我们可以通过代理模式(Proxy)在实际图像要显示的位置预先载入一个比较小、简单的图形。

图像Proxy代码:

Public Class ImageProxy

Private done As Boolean

Private tm As Timer

Public Sub New()

done = False

'
设置timer 延迟5

tm = New Timer( _

New TimerCallback(AddressOf tCallback), Me, 5000, 0)

End Sub

Public Function isReady() As Boolean

Return done

End Function

Public Function getImage() As Image

Dim img As Imager

'
显示预先的图像,直到实际图像载入完成

If isReady Then

img = New FinalImage()

Else

img = New QuickImage()

End If

Return img.getImage

End Function

Public Sub tCallback(ByVal obj As Object)

done = True

tm.Dispose()

End Sub

End Class

定义一个简单的接口:

Public Interface Imager

Function getImage() As image

End Interface

实现接口:

预先载入的图像的类:

Public Class QuickImage

Implements Imager

Public Function getImage() As Image _

Implements Imager.getImage

Return New bitmap("Box.gif")

End Function

End Class

载入实际图像的类:

Public Class FinalImage

Implements Imager

Public Function getImage() As Image _

Implements Imager.getImage

Return New Bitmap("flowrtree.jpg")

End Function

End Class

在显示图像的窗体中,定义一个图像代理的(Proxy)实例,在载入图像按钮事件中,载入图像:

Private imgProxy As ImageProxy

Public Sub New()

MyBase.New

Form1 = Me

InitializeComponent

imgproxy = New ImageProxy()

End Sub

Protected Sub btLoad_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btLoad.Click

pic.Image = imgProxy.getImage

End Sub

总结:

3 权限Protection (access) proxy:访问保护代理

Proxy模式的类图描述:

[
出自:wikimedia.org]

Subject
被代理的类的接口。
Proxy
代理类。该代理类实现了Subject接口。
RealSubject
代理元,即被代理的目标类。它实现了Subject接口。

Proxy模式的应用范例

下面,我们实现一个Protection (access) proxy
在该范例中,我们模拟了一个现实应用中使用代理控制对数据库表FILE_TBL的操作。当一个用户具有足够的权限时,则可以进行修改删除等操作。否则,打印权限不够的错误信息。
文件一览:
Client
    
测试类
FileTbl
    
与数据库表FILE_TBL相对应的数据类
Permission
    
权限控制
FileTblDao
    
操作数据库表FILE_TBLDAO接口
FileTblDaoImpl
    
操作数据库表FILE_TBLDAO接口的一个标准实现
FileTblDaoProxy
    FileTblDaoImpl
的一个代理类。通过该代理类,控制用户进行不同的操作

1.  public class Client {   

2.      /**  

3.       * for test  

4.       */  

5.      public static void main(String[] args) {   

6.          //只读权限的用户   

7.          Permission searchPermission = new Permission(Permission.PERMISSION.SEARCH);   

8.          FileTblDao searchDao = new FileTblDaoProxy(searchPermission);   

9.          FileTbl fileTbl = searchDao.getFile("file01");   

10.         //只读权限的用户修改文件   

11.         searchDao.updateFile(fileTbl);   

12.            

13.         //全权限的用户   

14.         Permission allPermission = new Permission(Permission.PERMISSION.ALL);   

15.         FileTblDao allDao = new FileTblDaoProxy(allPermission);   

16.         //全权限的用户修改文件   

17.         allDao.updateFile(fileTbl);   

18.     }   

19.   

20. }   

21.   

22.   

23. /**  

24.  * Subject  

25.  * 操作文件表的DAO  

26.  */  

27. interface FileTblDao {   

28.     public void deleteFile(FileTbl fileTbl);   

29.     public void updateFile(FileTbl fileTbl);   

30.     public void saveFile(FileTbl fileTbl);   

31.     public FileTbl getFile(String fileId);   

32. }   

33.   

34. /**  

35.  * RealSubject  

36.  * 操作文件表的DAO的一个标准实现  

37.  */  

38. class FileTblDaoImpl implements FileTblDao {   

39.   

40.     public void deleteFile(FileTbl fileTbl) {   

41.         System.out.println("delete file:" + fileTbl.getId());           

42.     }   

43.   

44.     public void updateFile(FileTbl fileTbl) {   

45.         System.out.println("update file:" + fileTbl.getId());   

46.     }   

47.   

48.     public void saveFile(FileTbl fileTbl) {   

49.         System.out.println("save file:" + fileTbl.getId());   

50.     }   

51.   

52.     public FileTbl getFile(String fileId) {   

53.         return new FileTbl(fileId);   

54.     }   

55.        

56. }   

57.   

58. /**  

59.  * Proxy  

60.  * 操作文件表的DAO代理。该DAO根据用户权限判断是否进行修改,删除文件等操作。  

61.  * 如果有足够的权限,则调用FileTblDaoImpl相关方法操作文件  

62.  *  

63.  */  

64. class FileTblDaoProxy implements FileTblDao {   

65.     FileTblDao fileTblDao;   

66.     Permission permission;   

67.        

68.     public FileTblDaoProxy(Permission permission) {   

69.         if (fileTblDao == null) {   

70.             fileTblDao = new FileTblDaoImpl();   

71.         }   

72.            

73.         this.permission = permission;   

74.     }   

75.        

76.     public void deleteFile(FileTbl fileTbl) {   

77.         if (Permission.PERMISSION.ALL.equals(permission.getLevel())) {   

78.             fileTblDao.deleteFile(fileTbl);   

79.         } else {   

80.             System.out.println("no permission to delete file:" + fileTbl.getId());   

81.         }   

82.     }   

83.   

84.     public void updateFile(FileTbl fileTbl) {   

85.            

86.         if (Permission.PERMISSION.ALL.equals(permission.getLevel())) {   

87.             fileTblDao.updateFile(fileTbl);   

88.         } else {   

89.             System.out.println("no permission to update file:" + fileTbl.getId());   

90.         }   

91.     }   

92.   

93.     public void saveFile(FileTbl fileTbl) {   

94.         if (Permission.PERMISSION.ALL.equals(permission.getLevel())) {   

95.             fileTblDao.saveFile(fileTbl);   

96.         } else {   

97.             System.out.println("no permission to save file:" + fileTbl.getId());   

98.         }   

99.     }   

100.     

101.       public FileTbl getFile(String fileId) {   

102.           return fileTblDao.getFile(fileId);   

103.       }   

104.   }   

105.     

106.     

107.   //文件表类   

108.   class FileTbl {   

109.       private String id;   

110.     

111.       public FileTbl(String id) {   

112.           this.id = id;   

113.       }   

114.       public String getId() {   

115.           return id;   

116.       }   

117.     

118.       public void setId(String id) {   

119.           this.id = id;   

120.       }   

121.          

122.   }   

123.     

124.   //权限控制类   

125.   class Permission {   

126.       public static enum PERMISSION {   

127.           ALL,    

128.           SEARCH   

129.       }   

130.          

131.       private PERMISSION level;   

132.          

133.       public Permission(PERMISSION level) {   

134.           this.level = level;   

135.       }   

136.     

137.       public PERMISSION getLevel() {   

138.           return level;   

139.       }   

140.   }  

 

 

4 论坛 会员 游客 权限问题

 以论坛中已注册用户和游客的权限不同来作为第一个例子:已注册的用户拥有发帖,修改自己的注册信息,修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。为了简化代码,更好的显示出代理模式的骨架,我们这里只实现发帖权限的控制。首先我们先实现一个抽象主题角色MyForum,里面定义了真实主题和代理主题的共同接口——发帖功能。

  代码如下:

public interface MyForum

{

public void AddFile();

}


  这样,真实主题角色和代理主题角色都要实现这个接口。其中真实的主题角色基本就是将这个接口的方法内容填充进来。所以在这里就不再赘述它的实现。我们把主要的精力放到关键的代理主题角色上。代理主题角色代码大体如下:

public class MyForumProxy implements MyForum
{
 private RealMyForum forum ;
 private int permission ; //权限值

 public MyForumProxy(int permission)
 {
  forum = new RealMyForum()
  this.permission = permission ;
 }

 //实现的接口

 public void AddFile()
 {
  //满足权限设置的时候才能够执行操作
  //Constants是一个常量类
  if(Constants.ASSOCIATOR == permission)
  {
   forum.AddFile();
  }
  else
   System.out.println("You are not a associator of MyForum ,please registe!");
 }
}


  这样就实现了代理模式的功能。当然你也可以在这个代理类上添加自己的方法来实现额外的服务,比如统计帖子的浏览次数,记录用户的登录情况等等。

  还有一个很常见的代理模式的使用例子就是对大幅图片浏览的控制。在我们常见的网站上面浏览图文的信息时,不知道你有没有注意到,图片位置放置的是经过缩小的,当有人要仔细的查看这个图片时,可以通过点击图片来激活一个链接,在一个新的网页打开要看的图片。这样对于提高浏览速度是很有好处的,因为不是每个人都要去看仔细图上的信息。这种情况就可以使用代理模式来全面实现。这里我将思路表述出来,至于实现由于工作原因,就不表述了,至于这种方式在B/S模式下的真实可行性,我没有确认过,只是凭空的想象。如果不是可行的方式,那这个例子可以放到一个C/S下来实现,这个是绝对没有问题的,而且在很多介绍设计模式的书和文章中使用。两种方式的实现有兴趣的可以来尝试一下。

  我们在浏览器中访问网页时是调用的不是真实的装载图片的方法,而是在代理对象中的方法,在这个对象中,先使用一个线程向浏览器装载了一个缩小版的图片,而在后台使用另一个线程来调用真实的装载大图片的方法将图片加载到本地,当你要浏览这个图片的时候,将其在新的网页中显示出来。当然如果在你想浏览的时候图片尚未加载成功,可以再启动一个线程来显示提示信息,直到加载成功。

  这样代理模式的功能就在上面体现的淋漓尽致——通过代理来将真实图片的加载放到后台来操作,使其不影响前台的浏览。

  五、总结

  代理模式能够协调调用者和被调用者,能够在一定程度上降低系统的耦合度。不过一定要记住前面讲的使用代理模式的条件,不然的话使用了代理模式不但不会有好的效果,说不定还会出问题的

你可能感兴趣的:(设计模式)