问说网 · 发表于 2014-12-15

文章13126 · 评论476

深入分析.Net中的Composite模式

字数10611 · 浏览 1162 · 评论 0

首先,我们来剖析Web 控件的基类Control类的内部实现:

public class Control : IComponent, IDisposable, IParserAccessor, IDataBindingsAccessor{
    // Events;略
    // Methods
    public Control(){
        if (this is INamingContainer){
            this.flags[0x80] = true;
        }
    }
    public virtual bool HasControls(){
        if (this._controls != null){
            return (this._controls.Count > 0);
        }
        return false;
    }
    public virtual void DataBind(){
        this.OnDataBinding(EventArgs.Empty);
        if (this._controls != null){
            string text1 =
            this._controls.SetCollectionReadOnly("Parent_collections_readonly");
            int num1 = this._controls.Count;
            for (int num2 = 0; num2 < num1; num2++){
                this._controls[num2].DataBind();
            }
            this._controls.SetCollectionReadOnly(text1);
        }
    }
    protected virtual void Render(HtmlTextWriter writer){
        this.RenderChildren(writer);
    }
    protected virtual ControlCollection CreateControlCollection(){
        return new ControlCollection(this);
    }
    // Properties
    public virtual ControlCollection Controls{
    get{
        if (this._controls == null){
            this._controls = this.CreateControlCollection();
        }
        return this._controls;
        }
    }
    // Fields
    private ControlCollection _controls;
}

Control 基类中的属性和方法很多,为清晰起见,我只保留了几个与模式有关的关键方法与属性。在上述的源代码中,我们需要注意几点:

  1. Control 类不是抽象类,而是具体类。这是因为在设计时,我们可能会创建Control类型的实例。根据这一点来看,这并不符合OOP 的要求。一般而言,作为抽象出来的基类,必须定义为接口或抽象类。不过在实际的设计中,也不应拘泥于这些条条框框,而应审时度势,根据实际的情况来抉择最佳的设计方案。
  2. 公共属性Controls为ControlCollection 类型,且该属性为virtual属性。也就是说,这个属性可以被它的子类override。同时,该属性为只读属性,在其get访问器中,调用了方法CreateControlCollection();这个方法为protected虚方法,默认的实现是返回一个ControlCollection实例。
  3. 方法HasControls(),功能为判断Control 对象是否有子控件。它判断的依据是根据私有字段_controls(即公共属性Controls)的Count 值。但是需要注意的是,通过HasControls()方法的返回值,并不能决定对象本身属于叶节点,还是枝节点。因为即使是枝节点其内部仍然可以不包含任何子对象。
  4. 方法DataBind()的实现中,首先调用了自身的OnDataBinding()方法,然后又遍历了Controls 中的所有控件,并调用其DataBind()方法。该方法属于控件的共有行为,从这里可以看出不管是作为叶节点的控件,还是作为枝节点的控件,它们都实现统一的接口。对于客户端调用而言,枝节点和叶节点是没有区别的。
  5. Control 类的完整源代码中,并不存在Add()、Remove()等类似的方法,以提供添加和移除子控件的功能。事实上,继承Control类的所有子类均不存在Add()、Remove()等方法。

显然,在Control 类的定义和实现中,值得我们重视的是公共属性Controls 的类型ControlCollection。顾名思义,该类必然是一个集合类型。是否有关子控件的操作,都是在ControlCollection类型中实现呢?我们来分析一下ControlCollection的代码:

