代理人模式(Proxy Pattern)

代理人模式(Proxy Pattern)


   1: //饮料机
   2: public class DrinkMachine
   3: {
   4:     public bool state {get;set;}
   5:     int count{get;set;}
   6:     string location;    //位置资讯
   7:  
   8:     public DrinkMachine(string location)
   9:     {
  10:         this.location = location;
  11:     }
  12:  
  13:     public string getLocation()
  14:     {
  15:         return location;
  16:     }
  17:  
  18:     public int getCount()
  19:     {
  20:         return count;
  21:     }
  22:     
  23:     public bool getState()
  24:     {
  25:         return state;
  26:     }
  27:  
  28:     public void saleDrink()
  29:     {
  30:         if (count>0)
  31:         {
  32:             count--;
  33:             Console.WriteLine("谢谢您,饮料售出");
  34:         }else{
  35:             Console.WriteLine("不好意思,饮料售完");
  36:         }
  37:     }
  38:  
  39:     public void Replenishment(int count)
  40:     {
  41:        this.count += count;
  42:         if (this.count>10)
  43:         {
  44:             this.count=10;
  45:         }
  46:         Console.WriteLine(string.Format("补货完毕,目前有{0}罐",this.count));
  47:     }
  48: }


以上是一台饮料机的简单实践,里面有简单的库存机制、贩卖机制以及相关的属性。

现在饮料公司为了准备报表,想要建立饮料机的监控类,透过一个叫作DrinkMachineMonitor

只要传入放在各地的饮料机,就可以来快速的取得各个饮料机的位置资讯,并打印出报告。

这样的需求我们可以马上进行开发

   1: public class DrinkMachineReporter
   2: {
   3:     DrinkMachine machine;
   4:  
   5:     public DrinkMachineReporter(DrinkMachine drinkmachine)
   6:     {
   7:         this.machine = drinkmachine;
   8:     }
   9:  
  10:     public void Report(){
  11:         Console.WriteLine("饮料机的位置:"+machine.getLocation());
  12:         Console.WriteLine("饮料机的状态:"+machine.getState());
  13:         Console.WriteLine("饮料机的库存:"+machine.getCount());
  14:     }
  15:  
  16: }


头脑一动,我们马上就可以写出类似上面的程序。

简单测试一下就可以运行这个简单的监控程序

image

上述,我们透过了监视器的开发建立了一个糖果机的另一个View

简而言之,只是建立了另一个画面,让使用者看到

但这样出现了什么问题呢?我们假设每个对象都是一台真实的饮料机的话…

那我们这个监视器不是必须将所有的饮料机搬回公司,才能够透过“本地”端的饮料机

进行资讯的取得,并取得报表的资讯吗?

其实…更进一步的需求就是这样:透过类似网络的环境,能够将远在别处的饮料机的资讯

透过服务的方式来取得!

第一个关键字是“远端”、第二个关键字是“代理人”

所谓远端就是,我们希望在办公室远端监控这些机器,所以我们可以不要更动原先的Monitor程序

但是不要将饮料机传入Monitor,而是将饮料机的代理人交给Monitor程序

而代理人是指(Proxy)某个真实的对象,就像是饮料机一样,但是它不是饮料机,幕后是利用网络

和一个远端的饮料机沟通。

整体来说,在软件架构中,远端代理人就是“远端对象的本地代表”

远端对象是一种活在内存的Heap(堆中)的对象

而本地代表是在本地端调用的对象,其调用的行为会被转到远端对象中执行。

客户端的程序会以为他沟通的对象对象是真正在远端的对象

但其实它只有接触到代理人,再由代理人透过网络和真正的对象沟通。

代理人可以假装自己是远端的对象,但其实只是一个中间角色。

在这边,远端的对象是真正重要的角色,它才是真正的做事者。

客户对象运行起来就像是在调用远端,但其实是调用本地Heap区中的“代理人”对象,再交由代理人处理网络沟通的低阶细节。

首先先让饮料机准备好当一个远端的服务吧:

1.为饮料机建立一个远端界面,界面内包括了一组可以远端调用的方法。

2.在具象类中实践此界面。

