如何理解模块、组件和对象

模块、组件和对象这三个名词,是软件开发中非常常见的说法。在很多软件平台、库、框架中,都使用这三个名词作为描述其复杂结构的单元结构。模块、组件、对象三者虽然有相似的含义,但是,也有非常大的差别,本文探究这三个概念的异同目的,并非仅仅是规范这些用语,而是希望能对复杂软件的解构单位,作一次较完整的思考。

孙子兵法云:“制众如治寡,分而治之”。意思是:处理数量庞大而复杂的事物,其实和处理简单、单一的事物一样简单,关键是要把处理的目标分解开。软件系统的发展过程里,也对分解有很多不同的尝试。

早期结构化编程流行的阶段,软件是一个被层层分解的函数,每个函数都被划分成更多,具备平行含义的子函数。整个程序的结构就好像一本分类学书籍,由一个主题目main(),划分成非常多的子标题,子标题下面又分为更小的标题,形成一个树状的结构。

如果我们需要对这样一个程序的某部分代码重用,也就是说抽取其中一部分代码,放到其他程序里面,那么,一般来说都要以“函数”为包装形式,否则,拷贝整段代码的缺点是显而易见的。函数用作来重用代码,天然的有其接口形式:参数与返回值(或者是输入参数与输出参数)。因此,一个函数,就能成为一个封闭的代码块,具备固定的输入和输出接口,我们称之为模块。

然而,当我们在编写“函数模块”的时候,往往会发现,针对同一块数据,往往需要多个函数来一起操作。比如我们操作文件,针对特定的一个文件,我们需要打开、添加、删除、关闭这个文件。如果每个操作都是一个函数,那么我们的输出参数往往都需要一个代表文件的指针;而这个指针,又往往是其他函数的输入参数。——这种做法,实际上是破坏了模块的重用性,因为针对“模块”这个概念,我们设想的是,能简单使用一些输入参数,就能获得操作结果,而不希望还要关心输入参数的状态,以及维护输出参数的状态。由于我们编写的大多数商业系统中,都是针对一些数据进行操作,而不仅仅是进行某些数据的运算,所以数据状态反而成了程序的核心,各种所谓的“模块”,都是为了修改、操作某些特定数据的工具而已。

也许有人会说,这样的工具模块不也是挺好的吗?当然,从代码重用的角度来说,模块和工具都可以很方便的被重用在需要的地方。但是,我们希望被重用的代码,是能够比较简单的去使用的,如果使用“工具型函数”,需要用户去小心的维护一个复杂的数据模型,那么就达不到“简单”的重用代码的目的了。举个例子,如果我们有10个工具函数,都是操作同一个数据指针的,而这些函数的调用还是有一定的调用顺序或其他逻辑约束的,那么我们在使用的时候,就要认字仔细的查看例程,彻底搞明白这10个函数的关系,然后按设计者的思路小心翼翼的去使用。否则,如果只是看了下函数手册就用起来,很可能就会碰到诸如“未定义”的结果这类奇怪的错误。

显然,我们对于“模块”的追求,无法简单的用函数这个工具来实现,因此人们想到,我们能不能把数据和函数组合起来,这样使用的时候,就不需要在使用的时候去维护复杂的状态。——因此诞生了面向对象。这里的“对象”,指的就是把数据,和操作这些数据函数,根据内在逻辑封装起来的单位。由于有了这种封装,一个对象除了代表业务数据状态以外,还附带了一系列的“方法”,这些“方法函数”是可以随时随意的调用,而用来操作对象的。

虽然这些“方法函数”同样还是可以设置“输入参数”和“输出参数”,但是我们完全可以把方法函数所依附的对象(this对象),作为自由操作的输入或输出参数。我们在编写方法函数的时候,就可以有意识的去管理所依赖的数据状态——this对象,这样任何一个方法函数,都可以明确而唯一的操作this的内容,而无需用户去操心。

举个例子,如果我们用面向对象的类库去操作文件,我们只需要创建一个File对象,就可以随意的删除、更新、修改这个对象的内容。如果这个对象对应的文件没有打开,或者不存在,那么File对象本身会有一个状态值做记录,那些删除、更新的方法就可以先判断一下这个状态值,从而返回“错误”提示。这样就可以避免用户去按照某些逻辑约束使用各个方法,极大的降低了对象作为“模块”的学习难度。

对象这个概念,对于那些希望把数据+操作构建成模块的情况,是非常合适的。但是,他也有一些缺点。其中有一个非常典型,就是对象模型是一个编程语言的概念,本质上一个对象只是内存中的一堆数据。然而我们要使用的模块,往往不止是内存中的一堆数字就能解决问题的。比如我们需要管理数据库中的一个表,或者操作一个GUI的按钮,又或者控制一个游戏里面的动画角色……这些都不止用内存中的“属性”能满足。

这个时候,人们依托IDE工具,把许多需要复用的复杂数据,和对象模型关联起来,封装成一个个可以根据预先约束的用法去使用的模块,这就是组件了。组件一般会比对象的约束要多,因为每一类组件,都有明确的使用接口,以便能“组合”到某个框架里面。比如JavaBean规范规定,所有这类的组件,必须要以getter/setter的形式对外提供属性的读写。又比如Flex规范中,组件甚至可以仅仅用XML来描述,而不必要是某种程序代码。

[delphi中的数据库、表空间可以用图形化修改属性]

