可复用技术

自从软件开发成为一项严谨的职业以来,程序员们都在寻找轻松工作编写完美代码的方法。可惜的是,Brooks博士在他的名著《人月神话》中,就论述了软件本身所具有的复杂性,这个世界上没有能够一劳永逸的银弹。

但现实却是,我们的工作和生活需要越来越多的软件来支撑。所以为了减少软件开发的工作量,降低系统隐藏的bug量,有许多理论和方法被提出以解决这一问题。

可复用技术可以说是非常直观的想法,这来源于人们在其他工程领域的经验。例如在制造业、建筑工程中,大量的基础部件都是标准的通用件。

那么软件是不是也可以由一组标准的、可重复使用的子程序构成呢?

在结构化编程语言时代,业界众多公司都做出过努力,但最终仍然没有形成统一的认识。大家都认为自己的函数库是最好用的,以至于给C语言的程序员贴上了喜欢重复发明轮子的标签。

在开发语言进入面向对象的时代之后,可复用技术终于有了大步的发展。这得益于面向对象技术的基本特点,包括类的概念、继承、多态等,以及后来的高级特性,包、异常、垃圾回收等。(详细内容可参见我的历史文章学好面向对象编程语言的关键,在于掌握它们的共通结构与特性 )

面向对象设计的常用策略(面向对象可复用技术)(1)

类库

毫无疑问,面向对象开发语言流行之后,类库是可复用技术的第一个成果。为了让代码可重复使用,程序员很自然地就会把类似代码都归总到类中。这些类汇聚在一起,就形成了类库。

接下来为了方便讨论,将会使用C语言和Java语言进行说明。

先提出一个问题,就是要在不同平台上实现IO类的操作,代码差异有多大?例如在linux上和Windows上生成一个网络套接字的方法。

如果用C语言来编写,那么linux上是这样的:

#include <sys/socket.h> int createSock() { int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); }

在Windows上则看起来是这样:

#include <winsock2.h> #include <windows.h> #pragma comment(lib,"ws2_32b") ​ int createSock() { //初始化WSA WORD sockVersion = MAKEWORD(2, 2); WSADATA wsaData; if (WSAStartup(sockVersion, &wsaData) != 0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); }

我想,如果是初学者,看到这两段内容迥异但功能却一样的代码,估计内心是相当崩溃的吧。起码我当年学习网络编程时就是这样。拿着linux版本的那份代码,放到Windows上去修改编译直至通过,这中间趟了多少坑,都远不止代码上能看到的这些差异。

即使编译通过,同名函数的功能还存在差别呢。细心的同学们应该会注意到,在不同的平台上,socket()函数的返回值类型都不相同。

幸好,在Java语言里,这一切都不是问题了。Java创建套接字:

import java.Socket; ​ public static void main(String args[]) throws Exception { String host = "127.0.0.1"; int port = 4321; Socket socket = new Socket(host, port); }

上面这段代码可以放到任何一个平台上运行,并且预期的行为都是一致的,不存在差异。而面向对象语言都会拥有一个内置的标准类库,调用它们就如同调用C语言的内置库函数一样。

当然,Java的类库实现IO操作要依赖各个平台的系统调用接口。但这些差异处完全可以封装到类库中去,对于Java程序员来说,他们根本不必关心这些差异,只要掌握了Java类库的方法就可以。

这是类库对软件开发带来的可复用之处:更容易形成标准化的操作,避免重复发明轮子,屏蔽系统底层的差异。程序员可以摆脱技术细节的纠缠,专注于业务实现。

面向对象设计的常用策略(面向对象可复用技术)(2)

框架

随着互联网业务的快速发展,在构建业务应用层面,人们又有了新的需求。最典型的情况就是web快速增长的情况下,基于http技术的服务成了最普遍的要求。

当然,Java类库中也包含了http的操作方法。这只是解决了http协议相关的操作,但web还包含了一系列对请求进行响应的过程。而这些业务过程,大多数时候在技术上并无太大差异。于是,在更高层面上可复用的框架技术就出现了。

从使用目标与规模上来说,框架可以分为两大类。一是囊括了开发工具、运行时环境等,例如微软的 framework。在某种程度上,这甚至已经成为了一个商业概念。

