深入分析.Net中的Composite模式

问说网 · 发布于 2014-12-15 · 字数10611 · 浏览 1179 · 评论 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()中创建并返回它的实例。

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

相关文章

  • 2015-11-25showDialog基于jQuery的弹出窗口组件
  • 2017-01-19用JavaScript实现给出的盒子的序列是否可连为一矩型
  • 2016-03-07戏说实现简易而强大的游戏AI有限状态机FSM
  • 2016-05-17AI教程!教你绘制一个大气典雅的灯光背景
  • 2016-05-17手把手教你做霸气的PS特效标题字
  • 2016-06-30ESLint开源的JavaScript和JSX验证工具
  • 2016-07-06Java中面向对象的封装和继承以及多态
  • 2016-07-06Java异常处理
  • 发布评论

    为您推荐

    问说网 · 发布于 2015-11-07

    mmGrid基于jQuery的表格样式数据处理插件

    mmGrid 是 jQuery 实现对表格的样式库的插件。支持包括排序、Ajax、宽度锁定、列隐藏、锁定、nowrap、多选、选择和分页等功能,还可以配合Boo…

    问说网 · 发布于 2016-02-26

    Jade基于Node.js功能丰富的模版引擎

    Jade 是一个高性能的模板引擎,它深受 Haml 影响,使用 JavaScript 前端开发脚本实现的,并且可以供 Node 使用,Jade的语法简单易懂,能…

    问说网 · 发布于 2016-12-02

    FlyJSONP轻量级的跨域AJAX请求插件

    FlyJSONP是一个JavaScript库,用于实现跨域GET和POST请求服务,支持JSONP,并取得一个JSON格式的数据响应,这个Library具有易于…

    autoComplete.js移动端自动补全jQuery插件
    问说网 · 发布于 2016-06-27

    autoComplete.js移动端自动补全jQuery插件

    AutoComplete 是一个 jQuery Mobile 的插件,用于实现在 jQuery Mobile 中的搜索框中提供自动完成的功能。允许开发者添加到您…

    SweetTooltip基于jQuery和CSS3链接工具提示插件
    问说网 · 发布于 2016-07-01

    SweetTooltip基于jQuery和CSS3链接工具提示插件

    SweetTooltip 工具提示是一个基于jQuery和CSS3的工具提示,它不仅仅是一个风格,而是有七种风格。这种无背景图风格的工具提示制作使用线性梯度,阴…

    Vincent · 发布于 2016-07-06

    类和对象

    定义 1. 类是用于描述同一类形的对象的一个抽象的概念,类中定义了这一类对象所因具有的静态属性(attibute)和动态行为(method) 2. 类可以看成一…

    详细讲解JAVA中的IO流
    Vincent · 发布于 2016-07-06

    详细讲解JAVA中的IO流

    包 java.io   文件路径 1. java.io.File是文件和目录路径名的抽象表示形式 2. 抽象路径名中的第一个名称是目录名,抽象路径名中…

    为你的博客添加注册Google广告支持

    首先,通过 google adsense 注册链接进行注册。注册过程比较简单,通过鼠标点击和一些个人信息输入就可以完成。具体几个关键步骤可以看下面的截图。

    • 为你的博客添加注册Google广告支持
    • 为你的博客添加注册Google广告支持
    • 为你的博客添加注册Google广告支持
    • 为你的博客添加注册Google广告支持
    问说网 · 发布于 2016-07-15 · 浏览 1013 · 评论 0

    问说网手机版

    躺着 站着 跪着轻松访问

    更多详情 关于作者

    问说网

    问说网分享与设计有关的文章素材界面和作品,提供设计教程、素材分享、界面欣赏、编程设计、设计书籍、设计师导航等内容,你可以在这里阅读、学习、分享、交流。

    13086 文章
    493 评论
    1865 人气

    更多 热门话题

    APP界面

    关注 APP界面

    文章 41506 · 浏览 1575

    APP欣赏

    关注 APP欣赏

    文章 41427 · 浏览 1506

    APP手机界面

    关注 APP手机界面

    文章 41417 · 浏览 1522

    图片素材

    关注 图片素材

    文章 29463 · 浏览 1026

    高清图片

    关注 高清图片

    文章 26530 · 浏览 1217

    更多 推荐作者

    关注 秋天的孤寂

    文章 99 · 评论 0

    关注 怎麽继续

    文章 90 · 评论 2

    关注 倾听寂寞

    文章 83 · 评论 0

    关注 溫柔的溫柔

    文章 91 · 评论 0

    关注 走了留下什么

    文章 110 · 评论 0

    关注 莪很迷茫

    文章 97 · 评论 0

    顶部 反馈 评论 底部

    意见反馈

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

    您的反馈我们已收到!

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