65.9K
CodeProject 正在变化。 阅读更多。
Home

理解和实现 C# 中的代理模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (19投票s)

2012 年 11 月 12 日

CPOL

5分钟阅读

viewsIcon

135566

downloadIcon

2851

本文介绍了代理模式,何时应该使用代理模式以及使用代理模式的好处。

引言

本文介绍了代理模式,何时应该使用代理模式以及使用代理模式的好处。本文还提供了一个简单粗略的代理模式实现来阐述该模式。

背景  

有时,我们需要访问属于不同应用程序、不同域或可能位于世界另一端的对象。

如果我们需要访问此类对象或功能,我们也需要处理应用程序之间的通信逻辑。例如,如果我们想与同一台机器上的不同应用程序通信,我们需要 IPC。如果我们想与同一网络上其他机器上的应用程序通信,则需要套接字编程。如果我们想访问位于互联网上的应用程序,那么我们可能需要 SOAP 机制来访问它。

以上场景说明的重点是,除了处理实际功能/数据的代码/逻辑之外,我们还需要放入处理通信逻辑的逻辑。如果我们将这两种逻辑放在同一个对象中,这将导致软件难以维护。此外,它也很容易出错。

代理模式的整个想法是有一个本地模块来模仿真实模块的功能。这样,应用程序就会像只包含真实功能一样处理这个本地模块。所有与远程模块通信的逻辑都可以放在这个本地模块(即代理)中。

GoF 将代理模式定义为“为另一个对象提供代理或占位符,以控制对它的访问”。

为了说明这种设计模式,首先看一下该模式的类图。

 

让我们逐一了解它们。

  • Subject:此类提供了一个接口,实际类和代理类都将实现该接口。这样,代理就可以轻松地用作真实主体的替代品。
  • Proxy:此类的应用程序将使用,它将公开 Subject 公开的方法。应用程序将使用此类,此类内部将负责与 RealSubject 通信并将数据获取到本地应用程序。
  • RealSubject:这是包含检索数据/实际功能的真实对象。这是代理在应用程序端代表的类。

我们之前已经看到了一些关于何时需要代理的初步分类。如果我们要根据场景识别代理的类型,那么我们可以有三种类型的代理。

  • 远程代理:它们负责表示位于远程的对象。与真实对象通信可能涉及数据的封送和解封以及与远程对象的通信。所有这些逻辑都封装在这些代理中,客户端应用程序无需担心它们。
  • 虚拟代理:如果真实对象需要一些时间来产生结果,这些代理将提供一些默认的即时结果。这些代理会启动真实对象上的操作,并向应用程序提供默认结果。一旦真实对象完成,这些代理就会将实际数据推送到客户端,而之前提供的是假数据。
  • 保护代理:如果应用程序无权访问某些资源,则此类代理将与拥有该资源访问权限的应用程序中的对象通信,然后将结果返回。

使用代码

现在让我们继续尝试一个简单的例子,看看如何实现这种模式。我们将尝试实现一个简单的应用程序,该应用程序尝试从远程服务器获取商品的最新价格和美元价值。本地应用程序将使用一个 Proxy 类,该类提供简单的方法来获取这些值。代理类将内部与服务器通信并获取值。

此场景的服务器通常是一个返回数据库值的 Web 服务。为了简单起见,我已经将服务器编写为一个返回硬编码值的简单应用程序。客户端和服务器之间的通信是简单的套接字编程。

让我们从查看 Subject 接口开始,该接口为 proxyRealSubject 提供了通用接口。

interface IActualPrices
{
    string GoldPrice
    {
        get;
    }
 
    string SilverPrice
    {
        get;
    }
 
    string DollarToRupee
    {
        get;
    }
}

现在我们已经准备好了 Subject 类,让我们看看我们的 RealSubject 的实现。

class ActualPrices : IActualPrices
{
    public string GoldPrice
    {
        get
        {
            // This value should come from a DB typically
            return "100";
        }
    }
 
    public string SilverPrice
    {
        get
        {
            // This value should come from a DB typically
            return "5";
        }
    }
 
    public string DollarToRupee
    {
        get
        {
            // This value should come from a DB typically
            return "50";
        }
    }
}

