代理设计模式






4.60/5 (5投票s)
引言
本系列文章将尝试演示所有设计模式的用法。本文从“代理”开始。本系列文章不会遵循 GoF 的设计模式顺序。首先,我将尝试根据 http://www.dofactory.com/ 发布的那些被认为更常用的设计模式。
背景
书籍和大量的在线文章是解释设计模式的绝佳来源。然而,我的灵感来自于几个月前,我的一个同事提出了一个好观点。他指出,尽管他经常阅读很多关于设计模式的内容,但他仍然无法真正掌握如何在我们需要实现 IoC 容器和单元测试的业务代码库中实现它们。因此,本系列的目的是,不是解释模式(因为已经有很多书籍和文章可供参考),而是演示设计模式的用法,以及大多数开发人员在其工作环境中使用的工具。
本系列将从代理设计模式开始,以下内容基于 C# 3.0 设计模式一书中的 Spacebook 示例。我尝试重构了书中的代码,并使用 Castle.Windsor 和 NUnit 框架展示了一个替代版本。
《C# 3.0 设计模式》一书中 Spacebook 示例的作者使用了一个私有类作为主题类,因为他试图强调代理的以下特性:“客户端无法直接访问主题类。对主题类的访问是通过代理实现的”。
对 C# 3.0 设计模式一书中代理的简要分析
模式中的参与者包括:
ISubject
主题和代理的通用接口,使它们能够被
互换使用
主题
代理所代表的类
Proxy
一个创建、控制、增强和验证主题访问的类
请求
通过代理路由的主题上的操作
中心类 Proxy,实现了 ISubject 接口。客户端可以使用
ISubject 来抽象代理。
每个 Proxy 对象都维护着对 Subject 的引用,而 Subject 才是真正执行操作的地方。
Proxy 执行前端工作。通过将 Subject 设为私有类,可以增强其价值,
这样客户端除了通过 Proxy 之外无法访问 Subject。
重构版 Spacebook 文章的关键特性是:
- 通过将 Subject 类设为 internal 来将其对客户端隐藏。
- Subject 类继承 ISpaceBook
- 内部类可以进行单元测试
- 使用 Castle.Windsor 进行依赖注入
- Subject 和 Proxy 类都可以进行单元测试
使用代码
解决方案由以下项目构成:
- 客户端项目(将调用 Proxy 类,而不是 Subject 类)
- ProxyCastle 项目(Subject 和 Proxy 类)
- ProxyCastle.UnitTests
- ProxyCastle.Dependencies
第一步是创建 ISpaceBook 接口
public interface ISpaceBook { bool IsUnique(string name); void Add(string n); void Add(string friend, string message); void Poke(string who, string friend); string SetName { set; } }
然后,我们创建实现了 ISpaceBook 接口的内部类 SpaceBook。如前所述,围绕代理设计模式的一个特性是,客户端不应该能够访问 Subject 类。这正是拥有代理类的意义所在,以便通过代理类实现访问。书中的 Spacebook 示例通过将其设为 **Private** 来隐藏 Subject 类。我试图通过将其设为 **Internal** 来达到相同的目的。
internal class SpaceBook :ISpaceBook { internal static SortedList<string, SpaceBook> community = new SortedList<string, SpaceBook>(100); private string pages; private string name; private string gap = "\n\t\t\t\t"; public string SetName { set { name = value; community[name] = this; } } public bool IsUnique(string name) { return community.ContainsKey(name); } public virtual void Add(string s) { pages += gap + s; Console.Write(gap + "======== " + name + "'s SpaceBook ========="); Console.Write(pages); Console.WriteLine(gap + "==================================="); } public virtual void Add(string friend, string message) { community[friend].Add(message); } public void Poke(string who, string friend) { community[who].pages += gap + friend + " poked you"; } }
在 AssemplyInfo.cs 中,我们需要添加以下内容,以便能够让 internal 模块“可见”,我们只希望它在特定的项目中可见。
[assembly: InternalsVisibleTo("ProxyCastle.UnitTests",AllInternalsVisible = true)] [assembly: InternalsVisibleTo("ProxyCastle.Dependencies", AllInternalsVisible = true)] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
然后我们就可以创建 Proxy 类了:
public class SpaceBookProxy { private ISpaceBook _spaceBook; // Combination of a virtual and authentication proxy private string password; private string name; public bool loggedIn = false; public SpaceBookProxy(ISpaceBook spacebook) { if (spacebook == null) throw new ArgumentNullException("spaceBook"); if (_spaceBook == null) _spaceBook = spacebook; } private void Register() { Console.WriteLine("Let's register you for SpaceBook"); do { Console.WriteLine("All SpaceBook names must be unique"); Console.Write("Type in a user name: "); name = Console.ReadLine(); } while (_spaceBook.IsUnique(name)); Console.Write("Type in a password: "); password = Console.ReadLine(); Console.WriteLine("Thanks for registering with SpaceBook"); } private bool Authenticate() { Console.Write("Welcome " + name + ". Please type in your password: "); string supplied = Console.ReadLine(); if (supplied == password) { loggedIn = true; Console.WriteLine("Logged into SpaceBook"); _spaceBook.SetName = name; return true; } Console.WriteLine("Incorrect password"); return false; } public void Add(string message) { Check(); if (loggedIn) _spaceBook.Add(message); } public void Add(string friend, string message) { Check(); if (loggedIn) _spaceBook.Add(friend, name + " said: " + message); } public void Poke(string who) { Check(); if (loggedIn) _spaceBook.Poke(who, name); } private void Check() { if (!loggedIn) { if (password == null) Register(); Authenticate(); } } public bool IsUnique(string name) { return _spaceBook.IsUnique(name); } public void Poke(string who, string friend) { _spaceBook.Poke(who, friend); } }
此时,我们在 Subject 类级别引入了接口,以便 Proxy 可以通过接口使用依赖注入来引用 Subject。客户端应用程序将调用 Proxy 类,而无需知道 Subject 类的存在(SpaceBook)。
namespace Client { class Program { public static void Main() { var mySpaceBook = new SpaceBookProxy(DependencyResolver.Disolve()); mySpaceBook.Add("Hello world"); mySpaceBook.Add("Today I worked 18 hours"); var tom = new SpaceBookProxy(DependencyResolver.Disolve()); tom.Poke("Veronica"); tom.Add("Veronica", "Poor you"); tom.Add("Off to see the Lion King tonight"); } } }
最后一步是添加 ProxyCastle.Dependencies 项目。
我们可以将这个责任委托给客户端,但那样的话,我们就需要将 Subject 类在客户端中可用。如果我们想在这个级别解决依赖关系,客户端就需要“了解”Subject类。
为了解决这个问题,我们可以将这个责任委托给另一个项目。这就是我们添加 ProxyCastle.Dependencies 的地方。这个项目需要了解 Subject 类,所以我们选择在这个项目上解决依赖关系。通过这种方式,客户端可以通过代理来引用 Subject 类,从而保持我们想要的结构,因此,它不需要了解 SpaceBook 类。
namespace ProxyCastle.Dependencies { public class DependencyResolver { private static WindsorContainer _container; public static ISpaceBook Resolve() { _container = new WindsorContainer(); _container.Register(Component.For<ISpaceBook>().ImplementedBy<SpaceBook>().LifeStyle.Transient); return _container.Resolve<ISpaceBook>(); } } }
我已包含本篇文章的解决方案。如果有什么可以改进的地方,请告诉我,我将相应更新。
关注点
内部类和单元测试
使用 Castle.Windsor 进行依赖注入
历史
在此处保持您所做的任何更改或改进的实时更新。