public class ControlCollection : ICollection, IEnumerable{
    // Methods
    public ControlCollection(Control owner){
        this._readOnlyErrorMsg = null;
        if (owner == null){
            throw new ArgumentNullException("owner");
        }
        this._owner = owner;
    }
    public virtual void Add(Control child){
        if (child == null){
            throw new ArgumentNullException("child");
        }
        if (this._readOnlyErrorMsg != null){
            throw new
            HttpException(HttpRuntime.FormatResourceString(this._readOnlyErrorMsg));
        }
        if (this._controls == null){
            this._controls = new Control[5];
        }else if (this._size >= this._controls.Length){
            Control[] controlArray1 = new Control[this._controls.Length * 4];
            Array.Copy(this._controls, controlArray1, this._controls.Length);
            this._controls = controlArray1;
        }
        int num1 = this._size;
        this._controls[num1] = child;
        this._size++;
        this._version++;
        this._owner.AddedControl(child, num1);
    }
    public virtual void Remove(Control value){
        int num1 = this.IndexOf(value);
        if (num1 >= 0){
            this.RemoveAt(num1);
        }
    }
    // Indexer
    public virtual Control this[int index]{
        get{
            if ((index < 0) || (index >= this._size)){
                throw new ArgumentOutOfRangeException("index");
            }
            return this._controls[index];
        }
    }
    // Properties
    public int Count{
        get{
            return this._size;
        }
    }
    protected Control Owner{
        get{
            return this._owner;
        }
    }
    protected Control Owner { get; }
    // Fields
    private Control[] _controls;
    private const int _defaultCapacity = 5;
    private const int _growthFactor = 4;
    private Control _owner;
}

一目了然,正是ControlCollection 的Add()、Remove()方法完成了对子控件的添加和删除。例如:

Control parent = new Control();
Control child = new Child();
//添加子控件child;
parent.Controls.Add(child);
//移除子控件child;
parent.Controls.Remove(child);

为什么要专门提供ControlCollection 类型来管理控件的子控件呢?首先,作为类库使用者,自然希望各种类型的控件具有统一的接口,尤其是自定义控件的时候,不希望自己重复定义管理子控件的操作;那么采用透明方式自然是最佳方案。然而,在使用控件的时候,安全也是需要重点考虑的,如果不考虑子控件管理的合法性,一旦使用错误,会导致整个应用程序出现致命错误。从这样的角度考虑,似乎又应采用安全方式。这里就存在一个抉择。故而,.Net 在实现Control 类库时,利用了职责分离的原则,将控件对象管理子控件的属性与行为和控件本身分离,并交由单独的ControlCollection类负责。同时采用聚合而非继承的方式,以一个公共属性Controls,存在于Control 类中。这种方式,集保留了透明方式和安全方式的优势,又摒弃了这两种方式固有的缺陷,因此我名其为“复合方式”。

“复合方式”的设计,其对安全的保障,不仅仅是去除了Control 类关于子控件管理的统一接口,同时还通过异常管理的方式,在ControlCollection类的子类中实现:

public class EmptyControlCollection : ControlCollection{
    // Methods
    public EmptyControlCollection(Control owner) : base(owner){
    }
    public override void Add(Control child){
        this.ThrowNotSupportedException();
    }
    private void ThrowNotSupportedException(){
        throw new HttpException(HttpRuntime.FormatResourceString
        ("Control_does_not_allow_children", base.Owner.GetType().ToString()));
    }
}

EmptyControlCollection 继承了ControlCollection 类,并重写了Add()等添加子控件的方法,使其抛出一个异常。注意,它并没有重写父类的Remove()方法,这是因为ControlCollection类在实现Remove()方法时,对集合内的数据进行了非空判断。而在EmptyControlCollection类中,是不可能添加子控件的,直接调用父类的Remove()方法,是不会出现错误的。

既然管理子控件的职责由ControlCollection 类型负责,且Control 类中的公共属性Controls 即为ControlCollection 类型。所以,对于控件而言,如果是树形结构中的叶节点,它不能包含子控件,它的Controls 属性就应为EmptyControlCollection 类型,假如用户调用了Controls的Add()方法,就会抛出异常。如果控件是树形结构中的枝节点,它支持子控件,那么Controls属性就是ControlCollection类型。究竟是枝节点还是叶节点,决定权在于公共属性Controls:

public virtual ControlCollection Controls{
    get{
        if (this._controls == null){
            this._controls = this.CreateControlCollection();
        }
        return this._controls;
    }
}

