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






4.79/5 (19投票s)
本文介绍了代理模式,何时应该使用代理模式以及使用代理模式的好处。
引言
本文介绍了代理模式,何时应该使用代理模式以及使用代理模式的好处。本文还提供了一个简单粗略的代理模式实现来阐述该模式。
背景
有时,我们需要访问属于不同应用程序、不同域或可能位于世界另一端的对象。如果我们需要访问此类对象或功能,我们也需要处理应用程序之间的通信逻辑。例如,如果我们想与同一台机器上的不同应用程序通信,我们需要 IPC。如果我们想与同一网络上其他机器上的应用程序通信,则需要套接字编程。如果我们想访问位于互联网上的应用程序,那么我们可能需要 SOAP 机制来访问它。
以上场景说明的重点是,除了处理实际功能/数据的代码/逻辑之外,我们还需要放入处理通信逻辑的逻辑。如果我们将这两种逻辑放在同一个对象中,这将导致软件难以维护。此外,它也很容易出错。
代理模式的整个想法是有一个本地模块来模仿真实模块的功能。这样,应用程序就会像只包含真实功能一样处理这个本地模块。所有与远程模块通信的逻辑都可以放在这个本地模块(即代理)中。
GoF 将代理模式定义为“为另一个对象提供代理或占位符,以控制对它的访问”。
为了说明这种设计模式,首先看一下该模式的类图。

让我们逐一了解它们。
Subject
:此类提供了一个接口,实际类和代理类都将实现该接口。这样,代理就可以轻松地用作真实主体的替代品。
-
Proxy
:此类的应用程序将使用,它将公开 Subject 公开的方法。应用程序将使用此类,此类内部将负责与 RealSubject 通信并将数据获取到本地应用程序。
-
RealSubject
:这是包含检索数据/实际功能的真实对象。这是代理在应用程序端代表的类。
我们之前已经看到了一些关于何时需要代理的初步分类。如果我们要根据场景识别代理的类型,那么我们可以有三种类型的代理。
- 远程代理:它们负责表示位于远程的对象。与真实对象通信可能涉及数据的封送和解封以及与远程对象的通信。所有这些逻辑都封装在这些代理中,客户端应用程序无需担心它们。
- 虚拟代理:如果真实对象需要一些时间来产生结果,这些代理将提供一些默认的即时结果。这些代理会启动真实对象上的操作,并向应用程序提供默认结果。一旦真实对象完成,这些代理就会将实际数据推送到客户端,而之前提供的是假数据。
- 保护代理:如果应用程序无权访问某些资源,则此类代理将与拥有该资源访问权限的应用程序中的对象通信,然后将结果返回。
使用代码
现在让我们继续尝试一个简单的例子,看看如何实现这种模式。我们将尝试实现一个简单的应用程序,该应用程序尝试从远程服务器获取商品的最新价格和美元价值。本地应用程序将使用一个 Proxy 类,该类提供简单的方法来获取这些值。代理类将内部与服务器通信并获取值。
此场景的服务器通常是一个返回数据库值的 Web 服务。为了简单起见,我已经将服务器编写为一个返回硬编码值的简单应用程序。客户端和服务器之间的通信是简单的套接字编程。
让我们从查看 Subject
接口开始,该接口为 proxy
和 RealSubject
提供了通用接口。
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日:初稿