温故知新(9)——访问者模式
概述
访问者模式不是一个很常用的模式,首先来看GOF《设计模式》给出的意图描述。
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
由这段意图描述,可以看出访问者模式的主要用途就是现有的类附加职责,而且这些现有的类处于一个继承树中。我们定义这样的术语:“现有的类”称为“被访问类”,包含附加行为的类称为“访问者”。
使用访问者模式要求,被访问类比较稳定,而需要扩展的行为比较易变,这样通过模式将扩展行为分离出去,才可以获得比好的效果;相反如果被访问类易变,那么当被访问类增减时,都要对与之有关的所有访问者类进行修改,因此此时使用此模式不是很合适,详见结构部分。
此模式可能的应用场景是:
1、用较少的改动,为遗留的系统增加新的功能。
2、扩展一个不便直接修改代码第三方类库。
3、分离稳定核心行为与易变的非核心行为。
C#3.0以上的版本提供了扩展方法。扩展方法的用途与访问者模式非常相似,也是在不改变原类的情况下为原类增加新的行为。但是扩展方法的静态扩展只针对一个具体类,而访问者模式则强调继承树下的一组类。此外两者的实现方式也不相同。个人建议在通常情况下使用扩展方法,只在针对继承树中所有类都附加同一行为(行为的实现不同,否则在基类上附加就可以了)时考虑访问者模式。
结构
从结构上看,访问者模式显得有点复杂,下面是模式的类图:
模式的参与者如下:
1、访问者接口,定义了针对每个被访问类(上文所说的“原来的类”)的附加行为——IVisitor;
2、访问者接口的具体实现,通常每个行为作为一个访问者——VisitorA,VisitorB;
3、被访问类的抽象——Element;
4、被访问类的实现——ElementA,ElementB;
5、用于将访问者附加到被访问类上的一个结构——ObjectStructure;
6、客户端程序——Client;
注意,IVisitor接口中为每一个被访问类的实现定义了一个方法,这样的做法违背了面向抽象的原则,这是此模式的一个弱点,也是概述部分论述的那种场景不适用的原因。另外通常将ObjectStructure实现为一个数组形式,以便很方便的对一组被访问类附加访问者行为。关于ObjectStructure是否是此模式的必要成员,似乎存在争议。个人认为从意图角度来说ObjectStructure是一个方便使用的类型,因此不属于模式的必要成员。
示例
考虑一个电子商务平台,个人与经销商(统称卖家)均可以在此平台上销售自己的商品,我们设计了卖家的抽象类,以及继承自卖家的抽象类的个人与经销商具体类。系统已经上线为用户提供服务了。现在有一个新需要,需要显示买家名片,个人与经销商的名片规则不同。我们不想对现有的类型做大的修改,考虑到个人与经销商的划分比较稳定,而未来还可能有很多类似买家名片的这种需求,因此下面使用访问者模式重构系统并实现新需求。
1、卖家抽象类Vendor。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Visitor
5: {
6: /// <summary>
7: /// 商家基类
8: /// </summary>
9: public abstract class Vendor
10: {
11: /// <summary>
12: /// 名字。经销商时为公司名称,个人时为个人姓名。
13: /// </summary>
14: public string Name { get; set; }
15:
16: /// <summary>
17: /// 电话
18: /// </summary>
19: public string Tel { get; set; }
20:
21: /// <summary>
22: /// 获取在售列表(原有行为)
23: /// </summary>
24: /// <returns></returns>
25: public List<string> GetSellList()
26: {
27: return new List<string>();
28: }
29:
30: //其它行为。。。
31:
32: /// <summary>
33: /// 访问者接入点(新增)
34: /// </summary>
35: /// <param name="visitor">访问者</param>
36: public abstract void Accept(IVisitor visitor);
37: }
38: }
39:
2、具体的卖家实现。
经销商类Dealer。
1: using System;
2:
3: namespace DesignPatterns.Visitor
4: {
5: /// <summary>
6: /// 经销商(一个组织)
7: /// </summary>
8: public class Dealer : Vendor
9: {
10: /// <summary>
11: /// 地址
12: /// </summary>
13: public string Address { get; set; }
14:
15: /// <summary>
16: /// 实现访问者接入(新增)
17: /// </summary>
18: /// <param name="visitor">访问者</param>
19: public override void Accept(IVisitor visitor)
20: {
21: visitor.Visit(this);
22: }
23: }
24: }
25:
个人类Person。
1: using System;
2:
3: namespace DesignPatterns.Visitor
4: {
5: /// <summary>
6: /// 个人
7: /// </summary>
8: public class Person : Vendor
9: {
10: /// <summary>
11: /// 性别,true-男,flase-女
12: /// </summary>
13: public bool Gender { get; set; }
14:
15: /// <summary>
16: /// 实现访问者接入(新增)
17: /// </summary>
18: /// <param name="visitor">访问者</param>
19: public override void Accept(IVisitor visitor)
20: {
21: visitor.Visit(this);
22: }
23: }
24: }
25:
3、访问者接口IVisitor。
1: using System;
2:
3: namespace DesignPatterns.Visitor
4: {
5: /// <summary>
6: /// 访问者接口
7: /// </summary>
8: public interface IVisitor
9: {
10: void Visit(Dealer vendor);
11: void Visit(Person vendor);
12: }
13: }
14:
4、名片行为的具体实现AutographVisitor。
1: using System;
2:
3: namespace DesignPatterns.Visitor
4: {
5: /// <summary>
6: /// 商家签名
7: /// </summary>
8: public class AutographVisitor : IVisitor
9: {
10: public void Visit(Dealer vendor)
11: {
12: Console.WriteLine("+++++++++++++++++++++++++++++++++++++");
13: Console.WriteLine("经销商:" + vendor.Name);
14: Console.WriteLine("联系电话:" + vendor.Tel);
15: Console.WriteLine("公司地址:" + vendor.Address);
16: }
17:
18: public void Visit(Person vendor)
19: {
20: Console.WriteLine("+++++++++++++++++++++++++++++++++++++");
21: Console.WriteLine(vendor.Name + (vendor.Gender ? "先生" : "女士"));
22: Console.WriteLine("联系电话:" + vendor.Tel);
23: }
24: }
25: }
26:
5、为卖家附加名片展示行为的结构类VendorList。
1: using System;
2: using System.Collections.Generic;
3:
4: namespace DesignPatterns.Visitor
5: {
6: /// <summary>
7: /// 商家集合
8: /// </summary>
9: public class VendorList
10: {
11: private List<Vendor> vendors = new List<Vendor>();
12:
13: #region 公开的集合操作
14: public Vendor GetVendor(int index)
15: {
16: return vendors[index];
17: }
18:
19: public void AddVendor(Vendor vendor)
20: {
21: vendors.Add(vendor);
22: }
23:
24: public void RemoveVendor(Vendor vendor)
25: {
26: vendors.Remove(vendor);
27: }
28:
29: public int Count
30: {
31: get { return vendors.Count; }
32: }
33: #endregion
34:
35: /// <summary>
36: /// 批量接入访问者
37: /// </summary>
38: /// <param name="visitor">访问者</param>
39: public void Attach(IVisitor visitor)
40: {
41: foreach (var i in vendors)
42: {
43: i.Accept(visitor);
44: }
45: }
46: }
47: }
48:
6、客户端程序。
1: using System;
2:
3: namespace DesignPatterns.Visitor
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: Vendor dealer = new Dealer { Name = "XXXX贸易公司", Address = "XX省XX市XXX街道X号", Tel = "400-000-000" };
10: Vendor person = new Person { Name = "XXXX", Gender = true, Tel = "13212345678" };
11: VendorList vendorList = new VendorList();
12: vendorList.AddVendor(dealer);
13: vendorList.AddVendor(person);
14:
15: IVisitor v = new AutographVisitor();
16:
17: vendorList.Attach(v);
18:
19: Console.WriteLine("按任意键结束...");
20: Console.ReadKey();
21: }
22: }
23: }
24:
7、运行,查看结果。