验证、实现-在C#中使用访问者(Visitor)模式对组合(Composite)对象进行验证 -by小雨

第一次发帖

    在应用程序的开辟程过当中理合用使计设模式,不仅可以处理际实问题,进步开辟率效,而且还可以让程序结构更为清晰理合,对到达“低耦合、高内聚”的计设目标有着很大的帮助。现在网上有很多绍介计设模式的文章,有的也自成体系,基本涵盖了GoF的有所模式,但大多数似类文章都以一些较为单简的型类计设为例(比如Animal、Cat、Dog、Fruit、Apple、Banana等),虽然浅显易懂,但读完后之发明离际实应用还是存在定一的离距。鉴于这样的现状,我也算打总结一些我在项目中遇到的模式应用例案,通过对际实问题停止分析描述,来握把模式应用的思绪,从而帮助读者友朋来懂得如安在项目中理合地应用计设模式。事实上我经已总结了一些,以一种更为理合的织组式方宣布在了我的.NET/DDD架构计设论讨社区Apworks.ORG中,可以点击:http://apworks.org/?page_id=311进入浏览。惯习在博客园里浏览的友朋也可以看查本人博客的“计设模式”部份。

    

背景

    某公司算打开辟一套业企管理软件,于是,业企的织组结构管理就成为了这个软件的主要能功之一。织组结构管理系统所涵盖的能功还是比较多的,而且还会与系统的其它部份生产严密的联系,比如审批流程、成本核算等等都与业企织组结构严密相干。为了配合本文的描述,我们尽可能地简化这部份能功,只将能功限定在织组结构的建立、维护和验证上,基本求需包含:

    

  1. 业企织组结构包含门部、人员两种元素
  2. 门部可以含包多个子门部,还可以含包多个人员
  3. 有所人员必须归属于一个特定的门部
  4. 门部可以属于一另个门部,也可以作为一级门部直属于织组结构
  5. 供提一个单简的图形化界面,用于编辑业企织组结构
  6. 供提各个层级的验证的能功,用以对门部或人员的数据设置停止验证
  7. 供提存保和打开的能功,在存保之前会验证全部织组结构的设置,如果验证失败,将不予以存保

    接下来,我们以面向对象分析和计设的式方,来探究本例案的模型计设和实现。

    

