WCF 扩展之我见: IExtensibleObject

本系列索引,请见《开篇

前面一系列文章就 Service Model Layer 中的各个环节 (即 “从 Message 转变为对一个 CLR 方法的调用” 的过程)如何进行扩展给予了相应的介绍。本文要介绍的内容可与前面介绍的部分扩展相结合而实现更多的能力,它就是 IExtensibleObject(当然缺少不了 IExtension 和 IExtensionCollection)。

可扩展对象模式

从 MSDN 的解释来看,IExtensibleObject、IExtension 和 IExtensionCollection 这三个构成了可扩展模式。来看一下 MSDN 给的定义:

The extensible object pattern is used to either extend existing runtime classes with new functionality or to add new state to an object. Extensions, attached to one of the extensible objects, enable behaviors at very different stages in processing to access shared state and functionality attached to a common extensible object that they can access.

上面这段话的大意是:“可扩展对象模式被用来向一个运行过程中的对象添加新的功能或状态来达到扩展的目的”。虽然微软对此冠以了 “模式” 这个称谓,但是本人觉得它不应该被理解为是一种 “设计模式”,更多的是一种编码的模式(即,如果你希望你的对象是一个可扩展的对象,那么就要正确使用这几个接口)。

通过使用这几个接口,就会让一个对象具有被扩展能力(也就是说能够增加额外的状态和行为)。放到 WCF 的大背景下来说,就是让 WCF 运行时的某些对象(即,ServiceHostBase、InstanceContext、OperationContext 和 IContextChannel)变成可扩展,这样开发人员就可以为这些对象增加一些新的行为或者状态。这里要注意一个细节:当通过该模式为 WCF 中的这四个运行时对象添加了扩展之后,这些扩展的生命周期与这四个对象是一致的这意味着我们在 DispatchMessageInspector 中为 InstanceContext 中新增的属性,在 CallContextInitializer 中可以被取出来,且只要该 InstanceContext 实例存在(当支持会话状态的时候),该属性就一直存在)。

当然,除了运用于 WCF 项目中,还可以被用在其它类型项目中,比如把一个 Button 变成一个可扩展的 Button。


既然一切都与这三个接口相关,那先来了解下它们的定义:

IExtensiableObject<T>

    public interface IExtensibleObject<T>
        where T : IExtensibleObject<T>
    {    
        IExtensionCollection<T> Extensions { get; }
    }

通过实现这个接口让对象变成可扩展的(Extensible),i.e. 一个 Extensions 的属性,实际上就是允许往该对象中聚合各种扩展(这里是名词)。


IExtensionCollection<T>

    public interface IExtensionCollection<T> : ICollection<IExtension<T>>
        where T : IExtensibleObject<T>
    {    
        E Find<E>();        
        Collection<E> FindAll<E>();
    }

扩展的集合,提供了 Find 和 FindAll 来帮助快速从集合中找到特定类型的扩展。如果没有找到任何扩展则 Find<T> 将返回 Null,如果同类型的扩展有很多,则返回其中一个。FindAll<E> 当没有找到时,返回一个空的集合。


IExtension<T>

    public interface IExtension<T> where T : IExtensibleObject<T>
    {    
        void Attach(T owner);        
        void Detach(T owner);
    }

用于实现扩展(这里是名词),当一个类型实现了该接口,它就成了一个适用于某个可扩展对象(IExtensibleObject<T>)的扩展。我们可以使用 Attach 来修改 owner 对象已有的一些状态和行为,也可以直接在该扩展中定义新的属性来表示新的状态(定义新的方法来表示新的行为)。

Attach 会在当扩展被加入到集合的时候被触发,而 Detach 则在当扩展被移除集合的时候被触发。


“一个对象” 一词被加粗,首先是为了用于与 “类型” 进行区别,在这里强调的是运行过程中的对象,而非静态的 class;其次用于指明这里只有 “一个” 对象,不会因为被扩展了行为,而变成另一个对象(可以认为从对象被创建到添加扩展到最终销毁,都是同一个对象,同一个引用)。

如何使用(非 WCF)

MSDN 描述了使用 “可扩展对象模式” 的两种情形:

There are two main scenarios. 

The first scenario uses the Extensions property as a type-based dictionary to insert state on an object to enable another component to look it up using the type. 

The second scenario uses the Attach and Detach properties to enable an object to participate in custom behavior, such as registering for events, watching state transitions, and so on.

第一种情况就是为对象添加一个状态,用于在日后的某个时候可以取出来使用。

第二种情况就是为对象添加一些自定义的行为,比如用于注册 event、监控状态的迁移等。


举个粟子,假设我有一个 Button 按钮,我希望为这个按钮添加一个日志的行为。

首先,定义可扩展按钮:

public class ExtensibleButton : Button, IExtensibleObject<ExtensibleButton>
{
    private IExtensionCollection<ExtensibleButton> _extensions;

    public ExtensibleButton()
    {
        _extensions = new ExtensionCollection<ExtensibleButton>(this);
    }

    public IExtensionCollection<ExtensibleButton> Extensions
    {
        get
        {
            return _extensions;
        }
    }
}


接着,定义日志扩展:

public class MyLogButtonExtension : IExtension<ExtensibleButton>
{
    public void Attach(ExtensibleButton owner)
    {
        owner.Click += owner_Click;
    }

