什么是代理设计模式?
根据 Gang of four 的定义,代理设计模式为另一个对象提供了一个代理(代表其他行动人)或占位符来控制对它的访问。 代理是指“代替”或“代表”。
在最简单的形式中,我们可以将代理定义为一个类,作为其他东西的接口。 代理可以连接到任何东西,例如网络连接、内存中的大型对象、文件或其他一些昂贵或无法复制的资源。
我们也可以说代理(Proxy)是客户端(Client)调用的对象,用于访问幕后的真实对象。 这意味着,在代理设计模式中,一个类代表另一个类的功能。
通过示例了解 C# 中的代理设计模式:
请查看下图以更好地理解 C# 中的代理设计模式。 正如您在下图中看到的,当客户端想要使用真实对象的某些方法时,他/她需要通过代理对象。 这意味着客户端将调用代理对象的方法,而代理将负责调用真实对象的方法。
代理的类型:
有三种类型的代理。 它们如下。
- 虚拟代理:虚拟代理是“昂贵创建”对象的占位符。 真正的对象仅在客户端首次请求或访问该对象时创建。
- 远程代理:远程代理为驻留在不同地址空间中的对象提供本地表示。
- 保护代理:保护代理控制对敏感对象的访问。 代理对象在转发请求之前检查调用者是否具有所需的访问权限。
代理设计模式真实示例:
请看下图。 在右侧,您可以看到银行,在左侧可以看到一个叫 Anurag 的人。 Anurag 在银行有一个账户。 在早些时候,比如说 1960 年,Anurag 想从他的账户中取款。 那么他要做的就是,他必须带着他的存折去银行。 然后他必须填写表格并需要排队。 轮到他时,他必须将表格和银行存折交给银行员工,然后银行员工验证表格和他的存折,如果一切正常,则银行员工将所需的钱交给 Anurag。
假设 Anurag 现在想取钱。 因此,他现在能做的不是去银行,而是拿着银行卡到最近的 ATM。 然后他插入他的银行卡并输入密码和提款金额。 然后 ATM 将与银行通信并验证密码和金额,如果一切正常,ATM 将把钱交给 Anurag。 Anurag 无需去银行,直接从 ATM 取款。 因此,这里的银行是真实对象,ATM 是代理。 我认为这是代理设计模式的最佳真实示例。
为什么我们需要 C# 中的代理设计模式?
让我们以代理服务器为例来了解代理设计模式的必要性。
位于客户端应用程序(例如 Web 浏览器)和真实服务器之间的服务器称为代理服务器。 该代理服务器拦截所有传入的真实服务器请求,以查看它是否可以自行完成请求。 如果不是,那么它会将请求转发到真实服务器。
代理服务器有两个主要目标。 它们如下:
提高性能:
代理服务器可以极大地提高应用程序的性能。 这是因为它将请求的结果保存了一段时间。 例如,假设我们有两个用户 X 和 Y,他们想通过代理服务器访问特定资源。 首先,用户 X 请求一个特定的资源(比方说一个员工列表)并将该资源缓存一段时间。 稍后,用户 Y 也请求相同的资源,代理服务器不再将该请求转发给实际服务器(这是一项耗时操作),只需从缓存中返回数据即可。 由于客户端和代理服务器在同一个网络中,因此操作速度会快得多。
过滤请求:
代理服务器也可用于过滤传入的请求。 例如,一家公司可能会使用代理服务器来阻止其员工访问一组特定的网站,如某宝、拼某多等。
C#中代理设计模式的实现(保护代理):
业务要求:
请看下图。 正如您在下图中看到的,在右侧我们有一台共享文件夹的共享计算机。 在左侧,我们有在软件农场工作的员工。 共享计算机包含一个包含机密信息的共享文件夹,只有具有经理和首席执行官角色的员工才能访问此共享文件夹并执行读写操作。 另一方面,如果员工是开发人员,则不应允许访问共享文件夹。 那就是我们需要做某种保护。 在这种情况下,保护代理可以派上用场。
我们在这里可以做的是,在员工和共享计算机之间,我们需要引入文件夹代理。 这个文件夹代理可以做的是,它会检查员工的角色是经理还是首席执行官,然后允许员工访问共享文件夹并执行读写操作。 另一方面,如果员工角色是 Developer 那么它会说你没有权限访问这个文件夹。 保护逻辑我们可以写在文件夹代理中。
现在,我希望您了解代理设计模式。 那么,让我们一步步在C#中实现代理设计模式吧。
第一步:创建员工类
创建一个名为 Employee.cs 的类文件,然后将以下代码复制并粘贴到其中。
Step2:创建主体
创建一个名为 ISharedFolder 的接口,然后将以下代码复制并粘贴到其中。 该接口定义了将由 RealSubject 和 Proxy 类实现的常用方法。
第三步:创建真实对象
创建一个名为 SharedFolder.cs 的类文件,然后将以下代码复制并粘贴到其中。 此类实现主体 (ISharedFolder) 接口。
第四步:创建代理对象
创建一个名为 SharedFolderProxy.cs 的类文件,然后将以下代码复制并粘贴到其中。 此类还实现了 Subject (ISharedFolder) 接口,并且它还持有对真实对象的引用。
Step5:客户端代码
请修改 Main 方法,如下所示。
了解代理设计模式类图:
为了理解 C# 中代理设计模式的类图,请看下图。
如上图所示,代理设计模式涉及三个参与者。 它们如下:
- 主体(ISharedFolder): 这是一个定义将由 RealSubject 和 Proxy 类实现的成员的接口,以便可以在任何需要 RealSubject 的地方使用 Proxy。 在我们的示例中,它是 ISharedFolder 接口。
- 真实对象(SharedFolder): 这是一个我们希望通过使用代理类来更高效地使用的类。 在我们的示例中,它是 SharedFolder 类。
- 代理(SharedFolderProxy): 这是一个持有对 RealSubject 类的引用的类,可以根据需要访问 RealSubjectr 类成员。 它必须实现与 RealSubject 相同的接口,以便两者可以互换使用。 在我们的示例中,它是 SharedFolderProxy 类。
何时在 C# 实时应用程序中使用代理设计模式?
以下是您可以在 C# 实时应用程序中使用代理设计模式的一些实时场景。
- 添加对现有对象的安全访问。 代理将确定客户端是否可以访问感兴趣的对象。
- 简化复杂对象的 API。 代理可以提供一个简单的 API,这样客户端代码就不必处理感兴趣对象的复杂性。
- 为 Web 服务或 REST 资源等远程资源提供接口。
- 通过要求远程资源在访问资源之前尽快开始操作来协调对远程资源的昂贵操作。
- 在不更改现有类代码的情况下向现有类添加线程安全功能。
C# 中的代理设计模式实时示例 – 虚拟代理
虚拟代理是创建成本高昂的对象的占位符。 真正的对象只有在客户第一次请求或访问一个对象时才被创建。 让我们通过一个实时示例来理解这一点。 请看下面的图片。 在右侧,您可以看到系统A,它有一个 200 MB 的图像(老虎图像)。 在左侧,您可以看到客户端。 在客户端和系统A 之间,有充当虚拟代理的系统B。
比方说,客户端第一次向系统 B(虚拟代理)发送请求以显示老虎图像。 虚拟代理(即系统 B)要做的是,首先它会检查虚拟代理中是否存在真实图像对象。 如果真实图像对象不存在,那么在第 1 步中它将创建真实图像对象并从磁盘加载图像,在第 2 步中它将调用真实图像对象上的显示图像方法。 虚拟代理还保存在步骤 1 中创建的真实图像对象。 第 1 步,即创建真实图像对象并从磁盘加载图像是一项昂贵的操作。
假设客户端第二次向虚拟代理发出相同的请求以显示老虎图像。 现在,虚拟代理要做的是,检查实像对象是否存在,它发现虚拟代理中存在实像对象(这是因为在第一个请求中,虚拟代理持有真实- 图像对象)。 所以,虚拟代理要做的是,它不会执行第 1 步,即它不会创建真实图像对象并从磁盘加载图像。 相反,它将使用现有的真实图像对象并调用 Display Image 方法,即 step2。 所以,通过这种方式,使用代理设计模式,我们可以避免一次又一次地创建一个昂贵的对象。
类图:
请看下图:
C#中代理设计模式实时实例的实现:
让我们使用代理设计模式逐步实现上述实时示例。 众所周知,代理设计模式涉及三个组件,例如主题、真实对象和代理对象。 让我们一一实现上面的组件。
第一步:创建主体
这将是一个接口。 因此,创建一个名为 IImage 的接口,然后将以下代码复制并粘贴到其中。 该接口提供将由真实对象和代理对象具体类实现的功能。 在我们的示例中,接口定义了一种方法,即 DisplayImage
步骤2:创建真实对象
这将是一个具体类,此类实现 IImage 接口并提供 DisplayImage 方法的实现。 因此,创建一个名为 RealImage.cs 的类文件,然后将以下代码复制并粘贴到其中。 RealImage 类的这个构造函数将文件名作为参数,然后从磁盘加载文件。
注意:这里的对象创建过程中有一个昂贵的操作。 这是因为在创建对象时,它将从磁盘加载图像。 LoadImageFromDisk 方法用于从磁盘加载图像。 DisplayImage 方法只是用来显示图像。
第三步:创建代理
这将是一个具体类,它还实现 IImage 接口并提供 DisplayImage 方法的实现。 因此,创建一个名为 ProxyObject.cs 的类文件,然后将以下代码复制并粘贴到其中。 作为 DisplayImage 方法的一部分,首先,我们检查 realImage 实例是否为 null。 如果为 null,则我们将创建实例,然后在 realImage 实例上调用 DisplayImage 方法。 另一方面,如果 realImage 实例不为 null,则它不会创建该实例,而是使用现有的 realImage 实例来调用 DisplayImage 方法。
第四步:客户端
请修改 Main 方法,如下所示。 首先,我们创建ProxyImage对象来显示老虎图像,然后调用3次DisplayImage方法。 在这种情况下,第一次调用 DisplayImage 方法将创建 RealImage 实例,因此它将从磁盘加载图像。 但是从第二次调用 DisplayImage 方法开始,它将使用现有的 RealImage 实例,因此不会从磁盘加载图像。 这个过程对于第二次创建代理对象以显示狮子图像也是相同的。