范畴模型

    从面下的基本求需描述不难得悉,模型对象包含三种:织组结构、门部和人员。织组结构和门部、门部和人员之间是组合系关,而门部和门部之间则是合聚系关,组合和合聚的差异就在于A否是必须依赖于B,这在UML的范规中是有论讨的,在此也就不多作说了明。另外,熟习DDD的友朋也经常可以听到“合聚”、“合聚根”的辞汇,但这里所说的“合聚”跟DDD中的不并一样,所以要需注意分区。

    事实上,在我们的范畴范围中,织组结构、门部和人员三者形成了一种树形层次结构:门部隶属于织组结构或一另门部,而人员又隶属于门部,这正是组合对象(Composite)模式应用的典范场景。因此,我们可以用使Composite模式来计设范畴模型。鉴于门部和人员共有着部份属性(例如全局独一标识“ID”)和一些相干操纵,我们就把这部份容内抽象出来,以OrganizationElement抽象类对其停止示表,于是,我们就得到了面下的模型图:

    image

    根据Composite模式的描述,Department型类继承于OrganizationElement抽象型类,同时,它又合聚了OrganizationElement型类,因此,Department型类中可以合聚任何OrganizationElement的派生型类,也就是可以含包多个Employee或者Department。为了编程便方,我在Department型类中计设了两个只读属性,用以别分回返所含包的有所Employee对象和Department对象。这类选筛其实很单简,直接用使LINQ语句便可成完,比如:

    // class Departmentreadonly List<OrganizationElement> elements = new List<OrganizationElement>();public IEnumerable<Department> Departments{ get { return (from element in elements where element is Department select (element as Department)).ToList(); }}

    有了面下计设的类图,将其转换成C#代码就非常单简了,以下是Organization、Department以及Employee类的实现代码段,当然,这些代码段仅表现了类之间的系关,此处并没有展示与能功实现相干的其它部份。

    public abstract class OrganizationElement{ readonly Guid id = Guid.NewGuid(); public override bool Equals(object obj) { if (ReferenceEquals(this, obj)) return true; if (obj == null) return false; OrganizationElement other = obj as OrganizationElement; return other != null && other.ID == this.ID; } public override int GetHashCode() { return id.GetHashCode(); } public Guid ID { get { return id; } }}public class Organization : ICollection<OrganizationElement>{ readonly List<OrganizationElement> elements = new List<OrganizationElement>(); public IEnumerable<Department> Departments { get { return elements.Where(p => p is Department) .Select(p => p as Department).ToList(); } } #region ICollection<OrganizationElement> Members public void Add(OrganizationElement item) { elements.Add(item); } public void Clear() { elements.Clear(); } public bool Contains(OrganizationElement item) { return elements.Contains(item); } public void CopyTo(OrganizationElement[] array, int arrayIndex) { elements.CopyTo(array, arrayIndex); } public int Count { get { return elements.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(OrganizationElement item) { return elements.Remove(item); } #endregion #region IEnumerable<OrganizationElement> Members public IEnumerator<OrganizationElement> GetEnumerator() { return elements.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return elements.GetEnumerator(); } #endregion }public class Department : OrganizationElement, ICollection<OrganizationElement>{ readonly List<OrganizationElement> elements = new List<OrganizationElement>(); public IEnumerable<Department> Departments { get { return (from element in elements where element is Department select (element as Department)).ToList(); } } public IEnumerable<Employee> Employees { get { return (from element in elements where element is Employee select (element as Employee)).ToList(); } } public string Name { get; set; } public string Description { get; set; } #region ICollection<OrganizationElement> Members public void Add(OrganizationElement item) { elements.Add(item); } public void Clear() { elements.Clear(); } public bool Contains(OrganizationElement item) { return elements.Contains(item); } public void CopyTo(OrganizationElement[] array, int arrayIndex) { elements.CopyTo(array, arrayIndex); } public int Count { get { return elements.Count; } } public bool IsReadOnly { get { return false; } } public bool Remove(OrganizationElement item) { return elements.Remove(item); } #endregion #region IEnumerable<OrganizationElement> Members public IEnumerator<OrganizationElement> GetEnumerator() { return elements.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return elements.GetEnumerator(); } #endregion} public class Employee : OrganizationElement{ public string FirstName { get; set; } public string LastName { get; set; } public string PhoneNumber { get; set; } public string Email { get; set; }}

    接下来,我们会进一步富丰这些类,并逐步完善应用程序的一些基本能功,比如:向户用供提一个Windows Forms的应用界面,在树形图视控件(TreeView)中对全部织组结构停止维护,并向户用供提建新、打开和存保织组结构的能功。这个程过其实还含包了很多C#/Windows Forms应用程序开辟的知识点,但这些都不是本文所要论讨的容内,因此我将跳过对这些细节容内的绍介。在成完了这些基本能功的开辟后,我们的应用程序大致如下图所示:

    SNAGHTMLfd00ef

    面下,我们要需向应用程序添加验证能功。为了单简起见,此处我们仅实现以下验证逻辑:

    

  1. 同别级中不存在同名的门部或人员
  2. 门部名称不能为空
  3. 人员的姓、名不能为空
  4. 人员的电子邮件不能为空,并应合符电子邮件地址式格
  5. 人员的电话号码不能为空,并应合符电话号码的式格

    在C#中,实现这些验证的式方是多样的,就我们现在的这个例案而言,大致可以用使以下几种式方:

    

  • 在属性的设置器(setter)中验证数据效有性,当验证失败时抛出异常,Windows Forms的PropertyGrid控件会捕获异常并止防数据写入
  • 自定义一套基于Attribute的验证制机,在属性上设置Attribute,并在属性被设置的时候,通过这套制机成完验证
  • 用使AOP,拦截属性的设置器行为停止验证
  • 历遍全部树形结构,对个每点节停止验证,并计统各点节的验证结果,最后将结果报告给户用

    前三种式方其实都是在属性被设置的时候成完数据验证,这样做可以在户用操纵的个每骤步保确数据的正确性,但同时也会失损定一的户用休会;而第四种式方则向户用供提了更为高效的操纵休会,开辟者可以根据自己项目标际实情况停止择选。在现,就让我们起一懂得一下第四种式方的实现方法。

    

用使拜访者(Visitor)模式实现验证逻辑

    鉴于我们的范畴模型由于组合(Composite)模式的用使而呈现出一种特定的对象结构(此处是树形结构),我们可以采取历遍全部对象结构的式方,对该结构的每个点节停止指定的操纵(验证)。此处我将在织组结构的模型上应用拜访者(Visitor)模式,实现个每点节的验证能功。单简地说,拜访者(Visitor)模式的重点不并在于点节的历遍程过,它的点优在于,它可以将历遍程过当中针对个每点节的操纵,从对象结构本身分离出来,从而到达了“关注点分离”的计设目标。此外,由于定义新的操纵时,无需对已有的对象结构作任何修改,因此,Visitor模式的用使,还可以让计设满意“开-闭”准则(OCP)。

    从Visitor模式的实现上看,要主利用了面向对象的多态性,比如,针对织组结构模型,可以定义一个IVisitor接口,有所实现了该接口的型类都可以对Organization、Department和Employee三种型类的对象停止操纵:

    public interface IVisitor{ void Visit(Organization organization); void Visit(Department department); void Visit(Employee employee);}

    在Organization、Department和Employee中,则要需受接一个IVisitor接口的实例,并调用该实例中的应相方法,以成完对后以对象的操纵。这个程过其实很单简,比如可以在Organization、Department以及Employee中定义一个Accept方法,这个方法受接一个IVisitor的实例作为参数,而在Accept方法中,只要需调用IVisitor.Visit方法便可。就Department而言,由于它本身还合聚了其它的OrganizationElement对象,因此,在Department的Accept方法中,还要需将IVisitor实例传递给个每子OrganizationElement对象的Accept方法,以到达历遍全部对象结构的目标。以下就是Department类中的Accept方法实现:

    public override void Accept(IVisitor visitor){ visitor.Visit(this); foreach (var element in this.elements) element.Accept(visitor);}

    在现,让我们来优化一下这个计设。在前一部份的分析中,我们引入了OrganizationElement作为织组结构模型中有所元素的抽象型类,它含包了这些元素的共有属性和操纵。在历遍全部织组结构对象模型的时候,个每模型元素都将被拜访一次,这也就意味着Visitor中所定义的操纵会应用到个每模型元素上。由此可见,我们可以从实现将上Accept方法定义在OrganizationElement的层面上,在OrganizationElement中,供提一个Accept的抽象方法,有所继承于OrganizationElement的型类都要需实现Accept方法以成完Visitor对其的拜访。

    为了进一步同一Organization类与OrganizationElement类的行为,我们在更高的层面上引入IVisitorAcceptor接口,并让Organization和OrganizationElement都实现这个接口,这样做的处好是,在户用界面部份,我们无需分区后以中选的验证点节究竟是Organization还是OrganizationElement,只要需将存保在点节中的数据转换为IVisitorAcceptor接口的实例,便可受接Visitor来历遍所选的对象结构。IVisitorAcceptor接口定义如下:

    public interface IVisitorAcceptor{ void Accept(IVisitor visitor);}

    基于面下的分析,Organization和OrganizationElement的实现代码如下(仅列出与Visitor模式相干的部份):

    public class Organization : ICollection<OrganizationElement>, IVisitorAcceptor{ public void Accept(IVisitor visitor) { visitor.Visit(this); foreach (var department in this.elements) department.Accept(visitor); }}public abstract class OrganizationElement : IVisitorAcceptor{ public abstract void Accept(IVisitor visitor);}

    全部计设的完全类图如下所示:

    image

    图中OrganizationValidator就是一个IVisitor接口的实现,在三个Visit的重载方法中,别分成完了对Organization、Department和Employee的验证逻辑。此处就不述详其实现代码了,读者请参考本文附带的源程序代码来懂得这个类的体具实现。

    

效果

    在后以的Windows Forms应用程序中添加上基于Visitor模式实现的验证逻辑后以,以可就在意任层级的点节上,击单标鼠右键并择选“验证”菜单项来发触验证逻辑。以下是在添加了一个新的人员息信后,在全部织组结构上停止数据验证的结果,应用程序提示该人员的电子邮件地址式格不正确,以及电话号码不能为空:

    image

    

总结

    本文通过一个际实例案展示了在应用程序开辟程过当中实现组合(Composite)模式和拜访者(Visitor)模式的式方,综上所述,Visitor模式在扩展已有对象结构的操纵上,显得很有优势。这类扩展与型类继承的式方有着实质的别区。通过型类继承可以在原型类上加增新的字段和方法,从而到达行为扩展的目标;但从面向对象的角度来看,有些行为又本不该应属于这些对象,比如本例案中的验证能功,它本不该应是织组结构模型的一种行为(织组结构对象不可能自己验证自己),而是应用程序为织组结构供提的一种附加能功。Visitor模式很好地把这些行为的实现与对象结构分离,使得应用程序可以在不转变对象结构和现有行为的基础上,为之供提新的行为实现(比如,在本例案中如果还要需实现全部织组结构的某项数据计统能功,那么只要需再实现一个OrganizationCounterVisitor型类便可),效有、理合地满意了面向对象计设中的“关注点分离”和“开闭”准则。

    

源程序代码

    请至 http://sdrv.ms/11eOzEF 下载本例案的源程序代码。在Visual Studio 2012中打开处理方案文件后,运行Windows Forms主程序OrgMgmt,然后用使“系统 –> 打开”菜单来打开压缩包中的organization.org文件。

文章结束给大家分享下程序员的一些笑话语录: AdobeFlash拖垮Windows拖垮IE!又拖垮Linux拖垮Ubuntu拖垮FirxEox!还拖垮BSD拖垮MacOS拖垮Safri!简直无所不拖!AdobeFlash滚出网路世界!不要以为市占有率高就可以持续出烂货产品!以后替代品多得是!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值