热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

.Net开发笔记(十)“容器组件服务”模型

我前面一篇博客讲了自定义窗体设计器,其实功能太简单,主要想阐述的是底层原理(虽然我不保证VSI

我前面一篇博客讲了自定义窗体设计器,其实功能太简单,主要想阐述的是底层原理(虽然我不保证VS IDE设计器确实是那样去实现的)。编程讲究的是刨根问底,刨到祖坟最好,这篇或者可能以后几篇博客我想说一下VS IDE中的窗体设计器,虽说不能面面俱到,但也能让大家知道个大概。初学者可能阅读起来有些困难。

其实回头一看,我之前的好几篇博客倒是跟窗体设计器有些关系,当时写的时候也没有想到说为了照顾以后要说的内容,算是凑巧,这其中包括系列(九)、系列(八)、系列(七)。我总结了一下,了解窗体设计器主要搞懂三个部分:

“容器-组件-服务”模型;

设计时(Design_Time)和运行时(Run_Time);

代码自动生成。

这篇主要讲第一个,也就是本文标题说的“容器-组件-服务”模型。下面才是正文:

“容器-组件-服务”模型平时很少用到,不过窗体设计器中就用到过,不知道诸位看官对“容器”、“组件”、“服务”的了解程度如何,今儿我都讲一遍:

容器:

我们平时经常碰到过所谓的容器,比如Panel控件,它可以存放Button控件,那么它就可以说是一个容器,再比如ArrayList可以存放int数据,那么它也可以说是一个容器(暂且称为物理容器吧),但是,今天讲到的“容器”跟这些都不一样,前两个可以说是物理容器,有很明显的“把一个东西放进另外一个东西内部”的意思,属于“物理包含”,而今儿咱们讨论的容器属于“逻辑包含”,也就是说不需要将元素放到它内部,元素与容器之间只需要存在某一个关系就行。见图1,


图1

如图,一个元素可以包含在一个物理容器中,但同时也可以包含在一个逻辑容器中(通过某一种关系关联),比如元素1,当然,也可以有元素不存在任何容器中,比如元素3。

在.net中将直接或间接实现了System.ComponentModel.IContainer接口的类称之为“容器”,它与它元素之间的关系为“逻辑包含”。另外,它的元素有严格的规定,必须是“组件”(.net中将直接或者间接实现了System.ComponentModel.IComponent接口的类称之为组件,详细参考后面讲到的组件)。IContainer接口的源码为:

View Code
1 public interface IContainer : IDisposable
2 {
3 // Methods
4 void Add(IComponent component);
5 void Add(IComponent component, string name);
6 void Remove(IComponent component);
7
8 // Properties
9 ComponentCollection Components { get; }
10 }

1 public interface IContainer : IDisposable
2 {
3 // Methods
4 void Add(IComponent component);
5 void Add(IComponent component, string name);
6 void Remove(IComponent component);
7
8 // Properties
9 ComponentCollection Components { get; }
10 }

可以看出,只要是容器,必须有“添加”和“删除”组件的功能,另外,只要是容器,都必须遵守“Dispose”模式(有关Dispose模式,在这里我就不再讲了,它是一种安全管理系统资源的方式)。初看起来,虽然这里讲到的容器跟其他所谓的容器好像没什么两样,重要的不同接下来会说到。

组件:

其实,系列(七)中我已经说到了组件的概念,组件其实就是一个类,一个特殊的类,再总结一遍:

将直接或者间接实现了System.ComponentModel.IComponent接口的类称之为“组件”;

组件是一个类,实现了IComponent接口的特殊的类;

组件一定是类,但类不一定是组件;

在使用类似VS IDE这样的开发工具时,如果一个类型是“组件”,那么它就有可能出现在ToolBox中,也就是说,组件支持编程可视化。(至于为什么,下一篇博客能讲到);

组件需要遵循“Dispose”模式,因为它实现了IComponent接口,而IComponent接口又实现了IDisposable接口。

以上是之前得出来的结论,今天,我们再来看一下System.ComponentModel.IComponent接口的源码,稍后又可以总结出几个结论:

View Code
1 public interface IComponent : IDisposable
2 {
3 // Events
4 event EventHandler Disposed;
5
6 // Properties
7 ISite Site { get; set; }
8 }

1 public interface IComponent : IDisposable
2 {
3 // Events
4 event EventHandler Disposed;
5
6 // Properties
7 ISite Site { get; set; }
8 }

如你所见,IComponent接口确实实现了IDisposable接口,所以正如系列(七)中总结的一样:组件需遵循“Dispose”模式,除此之外,就一个Disposed事件,这个很容易就知道,在组件dispose时候触发的事件,还有一个ISite接口成员,这个对我们来说比较陌生,看一下ISite接口的源码(System.ComponentModel.ISite):

View Code
1 public interface ISite : IServiceProvider
2 {
3 // Properties
4 IComponent Component { get; }
5 IContainer Container { get; }
6 bool DesignMode { get; }
7 string Name { get; set; }
8 }

1 public interface ISite : IServiceProvider
2 {
3 // Properties
4 IComponent Component { get; }
5 IContainer Container { get; }
6 bool DesignMode { get; }
7 string Name { get; set; }
8 }

我们先不考虑IServiceProvider接口和DesignMode属性(以后再说),通过Component属性和Container属性,我们就应该很容易的知道ISite的作用其实就是关联一个组件和一个容器,将一个组件和一个容器关联起来。

因为IComponent中就有这么一个ISite,再根据前面讲“容器”的时候说到,容器中只能存放组件,而且是通过某一种关系关联起来的,那么,我们就可以断言,他们之间的关联就是通过ISite维持的,也就是说,容器可以通过ISite与它内部的组件通信,反过来,组件也可以使用ISite与它所在的容器通信,因此,得出结论如下:

 容器中存放组件,容器和组件之间可以进行通讯,以ISite为桥梁;

 将1扩展一下,既然容器和组件通过ISite为桥梁能通信,那么同一个容器中的组件之间肯定也是可以通讯的(容器作为中转)。如图2:


图2

 既然IContainer实现了IDisposable接口,IComponent接口也实现了IDisposable接口,再加上容器中是存放组件的,因此,我们可以认为,容器还有更重要的一个功能,那就是统一负责它其中的组件的资源释放。当容器Dispose的时候,里面所有的组件都跟着Dispose。

ISite专业术语称之为“站点”,当一个组件(逻辑意义上)放到一个容器中时,组件的ISite就会被赋值(站点化),之后,组件就和容器关联上了。再补充一句,.net中已经为我们默认实现了System.ComponentModel.IComponent接口和System.ComponentModel.IContainer接口,分别为System.ComponentModel.Component和System.ComponentModel.Container,我需要说明的是,它们都只是默认实现,实现了最基础最简单的部分,比如Dispose方法,IContainer.Add()、IContainer.Remove以及接下来要讲到的GetService方法(这个在后面讲“服务”的时候会说到),如果你需要功能更加详细的更加强大的容器或者组件,请从Container类或者Component派生出的新的容器或者组件。我再送上一张图,贴近实际地解释一下“容器”和“组件”的关系,如图3:


图3

如图所示,Form是一个物理容器,存放着Label和TextBox控件,由于上图虚线框中,所有元素都属于“组件”(IComponent->Component->Control,具体原因请参考系列(七)),所以图中容器可以包含虚线框中的所有控件。稍微提前透漏一下,我们在winform中的Form1.Designer.cs文件中,经常看见一行:

View Code
1 private System.ComponentModel.IContainer compOnents= null;

1 private System.ComponentModel.IContainer compOnents= null;

和InitializeComponent方法中有一行:

View Code
1 compOnents= new System.ComponentModel.Container();

1 compOnents= new System.ComponentModel.Container();

components就是一个默认容器,一般用于存放窗体中的Timer、ImageList、ErroProvider等类似组件,注意,窗体上其他控件不在容器范围内。components主要负责本窗体内所有组件的资源释放,因此,你可以在窗体的Dispose(bool disposing)方法中看见:

components.Dispose();  //释放本窗体中所有组件资源

我们再来思考一个问题,容器中存放组件,那么,组件内部可不可以存在容器呢?也就是说,容器包含组件,组件中是否可以再存在容器成员?比如一个组件类似如下:

View Code
1 class MyComponent:Component
2 {
3 private Container _cOntainer= null;
4 public MyComponent()
5 {
6 _cOntainer= new Container();
7 System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
8 timer.Tick+=… //注册事件
9 timer.Start();
10 _container.Add(timer); //向容器中添加Timer组件
11 }
12 //其他代码
13 }

1 class MyComponent:Component
2 {
3 private Container _cOntainer= null;
4 public MyComponent()
5 {
6 _cOntainer= new Container();
7 System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
8 timer.Tick+=… //注册事件
9 timer.Start();
10 _container.Add(timer); //向容器中添加Timer组件
11 }
12 //其他代码
13 }

在组件内部,存在一个Container数据成员(当然可能存在很多个),然后向其中添加其他组件(比如Timer),设想一下,如果把这个动作循环下去,也就是说,容器包含组件,组件内部又有一个容器,这个容器又包含一些组件,各个组件内部又包含容器…如此循环下去,有点绕啊,不过这个可以看成“树形图”,如图4:


图4

如上图,包含层数只有4层,理论上可包含无数层,实质上,我们在编写一个组件的时候,如果内部需要用到更多的其他组件,为了更好的管理各个组件的资源,我们一般都会将这些组件放到一个容器中,再将这个容器方法父组件中,那么,父组件的代码就应该是这样子了(强烈建议诸位看官熟悉“Dispose”模式)

View Code
1 class MyComponent:Component
2 {
3 private Container _cOntainer= null;
4 public MyComponent()
5 {
6 _cOntainer= new Container();
7 System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
8 timer.Tick+=… //注册事件
9 timer.Start();
10 _container.Add(timer); //向容器中添加Timer组件
11 }
12 //其他代码
13 // ---- 以下是新加内容
14 protected override void Dispose(bool disposing)
15 {
16 If(disposing) //释放托管资源
17 {
18 _container.Dispose(); //容器会调用容器中组件的Dispose,依次向下
19 }
20 base.Dispose(disposing); //重要
21 }
22 }

1 class MyComponent:Component
2 {
3 private Container _cOntainer= null;
4 public MyComponent()
5 {
6 _cOntainer= new Container();
7 System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
8 timer.Tick+=… //注册事件
9 timer.Start();
10 _container.Add(timer); //向容器中添加Timer组件
11 }
12 //其他代码
13 // —- 以下是新加内容
14 protected override void Dispose(bool disposing)
15 {
16 If(disposing) //释放托管资源
17 {
18 _container.Dispose(); //容器会调用容器中组件的Dispose,依次向下
19 }
20 base.Dispose(disposing); //重要
21 }
22 }

因此,容器和组件嵌套多少层都无所谓,只要你记得在组件dispose的时候,负责内部容器的dispose就ok。

到目前为止,我们已经总结出“容器”和“组件”相结合的两大好处:

容器可以负责很多组件的资源释放;

容器可以协助容器内部各个组件之间的相互通讯。

我们已经了解了第1条,至于怎么实现组件之间相互通讯,首先要知道“服务”的概念,接下来“服务”登场。

服务:

 “服务”,就是“帮助”的意思,某人为你提供服务,就是某人为你提供帮助,今天要说的这个“服务”也是这个意思,组件跟容器通讯,或者组件与同一容器中其他组件通讯靠的就是“服务”,“服务”由容器默认提供,各个组件可以获取容器的服务,每个服务负责一项(或者几项紧密相关)的任务。说得直白一点就是,打个比方,你去镇政府咨询一些农村政策,政府部门会派相关的代表为你提供解答,那么这些代表就为你提供服务,每个代表负责一项事情,比如你咨询农业税相关事情,专门有负责农业税的代表为你提供帮助,如果你咨询贫困户名额的事情,专门有负责贫困户审核的代表为你提供帮助,那么这里,镇政府就是“容器”,你就是“组件”,代表就是“服务”,很明显,镇政府只是逻辑上包含你。将来哪一天,天朝公平公正原则大型实施,允许你(组件)委派代表(服务)去镇政府(容器)工作,也就是说,组件有时候也是可以向容器提供“自定义服务”的,按照自己规定的方式提供服务,当然,你(组件)不可能委派一个代表(服务)去镇政府(容器)当镇长或者书记吧,也就是说,容器中有的服务是不可替代的,因为有些服务是整个模式中的核心,必须由容器开发者默认提供,如果随便交给使用者(Coder)去替换掉,容器可能就乱掉,不能正常工作,就像书记镇长这样的职位,想必只能由上一级委派吧。有点乱,来一张图来说明一下问题,见图5


图5

如图,“我”这个组件可能有一些背景,可以向镇政府(容器)添加自定义服务。图中,既然你我他三个组件都能与容器通讯,那么,你我他三个组件相互之间定可以相互通讯了,容器的“中转”加上“服务”,足以让各个组件之间进行互相沟通。

接下来,我说一下“服务”的种类,这个没有官方分类,只是我自己归纳的:

主动型。组件从容器取得“服务”后,可以使用这个服务进行一些操作(对容器或者其他组件),体现为:

       Service s = GetService(serviceType); //组件从容器获取指定服务

       s.DoSomething(); // 主动操作

       s.Event += new EventHandler(…); //监听一些事件

       s.Property = “12345”; //给…赋值

       string str = s.Property; //取值

被动型。“被动型服务”一般属于“自定义服务”,由组件提供给容器,容器在需要的时候会使用该服务,体现为:

       MyService s = new MyService(); //新建自定义服务

       container.AddService(s); // 向容器添加自定义服务,供容器使用,有点类似注册事件

以上就是“容器-组件-服务”模式,想必说得有点乱,或者太抽象,几乎看不大懂,我做了一个demo,提供源码下载,模拟一个从外部接收数据,显示在主界面的程序,有三个接收模块,每个模块都是一个组件,同时主界面可以随时控制“开始”和“停止”,大概结构图如下,如图6


图6

如上图,显示窗体、接收模块1、2、3均为组件,显示窗体同时属于“控件”,将他们4个放进(逻辑地)一个容器(Container),由Container负责他们之间的通讯,如“接收模块—>数据—>显示窗体”、“显示窗体—>控制命令—>接收模块”。Demo虽小,完全的可以划分为5个部分,四个组件和一个容器。开发组件时,只要知道服务接口,每人(每小组)单独负责一个组件的开发,四个组件在开发过程中,完全没有任何耦合的地方。提供一张效果图,如图7:


图7

其中,可以自定义服务,来控制消息格式化。xp .net3.5运行通过,源码下载地址:http://files.cnblogs.com/xiaozhi_5638/Container_Component_Service.rar

为了方便,3个接收数据的组件我放在了一个项目中,其实应该都单独分开。另外,编译后请不要用鼠标将3个组件拖进窗体中,因为这样的话,设计器就会生成将组件添加到默认容器中(System.ComponentModel.IContainer components),最好手动写代码。

题后话:

写到这儿,其实我已经意识到好多地方说漏了,没办法,我觉得这个东西说起来确实太多太复杂,比如组件在什么时候跟容器关联的?也就是说什么时候组件的ISite被赋值?其实这个东西只能查看.net源码,System.ComponentModel.Container源码中有答案,就是在组件被加到容器中的那一刻,组件就被站点化,即Container.Add(IComponent component)和Container.Add(IComponent component,string name)这两个方法中,另外,没有被站点化(ISite没被赋值)的组件,永远获取不了服务(因为根本没人给它提供),至于组件是怎么获取服务的,诸位看官请查看System.ComponentModel.Component源码。最后,一个组件可以不存在于任何容器内,比如Button控件,它一般只存在于父控件之中。

个人觉得写代码还是讲究一个“深入浅出”,不能钻那深,结果越陷越纠结,最后只知道内部原理,而且还是懵懵懂懂的,跳出来,却已经不知道整个大概的流程。“深入”,比如说.net初学者可能感觉System.Windows.Forms.Timer中的Tick事件处理程序中为啥不需要解决跨线程访问控件?因为给人感觉不就是Timer内部新开辟了一个线程吗?其实你追其源码,刨其祖坟,会发现它不是通过线程实现的,而是通过Windows中WM_TIMER消息去实现的,而window消息处理一般都是在ui线程中,根本就没有跨线程。“浅出”,你比方说,我们现在在研究窗体设计器原理,到时候研究得差不多的时候,你得总结一下窗体设计器中的每一个宏观上的具体操作,内部又是怎么去实现的,要一一对应起来,这样你才不会搞糊涂。

最后希望本文对您有所帮助,O(∩_∩)O~,文中代码有些是在word中写进去的,恐怕有些错误。如果感觉本文对您有帮助,请点一下右下角的“推荐”或者给点建议也是可以滴,O(∩_∩)O~。



推荐阅读
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 在CentOS 7环境中安装配置Redis及使用Redis Desktop Manager连接时的注意事项与技巧
    在 CentOS 7 环境中安装和配置 Redis 时,需要注意一些关键步骤和最佳实践。本文详细介绍了从安装 Redis 到配置其基本参数的全过程,并提供了使用 Redis Desktop Manager 连接 Redis 服务器的技巧和注意事项。此外,还探讨了如何优化性能和确保数据安全,帮助用户在生产环境中高效地管理和使用 Redis。 ... [详细]
  • 在使用Eclipse进行调试时,如果遇到未解析的断点(unresolved breakpoint)并显示“未加载符号表,请使用‘file’命令加载目标文件以进行调试”的错误提示,这通常是因为调试器未能正确加载符号表。解决此问题的方法是通过GDB的`file`命令手动加载目标文件,以便调试器能够识别和解析断点。具体操作为在GDB命令行中输入 `(gdb) file `。这一步骤确保了调试环境能够正确访问和解析程序中的符号信息,从而实现有效的调试。 ... [详细]
  • 在Linux系统中避免安装MySQL的简易指南
    在Linux系统中避免安装MySQL的简易指南 ... [详细]
  • Parallels Desktop 10 是一款功能强大的虚拟化软件,专为 Mac 用户设计,使其能够无缝运行 Windows 应用程序。该软件不仅显著提升了图形应用的性能,还优化了演示效果。对于需要在 Mac 上高效运行 Windows 程序的用户来说,Parallels Desktop 10 是一个理想的选择。本文将介绍如何获取其激活码及免费下载渠道,帮助用户轻松激活并使用这一强大工具。 ... [详细]
  • 二分查找算法详解与应用分析:本文深入探讨了二分查找算法的实现细节及其在实际问题中的应用。通过定义 `binary_search` 函数,详细介绍了算法的逻辑流程,包括初始化上下界、循环条件以及中间值的计算方法。此外,还讨论了该算法的时间复杂度和空间复杂度,并提供了多个应用场景示例,帮助读者更好地理解和掌握这一高效查找技术。 ... [详细]
  • 在 Android 开发中,`android:exported` 属性用于控制组件(如 Activity、Service、BroadcastReceiver 和 ContentProvider)是否可以被其他应用组件访问或与其交互。若将此属性设为 `true`,则允许外部应用调用或与之交互;反之,若设为 `false`,则仅限于同一应用内的组件进行访问。这一属性对于确保应用的安全性和隐私保护至关重要。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • 服务器部署中的安全策略实践与优化
    服务器部署中的安全策略实践与优化 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 在Cisco IOS XR系统中,存在提供服务的服务器和使用这些服务的客户端。本文深入探讨了进程与线程状态转换机制,分析了其在系统性能优化中的关键作用,并提出了改进措施,以提高系统的响应速度和资源利用率。通过详细研究状态转换的各个环节,本文为开发人员和系统管理员提供了实用的指导,旨在提升整体系统效率和稳定性。 ... [详细]
  • 在优化Nginx与PHP的高效配置过程中,许多教程提供的配置方法存在诸多问题或不良实践。本文将深入探讨这些常见错误,并详细介绍如何正确配置Nginx和PHP,以实现更高的性能和稳定性。我们将从Nginx配置文件的基本指令入手,逐步解析每个关键参数的最优设置,帮助读者理解其背后的原理和实际应用效果。 ... [详细]
  • 本文深入解析了WCF Binding模型中的绑定元素,详细介绍了信道、信道管理器、信道监听器和信道工厂的概念与作用。从对象创建的角度来看,信道管理器负责信道的生成。具体而言,客户端的信道通过信道工厂进行实例化,而服务端则通过信道监听器来接收请求。文章还探讨了这些组件之间的交互机制及其在WCF通信中的重要性。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
author-avatar
花儿在绽放2502857073
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有