    void owner_Click(object sender, EventArgs e)
    {
        //TODO: write to log file
        Debug.WriteLine("clicked");
    }

    public void Detach(ExtensibleButton owner)
    {
        owner.Click -= owner_Click;
    }
}


然后在 Desinger.cs 中用可扩展 Button 替换原 Button:

private void InitializeComponent()
{
    private ExtensibleButton button1;
    
    this.button1 = new ExtensibleButton();
    this.SuspendLayout();
    // 
    // button1
    // 
    this.button1.Location = new System.Drawing.Point(93, 47);
    this.button1.Name = "button1";
    this.button1.Size = new System.Drawing.Size(75, 23);
    this.button1.TabIndex = 0;
    
    //省略部分代码
}


最后,在 Form 的构造函数中把日志扩展添加到 Button 中去:

public Form1()
{
    InitializeComponent();

    button1.Extensions.Add(new MyLogButtonExtension());
}

Ok,现在当我们点击 button1 的时候,就会看到 Visual Studio 的输出窗口中会出现 “clicked” 字样。


如何使用(WCF)

相信大家已经清楚了如何进行使用,下面介绍下 WCF 中的使用场景。如文章开头所述,WCF 已经为我们提供了四个可扩展对象,以便于我们在 WCF 的运行时中加以使用。这四个对象为:ServiceHostBase、InstanceContext、OperationContext 和 IContextChannel。

每一个对象的生命周期和可操控的属性都不尽相同:

ServiceHostBase 代表服务宿主,在整个服务运行过程中一直存在。

InstanceContext 实例上下文,生命周期根据设定的实例模式而不相同,如 PerCall 的情况,只存在于一次调用过程中;PerSession,且绑定支持会话,则为会话的生命周期;Single 则与服务的生命周期一样。

OperationContext 可提供当前 Operation 的一些信息,比如 IncomingMessageHeader。

IContextChannel 可获得与当前 Channel 相关的信息。


让我来举个粟子, 在 InstanceContextInitializer 中根据 MessageHeder 向 InstanceContext 添加一个新的状态(GUID),然后在 CallContextInitializer 中获取这个 GUID。

首先,定义一个 MyGuidExtension:

public class MyGuidExtension : IExtension<InstanceContext>
{
    public Guid Guid
    {
        get;
        set;
    }

    public void Attach(InstanceContext owner)
    {
    }

    public void Detach(InstanceContext owner)
    {
    }
}


其次,定义一个 MyInstanceContextInitializer 把扩展添加到 InstanceContext 中:

public class MyInstanceContextInitializer : IInstanceContextInitializer
{
    public void Initialize(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
    {
        instanceContext.Extensions.Add(new MyGuidExtension{ Guid = Guid.NewGuid()});
    }
}


接着,定义一个 MyCallContextInitializer 来获取这个 Guid:

public class MyCallContextInitializer : ICallContextInitializer
{
    public void AfterInvoke(object correlationState)
    {

    }

    public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, System.ServiceModel.Channels.Message message)
    {
        MyGuidExtension ext = instanceContext.Extensions.Find<MyGuidExtension>();
        Debug.WriteLine(ext.Guid);            

        return null;
    }
}


最后,就是把 MyInstanceContextInitializer 和 MyCallContextInitializer 通过 Behavior 关联到 WCF 运行时中,这部分请大家参考《Behaviors》。


IExtensibleDataObject

有一些童鞋可能会分不清楚何时该用 IExtensibleObject,何时该用 IExtensibleDataObject。这两个虽然名字上很像,但作用并不相同。前者主要是为了往一个对象中添加行为和状态,而后者只是 WCF 用于提供向前兼容(Forward Compatibility)的一个变通方案,它的作用是在反序列化的时候把 Message 中额外的节点暂时存放到一个类似字典的集合中,然后在序列化的时候把这些节点在写到 Message 中。详细请参考我之前写的《WCF 契约之 DataContract [下]》中关于 IExtensibleDataObject 的部分。


设计模式

既然说到 “模式” 两字,那就再多讲一些。

关于 “可扩展” 这一特性,相信有些童鞋早就想到了几个设计模式(本人认为,模式的前提是原则,模式的推导是在结合了多种不同的原则后为解决某一特定领域问题而总结出的一套相对具体的行为规范。因为想要更好的掌握模式,必须先了解模式背后的原则):

Decorator

装饰者模式,这个模式的主要目的是将新的行为添加到一个已有的对象上(即,装饰),着重点在于 “添加”。


Strategy

策略模式,用于把算法从对象中分离出来,从而以便于在运行过程中进行替换,着重点在于 “替换”。有些朋友经常会觉得策略模式


Composite

组合模式,用于把多个 “部分” 聚合成一个 “整体”,通过暴露 “整体” 的接口代替暴露 “部分” 的接口,从而减少调用方的复杂度(只需要调用 “整体” 的一个接口,而不用依次调用各个 “部分” 的接口)。


上面只是简单的对部分相关的设计模式进行了介绍,之后如有机会将就设计原则与模式写一系列文章。


参考资源

Extensible Objects

来源于WCF的设计模式:可扩展对象模式[上篇]

Elementary Patterns Strategy, Decorator, and Composite

Decorators Compared To Strategies, Composites, and Presenters

鸭子-策略模式

装饰模式

文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charley Box | 关于本站 | 浙ICP备13014059号