总体来说,所谓组件,是在某套使用规范下,特别构建的软件模块。这种模块很多依托对象模型,有所谓“属性”和“方法”。但是这些属性和方法,为了能提供更直观方便的使用接口,一般会有所约束。一旦满足这些约束,开发者重用这种模块的时候,甚至是不需要用编程语言来调用这些“属性”、“方法”。所以组件和对象的差别,往往是在于其约束方面。很多组件都要求对象从某个基类派生,或者要有一个主动注册和校验的程序,才能从对象编程组件。但是反过来一般组件都提供某种编程语言下的对象模型,让用户可以对其编写代码。

因此,我们可以看出,模块、对象、组件之间是有一定的关系,但并不完全等同。一般来说,我们喜欢把任何可重用的代码都成为模块,我们希望模块是简单的、仅仅通过输入输出就能控制的重用代码,所以其含义是最广泛而通用的。可惜现实中能很简单使用的模块并不多,为了能处理复杂的问题,人们开发了很多编程的工具,其中一些为了解决特定问题,比如数据库、GUI、游戏,都强化了一些模块的使用方法,让可重用的代码,能更简单的投入使用,而这些易用性上的好处,都需要付出遵守一定的规范的代价。

这些能在特定问题解决框架下运行的模块,就成为了组件。虽然模块和组件本身都不需要采用面向对象的模型,但是面向对象作为编程上的一个重要概念,能帮助使用者理解和操作模块或者组件,并且因为其封装管理数据状态的特征,能降低编程上的复杂程度,更容易对业务领域建模,所以很多模块和组件,背后都采用了面向对象的概念来打造其编程接口和表现形式。

当我们自己希望开发一个框架的时候,我们往往会思考,如何让用户更方便的使用这个框架啊,如何提高框架的扩展能力。如果我们的框架是比较专门的解决某个方面的问题,那么我们把扩展接口设计成组件,可能是一个好主意,因为这样能提供更友好的用户使用界面,包括图形化或配置化的用法;但是这种组件接口也带来了一定的复杂性,在用户需要扩展自己的功能时,组件和框架的接口直接限制了框架、组件的功能外延。

所以,一般通用性比较高的框架或平台,其模块形式往往是类库,比如JDK或者C#的标准库,这种仅仅以对象为接口的模块,其编程灵活性是更加高的。然而,如果我们要处理的问题,更多的是数据计算,而不是数据状态的管理;或者我们要管理的数据模型高度抽象,比如Linux下以文件这个模型来管理所有的内存、外设,这样我们甚至可以不用对象模型,直接用函数就能解决问题。

不过一般来说,作为业务逻辑开发领域的程序员,要处理的数据模型往往不会太简单,有大量的显示、修改、逻辑判断的需求,这方面还是构建对象模型来的更容易建模些。

最后,可能会有疑问的是,函数能不能用来构建组件呢?实际上是可以的,比如我们在一些GUI编程中,如按钮的响应事件,就直接传入一个函数(VB);或者在多线程编程中,一个线程任务的接口往往就是一个函数。但是,由于函数这个模型比较简单,而且难以在空间上复制,必须和代码统一到一起,所以我们更倾向用对象来做组件,否则函数的约束也没有太多手段。

感谢大家的阅读,如觉得此文对你有那么一丁点的作用,麻烦动动手指转发或分享至朋友圈。如有不同意见,欢迎后台留言探讨。

原文发布于微信公众号 - 韩大(handa1740168)

原文发表时间:2016-03-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏狮乐园

高级 Angular 组件模式 (3b)

Kent C. Dodds的第四篇文章中的一个重要元素在上一篇文章中没有涉及,使用withToggle高阶组件(HoC, react中的常用模式)可以将<tog...

781
来自专栏小蠢驴iOS专题

iOS中纯代码创建的UI控件使用weak还是strong

1644
来自专栏前端知识分享

第217天:深入理解Angular双向数据绑定的原理

双向绑定是新的前端框架中频繁出现的一个新词汇,也是mvvm的核心原理。angularjs五条核心信念中的数据驱动,便是由双向绑定进行完成。

832
来自专栏JackeyGao的博客

使用Vue.js 和 semantic-ui 的一个简单TODO List

这是一个完全仿照官网案例的项目, 主要为了熟悉vue.js的基本用法, 不得不说这个案例能吸收到基本的vue.js 操作.

792
来自专栏Python

Flask路由报错:raise FormDataRoutingRedirect(request)

  raise FormDataRoutingRedirect(request) FormDataRoutingRedirect: A request was ...

3854
来自专栏技术博文

php性能监测模块XHProf

一,什么是XHProf XHProf是一个分层PHP性能分析工具。它报告函数级别的请求次数和各种指标,包括阻塞时间,CPU时间和内存使用情况。一个函数的开销,可...

3496
来自专栏移动应用测试

当uiautomator遇到xpath和ocr,畅快

Android 的 UI 测试中,经常要点击某个控件,google 给出了 uiautomator 工具可以方便的查看控件信息,但是写测试用例的时候,仍然经常遇...

1274
来自专栏pangguoming

黑盒测试和白盒测试的区别

1.        软件测试方法:白盒测试、黑盒测试、灰盒测试、静态测试、动态测试

431
来自专栏从零开始学自动化测试

Selenium2+python自动化70-unittest之跳过用例(skip)

前言 当测试用例写完后,有些模块有改动时候,会影响到部分用例的执行,这个时候我们希望暂时跳过这些用例。 或者前面某个功能运行失败了,后面的几个用例是依赖于这个功...

2764
来自专栏Django中文社区

分类与归档

侧边栏已经正确地显示了最新文章列表、归档、分类等信息。现在来完善归档和分类功能,当用户点击归档下的某个日期或者分类下的某个分类时,跳转到文章列表页面,显示该日期...

3369

扫码关注云+社区