我们先来定义一个远端界面,好供饮料机来提供服务,而且让监视器,可以取得服务的共同界面

(这边要注意,因为要分为远端与本地端,因此,这个共同的服务界面,我们要另外开一个项目出来)

简单而言如下:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace com.DrinkCompany
   7: {
   8:     //定义远端服务接口的界面,
   9:     public interface DrinkMachineRemote
  10:     {
  11:         string getLocation();
  12:         int getCount();
  13:         bool getState();
  14:     }
  15: }


从我们报表为例,我们需要这三个服务,因此我们先定义出来。并建置成一个dll文件

(以下实践采用的是.NET的Remoting架构,在Java中也可以Import RMI来进行实践,目的一致)

接着我们先由远端的饮料机来实现这个服务界面吧(其实我们饮料机早就有了,我们等于是为饮料机设计服务界面)

因此直接在饮料机的程序中实践即可,完全不需要改程序:(为了引入共同的界面定义,因此要参考先前建置完dll文件)

然后因为同一家公司,自然使用相同的Namespace,另外,为了实践底下的网络层我们必须让这个饮料机实践MarshalByRefObject对象

详细MarshalByRefObject的原理可以看MSDN的介绍(点我)

   1: using com.DrinkCompany;    //定义的共同远端服务界面
   2:  
   3: namespace com.DrinkCompany
   4: {
   5:     //饮料机
   6:     public class DrinkMachine : MarshalByRefObject, DrinkMachineRemote
   7:     {
   8:         public bool state { get; set; }
   9:         int count { get; set; }
  10:         string location;    //位置资讯
  11:  
  12:         public DrinkMachine(string location)
  13:         {
  14:             this.location = location;
  15:         }
  16:  
  17:         public DrinkMachine()
  18:         {
  19:             this.count = 3;
  20:             this.location = "Taipei";
  21:            this.state = true;
  22:         }
  23:  
  24:         public string getLocation()
  25:         {
  26:             return location;
  27:         }
  28:  
  29:         public int getCount()
  30:         {
  31:             return count;
  32:         }
  33:  
  34:         public bool getState()
  35:         {
  36:             return state;
  37:         }
  38:  
  39:         public void saleDrink()
  40:         {
  41:             if (count > 0)
  42:             {
  43:                 count--;
  44:                 Console.WriteLine("谢谢您,饮料售出");
  45:             }
  46:             else
  47:             {
  48:                 Console.WriteLine("不好意思,饮料售完");
  49:             }
  50:         }
  51:  
  52:         public void Replenishment(int count)
  53:         {
  54:             this.count += count;
  55:             if (this.count > 10)
  56:             {
  57:                 this.count = 10;
  58:             }
  59:             Console.WriteLine(string.Format("补货完毕,目前有{0}罐", this.count));
  60:         }
  61:     }


接着,我们实践完饮料机的服务了(其实没有实践,只是拿来直接用而己)

为了让这个饮料机能够直接提供服务,因此我们必须定义出服务的底层模式…其实方法很多重,也可以列很多篇幅来讨论

不过这边我们直接实践TCP的协定,在Server端饮料机的这边实践以下程序:

Server端

   1: class Program
   2: {
   3:  static void Main(string[] args)
   4:  {
   5:          TcpChannel channel = new TcpChannel(9876);    //定义Port
   6:          ChannelServices.RegisterChannel(channel,false);  //注册通道(与Java的RMI同原理)
   7:  
   8:          //服务类的注册
   9:          RemotingConfiguration.RegisterWellKnownServiceType(
  10:                      typeof(DrinkMachine), "remotePin", WellKnownObjectMode.SingleCall);
  11:  
  12:          System.Console.WriteLine("The Server is ready .... Press the enter key to exit...");
  13:          System.Console.ReadLine();
  14:  }
  15: }


其中,对于服务注册的含义各位可以参考这篇 点我 (像是这个可以定义WellKnowOjbectMode,可以定义SingleCall或是Singleton)

这关系到提供的服务是否为同一个对象,不过这次的分享主轴在代理人模式,所以我们先不讨论网络层的细节。

OK了,我们接着执行这个饮料机服务

image

然后等着我们的监控程序来调用服务了,现在我们不需要依赖具体类了

这我们直接依赖共用的服务界面,因此也要将先前建置的dll加入到客户监控程序的项目档

   1: public class DrinkMachineReporter
   2:         {
   3:             //换成依赖远端界面,而不是直接使用具象的DrinkMachine类
   4:             public DrinkMachineRemote machine;
   5:  
   6:             public DrinkMachineReporter(DrinkMachineRemote drinkmachine)
   7:             {
   8:                 this.machine = drinkmachine;
   9:             }
  10:  
  11:             public void Report(){
  12:                 try
  13:                 {
  14:                     Console.WriteLine("饮料机的位置:" + machine.getLocation());
  15:                     Console.WriteLine("饮料机的状态:" + machine.getState());
  16:                     Console.WriteLine("饮料机的库存:" + machine.getCount());
  17:                 }
  18:                 catch (Exception ex)
  19:                 {
  20:                     throw;
  21:                 }
  22:             }
  23:         }


以上可以看到先前的具体饮料机都被换成了服务界面,但是我们报表的程序仍然完全不用改

直接就像操作饮料机拿到资讯一样似的。(以下实践调用服务网络底层,有兴趣可以看先前.NET的RMI实践那篇)

主要与服务相关的就是他们要实践共同的界面(In dll中),然后第12行调用的协定(TCP)、通道(9876)与URL位置(remotePin)都是在Server端程序定义好的。

Client端

   1:  
   2: /// 
   3: /// Entry point into console application.
   4: /// 
   5: static void Main(string[] args)
   6: {
   7: TcpChannel channel = new TcpChannel();
   8: ChannelServices.RegisterChannel(channel,false);
   9:  
  10: // Create an instance of the remote object
  11: DrinkMachineRemote obj = (DrinkMachineRemote)Activator.GetObject(
  12:          typeof(DrinkMachineRemote), "tcp://localhost:9876/remotePin");
  13: // localhost  OR your server name
  14: if (obj.Equals(null))
  15: {
  16:     System.Console.WriteLine("Error: unable to locate server");
  17: }
  18: else
  19: {
  20:     String strArgs;
  21:     if (args.Length == 0)
  22:     {
  23:         strArgs = "Client";
  24:     }
  25:     else
  26:     {
  27:         strArgs = args[0];
  28:     }
  29:     DrinkMachineReporter tReporter = new DrinkMachineReporter(obj);
  30:     tReporter.Report();
  31: }       


因为透过界面来调用,因此本地端的DrinkMachineReporter 可以直接将远端的服务对象,传入监控程序使用。

所以调用了Repoter函数后,可以得到以下结果。

image

来定义代理人模式吧,在Head First Design Pattern中定义为:

让某个对象具有一个替身,以控制外界对此对象的接触。

类图如下:

image

实例:

远端饮料机(RealSubject)、本地饮料机监控(Proxy)、饮料机服务界面(Subject)

我们具备有一个  饮料机服务界面  的界面,供  远端饮料机  与  本地饮料机监控  共用

好让 本地饮料机监控 可以取代远端饮料机的功能(取得资讯),而不用大老远跑到饮料机所在地

远端饮料机 是真正做事的对象,它是被 本地饮料机监控 代理的对象,被控制存取的对象。

在某些案例中,本地饮料机监控还要负责 远端饮料机 的建立与消毁。客户必须透过Proxy才能与RealSubject交互

也就无形的控制了存取权限。因此被代理的对象可以是远端的对象、建立成本高的对象或者是需要安控的对象…

上述就是一般代理人(远端)的模式

代理人模式还有许多的变型:虚拟代理人、保护代理人、动态代理人、防火墙代理人、同步化代理人、备用代理人…etc

但这边不一一分享,未来若有机会再分享呗(下一次,代理人模式的变型:虚拟代理人)

参考数据:

Head First Design Pattern Book

.Net Remoting http://www.iiiedu.org.tw/knowledge/knowledge20030430_2.htm

Migrating Java RMI to .NET Remoting http://www.c-sharpcorner.com/UploadFile/pk_khuman/RMIto.NETRemoting01162006052210AM/RMIto.NETRemoting.aspx

MSDN