《模式——工程化实现及扩展》(设计模式C# 版)《中介者模式 Mediator》——“自我检验" 参考答案

转自:《模式——工程化实现及扩展》(设计模式C# 版)
http://www.cnblogs.com/callwangxiang/

 

 

 

 

 MarvellousWorks公司有ABC三个部门负责文件的拟稿、审批和备案,现有的流程如下:

 1.         A部门拟稿后将文件报B部门审核

2.         B部门对于文件审核后,确认文件体例没有缺项后就通知C部门发布

3.         如果B部门发现文件体例有缺项时,将文件返回给A部门重新修改

4.         C部门在接到B部门传来的文件时,先再发布,然后对其归档

 

不过,MarvellousWorks的管理层为了加强部门间文件流转的管理,正在酝酿修改工作流程:

1、 增加D部门专门负责归档,C部门将归档职责划入D部门后只负责发布文件

2、 C部门发布文件后也须先在D部门归档

3、 AB部门间所有往复流转的中间文件也都报给D部门归档

 

请采用本章介绍的中介者模式及其扩展处理,将文件的流转调度过程从ABCD各对象的职责中独立出来,并用单元测试验证不同的流转过程。

 

 

文件对象的定义

 

class Document
{
    
#region essential fields
    
public string Subject { getset; }
    
public string Body { getset; }
    
#endregion

    
#region optional fields
    
public string Comment { getset; }
    
#endregion

    
public override string ToString()
    {
        
return string.Format("\n[{0}]\n------------------\n{1}\n({2})\n", Subject, Body, Comment);
    }
}

 

 

参考答案

 

分析第一步

上述A、B、C、D部门间的协作关系比较复杂,而且预期很快会变化,但协作的中间内容很简单——都是文件,所以采用事件方式,由.NET Framework自己的事件机制作为中介者相对很简单,而且类型间的依赖关系全都推给.NET Framework,为以后扩展更多参与方的协作关系提供便利。

 

 据此,我们定义A、B、C、D时全部采用事件作为提供给外中介者协调响应关系的入口。

 

增加如下类型

class DocumentEventArgs : EventArgs
{
    Document document;
    
public DocumentEventArgs(Document document)
    {
        
this.document = document;
    }
    
public Document Document{getreturn document;}}
}

abstract class Department
{
    
protected Document document = new Document();
    
public Document Document
    {
        
getreturn document;}
        
protected set{ document = value;}
    }
}

 

 

分析第二步

 如果直接通过事件重载操作符 +=和-=建立各Colleague的响应关系,需要重复编写代码,而且不能在系统上线后将这个工作交给管理员维护。

因此,考虑参考前面的Builder模式,增加一个基于配置动态维护维护事件响应关系的对象。

 

 

实现和单元测试验证

 

 

1、验证“分析第一步”的设想

/// <summary>
/// 测试手工定义事件中介者的交互关系
/// </summary>
[TestMethod]
public void TestManualDefineEventMediatorInSucceedBranch()
{
    
//  用事件配置松散的响应关系
    a1.WriteDocumentFinishedHandler += b1.OnReceiveFileToReview;
    b1.ReviewDocumentFailedHandler 
+= a1.OnReviewFailed;
    b1.ReviewDocumentSucceedHandler 
+= c1.OnReceiveFileToPublish;
    b1.ReviewDocumentSucceedHandler 
+= c1.OnReceiveFileToArchive;

    
//  成功的路径
    a1.Write("a""b""c");

    
//  验证修订后的内容曾经流转给了B
    Assert.AreEqual<string>("a", b1.Document.Subject);
    Assert.AreEqual
<string>("b", b1.Document.Body);
    Assert.AreEqual
<string>("c", b1.Document.Comment);

    
//  验证修订后的内容也曾经流转给了C
    Assert.AreEqual<string>("a", c1.Document.Subject);
    Assert.AreEqual
<string>("b", c1.Document.Body);
    Assert.AreEqual
<string>("c", c1.Document.Comment);
}

Output窗口

------ Test started: Assembly: Mediator.Tests.dll ------

A begin write
A write finished

[a]
------------------
b
(c)

