文档章节

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

IT周见智
 IT周见智
发布于 2015/06/05 17:18
字数 4281
阅读 5
收藏 0
点赞 0
评论 0

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

可以看出,只要是容器,必须有“添加”和“删除”组件的功能,另外,只要是容器,都必须遵守“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 }

如你所见,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 }

我们先不考虑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;

和InitializeComponent方法中有一行:

View Code
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 }

在组件内部,存在一个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 }

因此,容器和组件嵌套多少层都无所谓,只要你记得在组件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~。

© 著作权归作者所有

共有 人打赏支持
IT周见智

IT周见智

粉丝 10
博文 61
码字总数 185891
作品 0
西青
Docker学习笔记一 简介

写在前面 工作原因需要接触很火的Docker,所以开始这个系列的笔记。初步制定的学习教材是《第一本Docker书》,辅以一些网上材料,所以笔记先以此书为主要内容。 1.0 容器的简介 容器与管理程...

一万
2016/07/07
47
0
《第一本Docker》笔记(一)之Docker简介

一、Docker简介 1.Docker是一个能够把开发的应用程序自动部署到容器的开源引擎。Docker的特别之处:Docker在虚拟化的容器执行环境中增加了一个应用程序部署引擎。该引擎的目标就是提供一个轻...

余二五
2017/11/23
0
0
《J2EE Tutorial中文版》读书笔记(1)

我看的是第一版,针对j2ee1.3的,新版的图书馆也有,但太厚了,看起来有点怕,还是从这本小书开始吧。 6-25-7.JPG 序言部分介绍了书中的主要内容: Enterprise JavaBean  Java Servlet ...

嗯哼9925
2017/12/26
0
0
IoC 容器--PicoContainer

PicoContainer是一个“微核心 ”(micro-kernel)的容器。它利用了Inversion of Control模式和Template Method模式,提供面向组件的开发、运行环境。PicoContainer是“极小”的容器,只提供了...

匿名
2008/09/17
3.9K
1
基于容器的分布式系统的设计模式

1、 介绍 在20世纪80年代末和20世纪90年代初,面向对象编程彻底改革了软件开发方法,普及了将应用程序作为模块化组件的创建方法。随着基于容器化软件组件的微服务架构的逐渐普及,现在在分布...

Caicloud
2016/07/18
23
0
通过Docker的LinuxKit,Linux容器终于可以运行在Windows系统上了(外文翻译)

本文翻译至Finally, Linux Containers Could Run on Windows with Docker’s LinuxKit image.png 自从两年前推出第一个测试版,有一件事Windows容器一直没有做,就是基于Windows环境,在Lin...

我在睡觉
2017/06/19
0
0
《轻量级微服务架构》读书笔记

微服务架构要求: 根据业务模块划分服务种类 每个服务可独立部署且互相隔离 通过轻量级API调用服务 服务需保证良好的高可用 微服务技术选型: 使用开发服务 使用作为服务网关反向代理调用服务...

蛙牛
2016/12/05
1K
8
C++ Web应用服务器中间件 MYCP 全面介绍

1、MYCP的概念 MYCP是一种利用C++编写,来简化企业解决方案的开发、部署和管理相关的复杂问题的体系结构。MYCP技术的基础就是核心C++语言平台,所以MYCP不仅拥有,例如“编写一次、随处运行”...

红薯
2011/01/05
1K
0
【J2EE】总结

一、组件-容器模型: J2EE是一个基于组件-容器模型的系统平台,其核心概念是容器,容器是指为特定组件提供服务的一个标准化的运行时环境,JAVA虚拟机就是一个典型的容器。组件是一个可以部署...

j15533415886
01/14
0
0
Mini 容器学习笔记1——环境搭建(基础篇)

一. 环境下载 到Mini 容器的官方网站下载NLite框架的二进制文件,下载并解压后就可以了。 我们使用NLite框架需要用到下面的文件: NLite.dll(必要) 二. 建立NLite应用程序 新建一个控制台应用...

netcasewqs
2011/08/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

StringUtils类中isEmpty与isBlank的区别

org.apache.commons.lang.StringUtils类提供了String的常用操作,最为常用的判空有如下两种isEmpty(String str)和isBlank(String str)。 StringUtils.isEmpty(String str) 判断某字符串是否为...

说回答
12分钟前
0
0
react native使用redux快速上手

先看个简单demo //app.jsimport React, {Component} from 'react';import {StyleSheet, Button, View} from 'react-native';import TestView from './src/testView'export default......

燕归南
14分钟前
0
0
页面输出JSON格式数据

package com.sysware.utils;import java.io.IOException;import javax.servlet.ServletResponse;import org.apache.log4j.Logger;import com.sysware.SyswareConstant;pub......

AK灬
35分钟前
0
0
springCloud-2.搭建Eureka Client的使用

1.使用IDEA,Spring Initializr创建 2.填写项目资料 3.选择spring boot版本,插件选择Cloud Discovery→Eureka Discovery 4.选择保存地址 5.修改application.yml eureka: client: s...

贺小康
38分钟前
0
0
CenOS 6.5 RPM 安装 elasticsearch 6.3.1

下载 wget --no-check-certificate https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.1.rpm...

阿白
41分钟前
0
0
1.4 创建虚拟机&1.5 安装CentOS7&1.6 配置ip(上)&1.7 配置ip(下)

1.4 创建虚拟机 知识点 虚拟机网络链接模式 桥连 直接将虚拟网卡桥接到一个物理网卡上面。需要手工为虚拟系统配置IP地址、子网掩码,而且还要和宿主机器处于同一网段,这样虚拟系统才能和宿主...

小丑鱼00
47分钟前
0
0
TrustAsia(亚洲诚信)助力看雪2018安全开发者峰会

2018年7月21日,看雪2018安全开发者峰会在北京国家会议中心圆满落下帷幕。拥有18年悠久历史的老牌安全技术社区——看雪学院联手国内最大开发者社区CSDN,汇聚业内顶尖的安全开发者和技术专家...

亚洲诚信
49分钟前
0
0
Spring注解介绍

@Resource、@AutoWired、@Qualifier 都用来注入对象。其中@Resource可以以 name 或 type 方式注入,@AutoWired只能以 type 方式注入,@Qualifier 只能以 name 方式注入。 但它们有一些细微区...

lqlm
59分钟前
0
0
32位汇编在64位Ubuntu上的汇编和连接

本教程使用的操作系统是Ubuntu Linux 18.04 LTS版本,汇编器是GNU AS(简称as),连接器是GNU LD(简称ld)。 以下是一段用于检测CPU品牌的汇编小程序(cpuid2.s): .section .dataoutput...

ryanliue
今天
0
0
CentOS系统启动报错Failed to mount /sysroot解决方法

xfs_repair -v -L /dev/dm-0

Mr_Tea伯奕
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部