深入分析.Net中的Composite模式

问说网 · 发布于 2014-12-15 · 字数10611 · 浏览 1187 · 评论 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-05-23如何在Photoshop中制作火焰字体效果
  • 2016-03-09Flowplayer基于Video和Flash的网页视频播放器
  • 2017-01-19用JavaScript实现给出的盒子的序列是否可连为一矩型
  • 2016-02-27Bootstrap Tour基于jQuery步骤流程引导插件
  • 2016-12-02FlyJSONP轻量级的跨域AJAX请求插件
  • 2016-05-11CoffeeScript CSS将脚本化样式变成CSS代码
  • 2016-12-02Response JS创建高性能的支持移动设备的网站
  • 2016-05-16色彩潮流抢先看!2015年网页设计配色新趋势
  • 发布评论

    为您推荐

    分享6款基于CSS和SVG的Loader加载动画特效

    如果你想给网站添加一些加载动画,如果使用Gif总觉得不太方便,无法自定义颜色和效果,不过如果使用CSS和SVG,那么我们就可以自定义任何我们想要的效果,接下来分…

    • 分享6款基于CSS和SVG的Loader加载动画特效
    • 分享6款基于CSS和SVG的Loader加载动画特效
    • 分享6款基于CSS和SVG的Loader加载动画特效
    • 分享6款基于CSS和SVG的Loader加载动画特效
    问说网 · 发布于 2015-10-09 · 浏览 1822 · 评论 0
    用JavaScript实现给出的盒子的序列是否可连为一矩型
    AustenJuliet · 发布于 2017-01-19

    用JavaScript实现给出的盒子的序列是否可连为一矩型

    by cpglkg <script>var arr=[5,10,15];function rect(arr,width){ arr.sort(fun…

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

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

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

    问说网 · 发布于 2016-03-17

    一个非必现问题的定位和反思

    在启动监测的时候需要填充userNum和userIns信息来通知底层启动监测的用户。但停止监测需求就是停止所有用户,只需要发送停止监测消息给底层,底层会停止所有…

    在CSS中使用LESS实现更多功能
    问说网 · 发布于 2016-03-23

    在CSS中使用LESS实现更多功能

    响应式设计是经济适用的可管理移动 Web 设计中的一项公认的关键技术。它在针对打印等可访问性和其他演示模式的设计中也很有用。由于为应对媒体查询而应用的多种样式处…

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

    Response JS创建高性能的支持移动设备的网站

    Response JS 是一个轻量级的 jQuery 插件,用来创建高性能的支持移动设备的网站。它提供了一套语法用来根据不同的设备环境动态替换HTML代码。例如…

    设计朋友有福了!教你快速打造炫彩的逆光场景

    朋友们!今天我们一起来学习如何快速打造炫彩光效场景,我们在平时经常会看到一些光效处理非常棒的作品。看后我们有时会反问自己:”这些看起来很牛逼的光是怎么打上去的呢…

    • 设计朋友有福了!教你快速打造炫彩的逆光场景
    • 设计朋友有福了!教你快速打造炫彩的逆光场景
    • 设计朋友有福了!教你快速打造炫彩的逆光场景
    • 设计朋友有福了!教你快速打造炫彩的逆光场景
    静人静心 · 发布于 2016-05-16 · 浏览 983 · 评论 0

    有帮助!值得一看!网页设计中的“点、线、面”

    今天要和设计朋友们探讨的内容——网页设计中的“点、线、面”——平面构成在网页设计中的运用。我们首先要明确:一个网页作品的基础并不是那些不理想的素材,也不是那些斑…

    • 有帮助!值得一看!网页设计中的“点、线、面”
    • 有帮助!值得一看!网页设计中的“点、线、面”
    • 有帮助!值得一看!网页设计中的“点、线、面”
    • 有帮助!值得一看!网页设计中的“点、线、面”
    傾城紅顔醉 · 发布于 2016-05-16 · 浏览 1042 · 评论 0

    问说网手机版

    躺着 站着 跪着轻松访问

    更多详情 关于作者

    问说网

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

    13130 文章
    495 评论
    2019 人气

    更多 热门话题

    APP界面

    关注 APP界面

    文章 41506 · 浏览 2474

    APP欣赏

    关注 APP欣赏

    文章 41427 · 浏览 2327

    APP手机界面

    关注 APP手机界面

    文章 41417 · 浏览 2376

    图片素材

    关注 图片素材

    文章 29463 · 浏览 1864

    高清图片

    关注 高清图片

    文章 26530 · 浏览 2032

    更多 推荐作者

    关注 秋天的孤寂

    文章 99 · 评论 0

    关注 怎麽继续

    文章 90 · 评论 2

    关注 溫柔的溫柔

    文章 91 · 评论 0

    关注 莪很迷茫

    文章 97 · 评论 0

    关注 流浪的脚步

    文章 86 · 评论 0

    关注 籹孒不认输

    文章 94 · 评论 0

    
    顶部 反馈 评论 底部

    意见反馈

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

    您的反馈我们已收到!

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