B received doc from A to review
B begin review
B review succeed
C received doc to publish from B
C published 
C received doc to archive from B
C archived

1 passed, 0 failed, 0 skipped, took 0.50 seconds (MSTest 10.0).

 

 

/// <summary>
/// 测试手工定义事件中介者的交互关系
/// </summary>
[TestMethod]
public void TestManualDefineEventMediatorInFailedBranch()
{
    
//  用事件配置松散的响应关系
    a1.WriteDocumentFinishedHandler += b1.OnReceiveFileToReview;
    b1.ReviewDocumentFailedHandler 
+= a1.OnReviewFailed;
    b1.ReviewDocumentSucceedHandler 
+= c1.OnReceiveFileToPublish;
    b1.ReviewDocumentSucceedHandler 
+= c1.OnReceiveFileToArchive;

    
//  失败的路径
    a1.Write("a""""");

    
//  验证确实文档曾经流转给了B
    Assert.AreEqual<string>("a", b1.Document.Subject);
    Assert.AreEqual
<string>("", b1.Document.Body);
    Assert.AreEqual
<string>("", b1.Document.Comment);

    
//  验证文档并没有流转给C
    Assert.IsNull(c1.Document.Subject);
    Assert.IsNull(c1.Document.Body);
    Assert.IsNull(c1.Document.Comment);

    
//  修正错误的内容,重新执行流程
    a1.Write("a""b""c");

    
//  验证修订后的内容曾经流转给了B
    Assert.AreEqual<string>("a", b1.Document.Subject);
    Assert.AreEqual
<string>("b", b1.Document.Body);
    Assert.AreEqual
<string>("c", b1.Document.Comment);

    
//  验证修订后的内容也曾经流转给了C
    Assert.AreEqual<string>("a", c1.Document.Subject);
    Assert.AreEqual
<string>("b", c1.Document.Body);
    Assert.AreEqual
<string>("c", c1.Document.Comment);
}

 

 

Output窗口

 

------ Test started: Assembly: Mediator.Tests.dll ------

A begin write
A write finished

[a]
------------------

()

B received doc from A to review
B begin review
B review failed
A received doc review failed from B
A begin write
A write finished

[a]
------------------
b
(c)

B received doc from A to review
B begin review
B review succeed
C received doc to publish from B
C published 
C received doc to archive from B
C archived

1 passed, 0 failed, 0 skipped, took 3.64 seconds (MSTest 10.0).

 

 

2、验证“分析第二部” 的设想

 

 定义管理基于事件的中介关系Builder

class EventMediatorBuilder
{
    
class ConfigItem
    {
        
public Type SourceType { getset; }
        
public Type TargetType { getset; }
        
public string SourceEventName { getset; }
        
public string TargetHandlerMethodName { getset; }

        
public override bool Equals(object obj)
        {
            
if (obj == nullthrow new ArgumentNullException("obj");
            var target 
= (ConfigItem)obj;
            
return
                SourceType 
== target.SourceType &&
                TargetType 
== target.TargetType &&
                
string.Equals(SourceEventName, target.SourceEventName) &&
                
string.Equals(TargetHandlerMethodName, target.TargetHandlerMethodName);
        }
    }

    IList
<ConfigItem> config = new List<ConfigItem>();

    
public EventMediatorBuilder AddConfig(Type sourceType, Type targetType, string sourceEventName, string targetHandlerMethodName)
    {
        
if (sourceType == nullthrow new ArgumentNullException("sourceType");
        
if (targetType == nullthrow new ArgumentNullException("targetType");
        
if (string.IsNullOrEmpty(sourceEventName)) throw new ArgumentNullException("sourceEventName");
        
if (string.IsNullOrEmpty(targetHandlerMethodName)) throw new ArgumentNullException("targetHandlerMethodName");

        
if (sourceType.GetEvent(sourceEventName) == nullthrow new NotSupportedException(sourceEventName);
        var item 
= new ConfigItem()
        {
            SourceType 
= sourceType,
            TargetType 
= targetType,
            SourceEventName 
= sourceEventName,
            TargetHandlerMethodName 
= targetHandlerMethodName
        };
        
if (!config.Contains(item))
            config.Add(item);

        
return this;
    }

    
public EventMediatorBuilder BuildAUpColleagues(params object[] colleagues)
    {
        
if (colleagues == nullthrow new ArgumentNullException("colleagues");
        
if (config.Count() == 0return this;       //  没有通信关系配置项
        if (colleagues.Count() == 1return this;    //  没有需要配置的关联对象组
        colleagues.ToList().ForEach(x => { if (x == nullthrow new ArgumentNullException(); });

        
////  限制:不支持一类对象的某个实例同时向另一类对象多个实例的通知
        //if (colleagues.GroupBy(x => x.GetType()).Count() != colleagues.Count())
        
//    throw new NotSupportedException();

        
foreach (var item in config)
        {
            var sources 
= colleagues.Where(x => x.GetType() == item.SourceType);
            
if ((sources == null|| (sources.Count() == 0))
                
continue;
            var targets 
= colleagues.Where(x => x.GetType() == item.TargetType);
            
if ((targets == null|| (targets.Count() == 0))
                
continue;
            var eventInfo 
= item.SourceType.GetEvent(item.SourceEventName);
            
if (eventInfo == null)
                
continue;
            var methodInfo 
= item.TargetType.GetMethod(item.TargetHandlerMethodName, BindingFlags.Public | BindingFlags.Instance);
            
if (methodInfo == null)
                
continue;

            
//  绑定事件响应关系
            foreach (var source in sources)
                
foreach (var target in targets)
                    eventInfo.AddEventHandler(source, Delegate.CreateDelegate(eventInfo.EventHandlerType, target, methodInfo));
        }

        
return this;
    }
}

 

实现和单元测试验证

 

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MarvellousWorks.PracticalPattern.Mediator.Tests.Exercise
{
    [TestClass]
    
public class DocumentWorkflowMediatorFixture
    {
        Scenario1.A a1;
        Scenario1.B b1;
        Scenario1.C c1;
        Scenario2.A a2;
        Scenario2.B b2;
        Scenario2.C c2;
        Scenario2.D d2;

        EventMediatorBuilder scenario1Builder;
        EventMediatorBuilder scenario2Builder;

        
/// <summary>
        
/// 配置不同协调关系
        
/// 实际项目中可以采用本章介绍的基于配置文件的定义方式
        
/// </summary>
        [TestInitialize]
        
public void Initialize()
        {
            a1 
= new Scenario1.A();
            b1 
= new Scenario1.B();
            c1 
= new Scenario1.C();
            a2 
= new Scenario2.A();
            b2 
= new Scenario2.B();
            c2 
= new Scenario2.C();
            d2 
= new Scenario2.D();

            scenario1Builder 
= new EventMediatorBuilder()
                
//  1.    A部门拟稿后将文件报B部门审核
                .AddConfig(typeof(Scenario1.A), typeof(Scenario1.B), "WriteDocumentFinishedHandler""OnReceiveFileToReview")
                
//  2.    B部门对于文件审核后,确认文件体例没有缺项后就通知C部门发布
                .AddConfig(typeof(Scenario1.B), typeof(Scenario1.C), "ReviewDocumentSucceedHandler""OnReceiveFileToPublish")
                
//  3.    如果B部门发现文件体例有缺项时,将文件返回给A部门重新修改
                .AddConfig(typeof(Scenario1.B), typeof(Scenario1.A), "ReviewDocumentFailedHandler""OnReviewFailed")
                
//  4.    C部门在接到B部门传来的文件时,先再发布,然后对其归档
                .AddConfig(typeof(Scenario1.C), typeof(Scenario1.C), "DocumentPublishedHandler""OnReceiveFileToArchive");


            scenario2Builder 
= new EventMediatorBuilder()
                .AddConfig(
typeof(Scenario2.A), typeof(Scenario2.B), "WriteDocumentFinishedHandler""OnReceiveFileToReview")
                .AddConfig(
typeof(Scenario2.A), typeof(Scenario2.D), "WriteDocumentFinishedHandler""OnReceiveFileToArchive")
                .AddConfig(
typeof(Scenario2.B), typeof(Scenario2.A), "ReviewDocumentFailedHandler""OnReviewFailed")
                .AddConfig(
typeof(Scenario2.B), typeof(Scenario2.D), "ReviewDocumentFailedHandler""OnReceiveFileToArchive")
                .AddConfig(
typeof(Scenario2.B), typeof(Scenario2.C), "ReviewDocumentSucceedHandler""OnReceiveFileToPublish")
                .AddConfig(
typeof(Scenario2.C), typeof(Scenario2.D), "DocumentPublishedHandler""OnReceiveFileToArchive");

        }

        
/// <summary>
        
/// 测试通过Event Mediator Builder构造现有业务流程下的协作关系
        
/// </summary>
        [TestMethod]
        
public void TestScenario1()
        {
            
//  通过Event Mediator以及配置信息建立三个部门Colleague间的协作关系
            
//  所有协调关系统一剥离到作为Mediator的.NET事件机制上
            scenario1Builder.BuildAUpColleagues(a1, b1, c1);

            
//  成功的路径
            Trace.WriteLine("Succeed path");
            a1.Write(
"a""b""c");

            
//  失败的路径
            Trace.WriteLine("\n\nFailed path");
            a1.Write(
"a""""");

            
//  修正错误的内容,重新执行流程
            Trace.WriteLine("Modified after review failed path");
            a1.Write(
"a""b""c");
        }

        