另一类则是单一语言开发,有特定用途的功能集合。我们接下来要讨论的,则是这种功能性框架。

例如Structs框架就实现了MVC模式的整套流程,程序员使用这个框架,就不必再关心如何去构建MVC的功能逻辑了。还有Hibernate框架,就帮助程序员可以轻松构建对象与关系数据的映射,程序员同样不必去实现ORM的功能逻辑了。

从框架的技术特点可以看出来,它包含了一套实现业务逻辑的流程。程序员要做的,就是在这套已有的流程之中添加自己的业务处理。

这个技术特点在业界有个特定的称谓:好莱坞原则。这条原则的本意是说在好莱坞,电影制作人在挑选演员时会傲慢地说“Don't call us, we will call you.” 在框架设计中,则被引申为应该由框架来调用业务处理,而不是业务代码去调用框架。

对于初学者来说,了解这一点非常重要。因为框架的单方面调用特性,许多细节都不会暴露在外。程序员在学习框架时,往往会感觉无从入手。因为它没有明确的从哪里开始到哪里结束的过程,就是继承一个类,然后重写调用方法,编译运行就可以了。

至于这个方法什么时候会被调用,什么条件下会被触发,基本上要靠猜。所以,了解了好莱坞原则以后,程序员在初学框架时,起码可以做到心中有数,能够从框架的整体逻辑上去分析,被重写的方法调用时机是什么。

面向对象设计的常用策略(面向对象可复用技术)(3)

设计模式

设计模式自从被提出以来,一直是业界的热门话题,今天我想从软件开发可复用的角度谈一谈它。

无论是类库还是框架,它们都是实在可用的代码,而设计模式则不一样,它是写代码的一种参考规范。打个比方的话,类和框架就是锤子和模具,而设计模式则是使用说明书。

从时间线来说,设计模式是后来出现的,因为它并不是凭空创造出来的,而就是来自于对代码本身的提炼与总结。

1995年,包括Erich Gamma等四人合著出版的《设计模式:可复用面向对象软件的基础》一书,可以说是这一领域的开山之作。这四人之后也被江湖尊称为GoF,意即Gang of Four。

在这本书中,对一些典型的代码组织和结构的问题,提出了相应的解决办法。由此而汇集成了23种设计模式。你可以说它们是一些技巧,或者是一些习惯用法。有了设计模式作为思想武器,可以将复杂的业务逻辑分解并实现。

前人智慧总结的好处,就在于它是历经实践所检验的,对于程序员来说,可以放心大胆地去使用。不过有一点还是需要程序员所警醒的,就是不要为了模式而模式,好像哪段代码套不进一个模式里都睡不着觉了。

GoF所提出的设计模式,它们都是基于面向对象技术的,因此熟悉面向对象的基本概念是十分重要的。包括前文提及的类、封装、继承、多态等。

从用途上说,设计模式被分为三个方面。创建型模式,用来指导如何为业务场景以适合的方式生成对象;结构型模式,用来拆解对象间复杂的关系,简化调用结构;行为模式,对于包含了一系列复杂操作的流程,提出了简洁可用的办法。

23种设计模式分类:

当然,随着软件工程的不断发展,也有越来越多的设计模式被提出,并在业界得到应用。可以说,只要程序员需要编写代码完成工作,设计模式就一直会是热门话题。

面向对象设计的常用策略(面向对象可复用技术)(4)

结语

程序员要掌握好软件开发的可复用技术,前提还是要熟悉面向对象的基本概念,熟练使用一门面向对象编程语言。

有了这些基础,在学习类库和框架时,就可以做到提纲挈领,不会迷失于大量的细节之中。当工作需要用到一项功能时,不必急于自己发明轮子,可以搜索开源项目,快速了解其原理并应用到自己的工作中。

而学习设计模式,还真的不是一件可以速成的事情。这需要程序员在不断编写代码的过程中去领会。因为在代码量还没有足够积累的情况下,很容易就陷入到唯设计模式的教条主义中去。

实际情况往往是千差万别的,应用设计模式也不是一成不变的。这需要程序员从实际出发,采用最适合的方法去解决问题。程序员应该记住的是,设计模式是一组原则,而不是配方。

关于可复用技术,也希望看到更多高手们留下自己的看法。

面向对象设计的常用策略(面向对象可复用技术)(5)

,