这些包含在服务器应用程序中。服务器应用程序的通信逻辑写在服务器应用程序的 Main 函数中,但这也可以嵌入到一个单独的骨架类中以提供更多的模块化。现在,我们假设 Main 函数就是骨架类的一部分。

static void Main(string[] args)
{
    IPAddress ip = IPAddress.Parse("127.0.0.1");
    TcpListener listener = new TcpListener(ip, 9999);
 
    while (true)
    {
        listener.Start();
        Console.WriteLine("Waiting .....");
        Socket s = listener.AcceptSocket();
 
        byte[] b = new byte[100];
 
        int count = s.Receive(b);
 
        string input = string.Empty;
 
        for (int i = 0; i < count; i++)
        {
            input += Convert.ToChar(b[i]);
        }
 
        IActualPrices realSubject = new ActualPrices();
        string returnValue = string.Empty;
 
        switch (input)
        {
            case "g":
                returnValue = realSubject.GoldPrice;
                break;
            case "s":
                returnValue = realSubject.SilverPrice;
                break;
            case "d":
                returnValue = realSubject.DollarToRupee;
                break;
        }
 
        ASCIIEncoding asen = new ASCIIEncoding();
        s.Send(asen.GetBytes(returnValue));
 
        s.Close();
        listener.Stop();
        Console.WriteLine("Response Sent .....");
    }
}

现在我们的应用程序的服务器端部分已经准备好了。现在让我们看看将与 RealSubject 通信的 Proxy 类。

class ActualPricesProxy : IActualPrices
{
    public string GoldPrice
    {
        get
        {
            return GetResponseFromServer("g");
        }
    }
 
    public string SilverPrice
    {
        get
        {
            return GetResponseFromServer("s");
        }
    }
 
    public string DollarToRupee
    {
        get
        {
            return GetResponseFromServer("d");
        }
    }
 
    private string GetResponseFromServer(string input)
    {
        string result = string.Empty;
        using (TcpClient client = new TcpClient())
        {
            client.Connect("127.0.0.1", 9999);
 
            Stream stream = client.GetStream();
 
            ASCIIEncoding asen = new ASCIIEncoding();
            byte[] ba = asen.GetBytes(input.ToCharArray());
 
            stream.Write(ba, 0, ba.Length);
 
            byte[] br = new byte[100];
            int k = stream.Read(br, 0, 100);
 
            
 
            for (int i = 0; i < k; i++)
            {
                result += Convert.ToChar(br[i]);
            }
 
            client.Close();
        }
        return result;
    }
}

这里需要注意的重要一点是,Proxy 暴露了与真实主题相同的接口,并在其中封装了通信、封送和解封的细节。这使得客户端代码更加简洁和简单,就像它在使用真实应用程序一样。

所以,如果我们看一下客户端应用程序代码:

static void Main(string[] args)
{
    IActualPrices proxy = new ActualPricesProxy();
 
    Console.WriteLine("Gold Price: ");
    Console.WriteLine(proxy.GoldPrice);
 
    Console.WriteLine("Silver Price: ");
    Console.WriteLine(proxy.SilverPrice);
 
    Console.WriteLine("Dollar to Ruppe Conversion: ");
    Console.WriteLine(proxy.DollarToRupee);
}

现在,如果我们运行 Client 应用程序(假设服务器应用程序已经运行),我们可以看到输出是

 

因此,我们可以看到 Proxy 类从位于另一个应用程序中的 RealSubject 获取结果,而 client 应用程序对该应用程序存在于其他地方的事实一无所知。在总结之前,让我们看一下我们应用程序的类图,并将其与 GoF 图进行比较。


关注点

在本文中,我们尝试了解了代理模式是什么,它何时有用。我们还看到了 C# 中代理模式的一个粗略实现。这种模式有时看起来与装饰器和适配器模式非常相似,但并非如此。装饰器通过包装对象来添加额外功能,适配器为对象提供已更改的接口,而代理模式提供与原始对象相同的接口,但对其进行包装以隐藏通信和封送/解封的细节。本文是从初学者的角度撰写的。希望有所启发。

历史

  • 2012年11月12日:初稿
© . All rights reserved.