/// <summary>
        
/// 测试通过Event Mediator Builder构造管理层期望的未来业务流程下的协作关系
        
/// </summary>
        [TestMethod]
        
public void TestScenario2()
        {
            
//  通过Event Mediator以及配置信息建立三个部门Colleague间的协作关系
            
//  所有协调关系统一剥离到作为Mediator的.NET事件机制上
            scenario2Builder.BuildAUpColleagues(a2, b2, c2, d2);

            
//  成功的路径
            Trace.WriteLine("Succeed path");
            a2.Write(
"a""b""c");

            
//  失败的路径
            Trace.WriteLine("\n\nFailed path");
            a2.Write(
"a""""");

            
//  修正错误的内容,重新执行流程
            Trace.WriteLine("Modified after review failed path");
            a2.Write(
"a""b""c");
        }
    }
}

 

 

备注1:现有情景下的A、B、C类型定义

 

namespace Scenario1
{
    
class A : Department
    {
        
public event EventHandler<DocumentEventArgs> WriteDocumentFinishedHandler;

        
public void Write(string subject, string body, string comment)
        {
            
if (Document == nullthrow new NullReferenceException("Document");
            Trace.WriteLine(
"A begin write");
            Document.Subject 
= subject;
            Document.Body 
= body;
            Document.Comment 
= comment;
            Trace.WriteLine(
"A write finished");
            Trace.WriteLine(Document);

            
if(WriteDocumentFinishedHandler != null)
                WriteDocumentFinishedHandler(
thisnew DocumentEventArgs(Document));
        }

        
public void OnReviewFailed(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"A received doc review failed from " + sender.GetType().Name);
            Document 
= eventArgs.Document;
        }
    }

    
class B : Department
    {
        
public event EventHandler<DocumentEventArgs> ReviewDocumentSucceedHandler;
        
public event EventHandler<DocumentEventArgs> ReviewDocumentFailedHandler;

        
public void Review()
        {
            Trace.WriteLine(
"B begin review");
            var result 
=
                (Document 
!= null&&
                
! string.IsNullOrEmpty(Document.Subject) &&
                
! string.IsNullOrEmpty(Document.Body);

            
if (result)
            {
                Trace.WriteLine(
"B review succeed");
                
if (ReviewDocumentSucceedHandler != null)
                    ReviewDocumentSucceedHandler(
thisnew DocumentEventArgs(Document));
            }
            
else
            {
                Trace.WriteLine(
"B review failed");
                
if (ReviewDocumentFailedHandler !=  null)
                    ReviewDocumentFailedHandler(
thisnew DocumentEventArgs(Document));
            }
        }

        
public void OnReceiveFileToReview(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"B received doc from " + sender.GetType().Name + " to review");
            Document 
= eventArgs.Document;
            Review();
        }
    }

    
class C : Department
    {
        
public event EventHandler<DocumentEventArgs> DocumentPublishedHandler;
        
public event EventHandler<DocumentEventArgs> DocumentArchivedHandler;

        
public void Publish()
        {
            Trace.WriteLine(
"C published ");
            
if(DocumentPublishedHandler != null)
                DocumentPublishedHandler(
thisnew DocumentEventArgs(Document));
        }

        
public void Archive()
        {
            Trace.WriteLine(
"C archived");
            
if(DocumentArchivedHandler != null)
                DocumentArchivedHandler(
thisnew DocumentEventArgs(Document));
        }

        
public void OnReceiveFileToPublish(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"C received doc to publish from " + sender.GetType().Name);
            Document 
= eventArgs.Document;
            Publish();
        }

        
public void OnReceiveFileToArchive(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"C received doc to archive from " + sender.GetType().Name);
            Document 
= eventArgs.Document;
            Archive();
        }
    }
}

 

 

备注2:管理层期望的A、B、C、D类型定义

 

 

namespace Scenario2
{
    
class A : Department
    {
        
public event EventHandler<DocumentEventArgs> WriteDocumentFinishedHandler;

        
public void Write(string subject, string body, string comment)
        {
            
if (Document == nullthrow new NullReferenceException("Document");
            Trace.WriteLine(
"A begin write");
            Document.Subject 
= subject;
            Document.Body 
= body;
            Document.Comment 
= comment;
            Trace.WriteLine(
"A write finished");
            Trace.WriteLine(Document);

            
if(WriteDocumentFinishedHandler != null)
                WriteDocumentFinishedHandler(
thisnew DocumentEventArgs(Document));
        }

        
public void OnReviewFailed(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"A received doc review failed from " + sender.GetType().Name);
            Document 
= eventArgs.Document;
        }
    }

    
class B : Department
    {
        
public event EventHandler<DocumentEventArgs> ReviewDocumentSucceedHandler;
        
public event EventHandler<DocumentEventArgs> ReviewDocumentFailedHandler;

        
public void Review()
        {
            Trace.WriteLine(
"B begin review");
            var result 
=
                (Document 
!= null&&
                
! string.IsNullOrEmpty(Document.Subject) &&
                
! string.IsNullOrEmpty(Document.Body);

            
if (result)
            {
                Trace.WriteLine(
"B review succeed");
                
if (ReviewDocumentSucceedHandler != null)
                    ReviewDocumentSucceedHandler(
thisnew DocumentEventArgs(Document));
            }
            
else
            {
                Trace.WriteLine(
"B review failed");
                
if (ReviewDocumentFailedHandler != null)
                    ReviewDocumentFailedHandler(
thisnew DocumentEventArgs(Document));
            }
        }

        
public void OnReceiveFileToReview(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"B received doc from " + sender.GetType().Name + " to review");
            Document 
= eventArgs.Document;
            Review();
        }
    }

    
class C : Department
    {
        
public event EventHandler<DocumentEventArgs> DocumentPublishedHandler;

        
public void Publish()
        {
            Trace.WriteLine(
"C published ");
            
if(DocumentPublishedHandler != null)
                DocumentPublishedHandler(
thisnew DocumentEventArgs(Document));
        }

        
public void OnReceiveFileToPublish(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"C received doc to publish from " + sender.GetType().Name);
            Document 
= eventArgs.Document;
            Publish();
        }
    }

    
class D : Department
    {
        
public event EventHandler<DocumentEventArgs> DocumentArchivedHandler;

        
public void Archive()
        {
            Trace.WriteLine(
"D archived");
            
if(DocumentArchivedHandler != null)
                DocumentArchivedHandler(
thisnew DocumentEventArgs(Document));
        }

        
public void OnReceiveFileToArchive(object sender, DocumentEventArgs eventArgs)
        {
            Trace.WriteLine(
"C received doc to archive from " + sender.GetType().Name);
            Document 
= eventArgs.Document;
            Archive();
        }
    }
}

 

 

posted @ 2011-05-17 12:14  蜡笔小王  阅读(767)  评论(2编辑  收藏  举报