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

文章13116 · 评论469

深入分析.Net中的Composite模式

字数10611 · 浏览 1147 · 评论 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界面

文章 41517

APP欣赏

APP欣赏

文章 41450

APP手机界面

APP手机界面

文章 41440

图片素材

图片素材

文章 29463

高清图片

高清图片

文章 26225

更多 推荐作者

问说网

文章 13116

GallupDarnell

文章 117

GibbonRoy

文章 119

StuartKelly

文章 115

YeatesGeoffrey

文章 113

发布评论

顶部 反馈 评论 底部

意见反馈

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

您的反馈我们已收到!

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