组件化开发概述 |
软件系统开发面临的困境与突破尝试
组件设计的基本原则
组件化软件开发的“三板斧”(套路):
组件化软件的开发方式:
- 软件代码难以读懂和维护,业务逻辑分散在多个代码模块中
- 软件系统面对着的需求不断变化,再次开发一个新的软件成本高
- 因此,我们应该“重用”,而不是每次都“从头开始”
- 大量的软件系统中都存在着功能重复的情况,因而开发可重用的软件组件是可行的。
组件(Component):
- 是指可以用于重用、开发和部署的软件模块。
组件化开发(CBD:Component-based Development)
- 以可重用的组件为基础“装配出”解决需求软件的过程。
组件实现的功能:
- 组件的功能是通过其接口(是宏观意义的接口,指向外界提供的功能)来表达;
- 组件只是黑盒,外界只能通过接口来访问,外界只关系通过接口可以实现的功能,并不关心组件的技术细节;
- 组件具有独立性和可组合性。
- 因此,创建可以当“积木”组合的软件组件是组件化开发的核心工作之一。
组件设计的基本原则
- 组件设计首先是组件接口的设计,这个组件该干什么事情,每个组件都应有一个明确的职责,只做好一件事,体现为一个明确的组件接口;
- 组件的具体实现技术。
- 组件需按职责进行设计!!
- 为实现复用而设计
-
- 允许围绕该组件的应用自由改变而无需改变组件自身,除非组件自己的功能需求改变,即只会改变其内部的技术实现,而不会改变其对外接口。
- 仔细考虑组件功能集合的大小
-
- 组件的粒度是用于衡量组件所提供的功能集的大小、所封装的代码量多少等与“规模”相关的特性。
- 小组件(如一个只允许输入邮编的文本框);
- 大组件:一个预先构造的应用软件包(如一个用于生成复杂报表的组件,用于文档处理的office软件包)。
- 粒度大小与灵活性通常成反比,与易用性成正比!
组件化软件开发的“三板斧”(套路):
- 重用已有组件;
- 开发部分新组件;
- 组合新旧组件搭建出新系统。
组件化软件的开发方式:
组件化开发全过程 |
实例:两数相加程序:从“天下一统”到“借鸡生蛋”
- 原始版本
- 原始需求:输入框接收用户输入,“=”按钮计算结果。
-
- “=”按钮事件:
private
void
btnAdd_Click(
object
sender,
EventArgs
e)
{
try
{
int
num1 =
Convert
.ToInt32(txtNumber1.Text);
int
num2 =
Convert
.ToInt32(txtNumber2.Text);
lblResult.Text = (num1+num2).ToString();
}
catch
(
Exception
ex)
{
lblResult.Text = ex.Message;
}
}
- 需求二:在输入数字时,能直接看到结果而无需点击“=”按钮。
-
- 为输入框添加 TextChanged 事件,并将原本按钮的事件复制到两个输入框的 TextChanged 事件中:
private
void
txtNumber1_TextChanged(
object
sender,
EventArgs
e)
{
try
{
int
num1 =
Convert
.ToInt32(txtNumber1.Text);
int
num2 =
Convert
.ToInt32(txtNumber2.Text);
lblResult.Text = (num1 + num2).ToString();
}
catch
(
Exception
ex)
{
lblResult.Text = ex.Message;
}
}
- 需求三:将雷同的用于相加的方法提取用于复用。
-
- 选中代码,右键菜单>重构>提取方法Add()
- 需求四:将窗口的数据输入与数据处理分离。
-
- 新增数据处理类MathOpt,并把权限改成public;
- 增加纯粹用于数据处理的方法Add;
- 在原来的类frmAdd中,实例化MathOpt类:private MathOpt calculator = new MathOpt();
- 在Add方法中将 lblResult.Text = (num1 + num2).ToString(); 替换为 lblResult.Text=calculator.Add(num1, num2).ToString(); ;
- 好处:Add方法中只与界面相关,并不涉及数据的处理
- 需求五:(迈向组件化开发的重要一步)
-
- 将数据处理的代码做成程序集
- 添加新类库MathLibrary:
- 将MathOpt类复制到MathLibrary类库中,并修改命名空间使其匹配,并将多余的类删除;
- 编译;
- 在需要该类库的项目添加MathLibrary的引用。
- 将数据处理的代码做成程序集
小结:
- 不要不功能代码直接写到事件响应代码中,而是用调用函数的方式(减少代码重复);
- 尽可能少地编写重复代码,将重复的代码编写成组件以复用;
- 事件界面代码与功能代码分离;
- 尽量复用二进制形式的程序集,而不是复用源代码,即不该直接拷贝代码,而是引用已经编译好的类库。
.NET组件化开发技术 |
程序集(Assembly)
组件设计的“对外接口最小化”原则
组件间的“依赖性”
组件的版本
- .NET Framework中基本的软件模块,它可以包容不限数目的类型,其常见的载体为一个或多个DLL文件,也可以是一个可独立执行的EXE文件。
- 程序集和命名空间是多对多的关系。一个命名空间可以包含多个程序集,一个程序集也可以分布在多个命名空间中。
- 一个项目如果需要使用特定程序集中的类型,需要添加对此程序集的“引用(Reference)”。
程序集的内部结构
- 程序集大致分为两种:
-
- 单文件程序集(占绝大多数)
- 多文件程序集,多用于国际化程序设计时,根据实际情况组合程序集实现国际化多语言。
- 程序集主要组成部分:元数据、IL代码、资源。
-
- IL代码:有.NET编译器编译生成,将编程语言代码转换为IL代码,是程序集实现功能的主体。
- 程序集元数据:程序集的公用信息。
- 类型数据:该程序集定义了哪些类、接口、方法、属性、字段、事件等信息
- 使用ildsam可以查看程序集元数据。
组件设计的“对外接口最小化”原则
- 一个程序集中,可以放置任意多个类,但仅有需要为程序集外界使用的类,才设置为“公有(public)”,以实现程序集的信息封装。
- 而公有类,需要使公有成员“越少越好”,这样的类才易于使用和维护。
组件间的“依赖性”
- 含义:包容于某组件内部的类,需要调用另一个组件中的类以实现功能。
- 这是由于类之间的依赖导致的。
- 在设计各组件接口时,尽量避免和减少组件之间的耦合性。
解决方案示例:
- UseDLL依赖于MyDLL和MyDLL2,UseDLLC、MyDLL和MyDLL2又依赖于.NET的基础组件System。
- 在实际开发中,两个程序集之间的依赖关系应该要是“单向”的,不能是双向的,这是系统架构的关键点。
- 具有相同的抽象级别的对个组件组成一个“层”,各个层之间的依赖关系也应是“单向”的。
- 把多个组件分层,组件间建立“单向”的依赖关系,这种方式就称为“组件化的多层架构”,是当前软件系统最常见、最主流的软件架构。
避免出现组件循环依赖的情况,这样在编译时就会出错
- 要消除这种循环依赖,应在设计时就避免;
- 可以使用在组件间移动类的方式,消除组件间的循环依赖
组件的版本
- 一个大型系统,需要注意其版本要一致。
- 这里的版本是指:
-
- 组件运行时要求的软件系统平台的版本;
- 组件自身的版本。