在属性的get 访问器中,调用了protected方法CreateControlCollection(),它创建并返回了一个ControlCollection实例:

protected virtual ControlCollection CreateControlCollection(){
    return new ControlCollection(this);
}

很明显,在Control基类实现Controls属性时,采用了Template Method模式,它推迟了ControlCollection的创建,将决定权交给了CreateControlCollection()方法。如果我们需要定义一个控件,要求它不能管理子控件,就重写CreateControlCollection()方法,返回EmptyControlCollection 对象:

protected override ControlCollection CreateControlCollection(){
    return new EmptyControlCollection(this);
}

现在再回过头来看HtmlControl 和HtmlContainerControl 类。根据前面的分析,我们要求HtmlContainerControl 继承HtmlControl 类,同时,HtmlContainerControl 应为枝节点,能够管理子控件;HtmlControl 则为叶节点,不支持子控件。通过引入ControlCollection 类和其子类EmptyControlCollection,以及Template Method模式后,这些类之间的关系与结构如下所示:

41215101917

HtmlContainerControl 继承了HtmlControl 类,这两个类都重写了自己父类的protected方法CreateControlCollection()。HtmlControl 类,该方法返回EmptyControlCollection 对象,使其成为了不包含子控件的叶节点;HtmlContainerControl 类中,该方法则返回ControlCollection对象,从而被赋予了管理子控件的能力,成为了枝节点:

public abstract class HtmlControl : Control, IAttributeAccessor{
    // Methods
    protected override ControlCollection CreateControlCollection(){
        return new EmptyControlCollection(this);
    }
}
public abstract class HtmlContainerControl : HtmlControl{
    // Methods
    protected override ControlCollection CreateControlCollection(){
        return new ControlCollection(this);
    }
}

HtmlControl 和HtmlContainerControl 类均为抽象类。要定义它们的子类,如果不重写其父类的CreateControlCollection()方法,那么它们的Controls 属性,就与父类完全一致。例如HtmlImage 控件继承自HtmlControl 类,该控件不能添加子控件;而HtmlForm 控件则继承自HtmlContainerControl类,显然,HtmlForm控件是支持添加子控件的操作的。

.Net 的控件设计采用Composite 模式的“复合方式”,较好地将控件的透明性与安全性结合起来,它的特点是:

  1. 在统一接口中消除了Add()、Remove()等子控件的管理方法,而由ControlCollection类实现,同时通过EmptyControlCollection类保障了控件进一步的安全;
  2. 控件能否管理子控件,不由继承的层次决定;而是通过重写CreateControlCollection()方法,由Controls属性的真正类型来决定。

如此一来,要定义自己的控件就更加容易。我们可以任意地扩展自己的控件类。不管继承自Control,还是HtmlControl 或HtmlContainerControl,都可以轻松地定义出具有枝节点或叶节点属性的新控件。如果有新的需求要求改变管理子控件的方式,我们还可以定义继承自ControlCollection 的类,并在控件类的方法CreateControlCollection()中创建并返回它的实例。

本文系作者 问说网 授权问说网发表,并经问说网编辑,转载请注明出处和 本文链接

问说网手机版

躺着 站着 跪着轻松访问

更多 热门话题

APP界面

关注 APP界面

文章 41508 · 浏览 1037

APP欣赏

关注 APP欣赏

文章 41431 · 浏览 938

APP手机界面

关注 APP手机界面

文章 41421 · 浏览 949

图片素材

关注 图片素材

文章 29463 · 浏览 617

高清图片

关注 高清图片

文章 26225 · 浏览 721

发布评论

更多 这篇文章被以下标签收录

顶部 反馈 评论 底部

意见反馈

感谢您对问说网的支持,提出您在使用过程中遇到的问题或宝贵建议,您的反馈对我们产品的完善有很大帮助。

您的反馈我们已收到!

感谢您提供的宝贵意见,我们会在1-2个工作日,通过您留下的联系方式将处理结果反馈给您!