本书阐述了应用于.NET 2.0框架的设计模式,重点以C#语言来演示应用各种模式。书中的主要内容包括面向对象编程的实质、模块化和异常、测试驱动开发、基本设计模式、应用于架构策略的设计模式、序列化和持久化等,同时针对测试、模式和重构,阐述了相关联的面向对象编程。 \r\n 本书适合高等学校计算机相关专业师生,以及从事.NET程序设计的程序员阅读。
第1章 面向对象编程的实质 \r\n 1.1 理解类型\r\n 1.2 模块化与可重用代码 \r\n 1.3 利用作用域控制访问 \r\n 1.4 理解继承\r\n 1.4.1 class与struct在继承上的区别\r\n 1.4.2 简单的继承 \r\n 1.4.3 利用虚函数继承\r\n 1.5 软件工程中的继承 \r\n 1.6 编写泛型代码 \r\n 1.6.1 泛型的实例 \r\n 1.6.2 约束\r\n 1.6.3 一些思考\r\n 1.7 组合\r\n 1.8 异步代码\r\n 1.9 最后的思考\r\n第2章 日志、错误与测试驱动开发 \r\n 2.1 日志管理 \r\n 2.1.1 简单的log4net例子 \r\n 2.1.2 利用ILog接口生成消息 \r\n 2.1.3 管理配置文件 \r\n 2.1.4 建立一个现实的配置\r\n 2.1.5 实现ToString\r\n 2.2 实现异常处理 \r\n 2.2.1 错误与异常分类 \r\n 2.2.2 实现错误策略\r\n 2.2.3 实现异常策略\r\n 2.3 使用NUnit来做测试驱动开发\r\n 2.3.1 理解测试驱动开发\r\n 2.3.2 在应用程序中使用NUnit \r\n 2.3.3 运行NUnit测试\r\n 2.3.4 更多详细的NUnit测试\r\n 2.4 最后的思考\r\n第3章 定义根基\r\n 3.1 定义应用程序的基础 \r\n 3.1.1 定义意图\r\n 3.1.2 测试驱动开发\r\n 3.2 实现Bridge模式\r\n 3.2.1 使用接口时留有选择余地\r\n 3.2.2 过犹不及 \r\n 3.2.3 关于.NET1.x\r\n 3.3 Bridge模式实现变种\r\n 3.3.1 实现应用程序逻辑\r\n 3.3.2 控制器接口\r\n 3.3.3 实现默认的基类 \r\n 3.3.4 接口和类的设计决策\r\n 3.4 使用Factory模式实例化类型\r\n 3.4.1 需要帮助类型\r\n 3.4.2 实现插件架构\r\n 3.4.3 根据计划创建对象\r\n 3.4.4 克隆对象 \r\n 3.5 最后的思考\r\n第4章 应用程序的架构\r\n 4.1 使应用程序正常运行\r\n 4.1.1 可扩展性和可维护性\r\n 4.1.2 使用黑盒 \r\n 4.2 PipesandFilters模式\r\n 4.2.1 例子:从Amazon.com购买电视机\r\n 4.2.2 电视机选择系统的架构\r\n 4.2.3 电视机选择系统的实现\r\n 4.2.4 关于PipesandFilters模式的几点思考\r\n 4.3 Client-Dispatcher-Server模式\r\n 4.3.1 定义Client-Dispatcher-Server模式的架构\r\n 4.3.2 静态分配器架构 \r\n 4.3.3 动态分配器架构 \r\n 4.3.4 架构Client-Dispatcher-Server模式\r\n 4.3.5 实现程序集目录解析器\r\n 4.3.6 实现Web服务解析器\r\n 4.4 Micro-Kernel模式\r\n 4.4.1 微内核的架构\r\n 4.4.2 隐藏微内核的细节\r\n 4.4.3 设计微内核\r\n 4.4.4 微内核实现细节\r\n 4.4.5 构建简单的银行应用程序\r\n 4.4.6 关于Micro-Kernel模式的思考\r\n 4.5 最后的思考 \r\n第5章 实现成组的组件 \r\n 5.1 两个传统的面向对象错误\r\n 5.1.1 属性和烤箱温度控制\r\n 5.1.2 继承和基类的脆弱性问题 \r\n 5.2 示例应用:翻译程序\r\n 5.2.1 快速编写一个简陋的应用程序\r\n 5.2.2 重构代码 \r\n 5.2.3 重构并且实现Bridge和Factory \r\n 5.2.4 实现Mediator模式\r\n 5.2.5 实现Template模式\r\n 5.2.6 实现Adapter模式 \r\n 5.2.7 关于翻译程序的最后思考\r\n 5.3 为应用程序添加多语言支持\r\n 5.3.1 想想看:Decorator还是Composite \r\n 5.3.2 实现ChainofResponsibility模式 \r\n 5.3.3 实现Command模式 \r\n 5.3.4 实现Composite模式 \r\n 5.3.5 实现Decorator模式\r\n 5.3.6 实现State模式 \r\n 5.3.7 实现Strategy模式\r\n 5.3.8 实现翻译语言的动态选择 \r\n 5.4 最后的思考 \r\n第6章 编写算法 \r\n 6.1 不做修改的功能模仿\r\n 6.1.1 实现Proxy模式 \r\n 6.1.2 使用仿函数增强类型\r\n 6.1.3 为集合创建泛型仿函数架构\r\n 6.2 构建电影票应用程序\r\n 6.2.1 从基础开始\r\n 6.2.2 计算票的销售额\r\n 6.2.3 读取销售额数据\r\n 6.2.4 使用null的问题 \r\n 6.2.5 更为简单的买票方法:使用Facade模式 \r\n 6.3 使用多态管理扩展\r\n 6.3.1 实现StaticExtension模式 \r\n 6.3.2 实现DynamicExtension模式\r\n 6.3.3 扩展、类型转换以及它们的意义 \r\n 6.4 使用Iterator模式遍历数据 \r\n 6.4.1 使用C#2.0实现Iterator模式\r\n 6.4.2 在迭代器中使用仿函数 \r\n 6.5 最后的思考\r\n第7章 高效代码\r\n 7.1 不可变类是高效的类\r\n 7.1.1 为什么不可变类具有一致性\r\n 7.1.2 为什么不可变类是可伸缩的\r\n 7.1.3 一些使用不可变类的经验\r\n 7.2 在Flyweight模式中使用不可变类 \r\n 7.2.1 Flyweight模式的例子 \r\n 7.2.2 通用的Flyweight架构 \r\n 7.2.3 使用通用Flyweight架构\r\n 7.2.4 使用Flyweight的实现 \r\n 7.3 对象池原理\r\n 7.3.1 对象池和COM+\r\n 7.3.2 对象池理论 \r\n 7.3.3 在.NET中实现ObjectPool模式\r\n 7.4 多线程应用程序 \r\n 7.4.1 简单的线程例子 \r\n 7.4.2 实现单例\r\n 7.4.3 使用生产者-消费者技术管理多线程问题\r\n 7.5 最后的思考 \r\n第8章 数据持久化\r\n 8.1 .NET中的序列化\r\n 8.1.1 .NET中的二进制对象序列化\r\n 8.1.2 .NET中的XML对象序列化\r\n 8.1.3 序列化的问题 \r\n 8.2 调整并完善Serializer模式\r\n 8.2.1 问外部状态:Visitor模式\r\n 8.2.2 访问内部状态:Memento模式 \r\n 8.3 使用NHibernate进行对象/关系数据映射\r\n 8.3.1 简单的NHibernate示例 \r\n 8.3.2 映射一对多关系 \r\n 8.3.3 其他类型的关联 \r\n 8.3.4 使用HQL \r\n 8.4 最后的思考 \r\n第9章 通过重构实现模式\r\n 9.1 测试驱动开发与重构\r\n 9.1.1 编写第一行代码 \r\n 9.1.2 在第一部分代码后 \r\n 9.1.3 重构的种类 \r\n 9.2 类,方法——一切都太大了\r\n 9.2.1 重构Stream类\r\n 9.2.2 重构Stream类的问题\r\n 9.2.3 重构类而不是基类型 \r\n 9.3 我不理解代码\r\n 9.3.1 处理未知代码\r\n 9.3.2 跟踪代码\r\n 9.3.3 中断代码\r\n 9.4 代码似同实异\r\n 9.4.1 为什么复制和粘贴代码有效\r\n 9.4.2 使用Template方法重构重复的代码\r\n 9.4.3 可以接受的重复 \r\n 9.5 时不我待 \r\n 9.6 我希望移除代码 \r\n 9.7 最后的思考
初看书名中的Foundation,会让人觉得这不过又是一本讲解面向对象以及设计模式基础知识的图书。市面上流行的模式方面的书籍还不够多吗?
说实话,刚从图灵公司编辑处获得此书时,我的感觉也是这样,甚至有一点点不屑。但随着深入地翻译,我越发感觉这样的书名对于这样的内容也许根本就是不合适的。未曾阅读章节内容,仅从目录即可一瞥作者的别具匠心:作者是在从宏观的角度引导如何构建健壮而具有可维护性和可扩展性的应用程序。
从程序的根基,到架构,到脉络(实现算法),到代码合理性,到数据持久化,到对模式的重构,作者以设计模式为主线穿插其中,深入浅出,融会贯通,为读者展现了一条清晰易懂的构建之路。
虽然书中的程序代码是以C#编写的,但是简洁易懂,对于稍具OOP经验的开发者来说完全不足以成为阅读的障碍,毕竟,书中的精髓在于思想而不是代码的堆砌。在领悟了作者的构建之道后,聪明的开发者一定能够举一反三。
我的同事李彦娜、张广亮也参与了本书的翻译工作,在此对他们表示由衷的感谢。牺牲了那么多私人生活时间,个中的辛苦只有他们自己了解。同时感谢他们能够容忍我一遍一遍的催促和埋怨。
感谢所有热心的读者,希望本书能在你通往技术殿堂的道路上提供许多的帮助。翻译中有不尽如人意的地方在所难免,期待读者的批评指正。
感谢傅志红编辑和人民邮电出版社给予我们这样的机会,在翻译的同时我们又学到了很多东西。
最后,感谢所有为此书的翻译、制作、出版、发行做出贡献的人们。
译者
2006年8月
Christian Gross是一位经验丰富、多才多艺的软件顾问,客户包括微软、戴姆勒-奔驰等大型公司。他精通各种微软客户端/服务器技术。除本书外,他还撰写了畅销书Ajax Patterns and Best Practices,并与人合作撰写了许多ASP、SQL Server、Windows编程方面的技术图书。
模式是一个已经被前人充分讨论过的有趣话题。最早谈及模式的书是Erich Gamma等编写的《设计模式》(Boston:Addison-Wesley,1995)。该书出版时,模式还是一种新的思想和概念;而现在该书已经成为设计模式的必备参考书,其中阐述的设计模式成为了各种应用程序的基础。
本书阐述了应用于.NET 2.0框架的设计模式。其中的一些设计模式来自于该书,一些则来自其他来源。本书的重点并不是要定义各种模式,而是用一种编程语言(例如C#)来演示各种模式的应用。因为最初的设计模式是利用C++进行阐述的,与.NET以及C#之间存在着很大的语言差异。
为什么使用模式
你也许会问:“为什么要写这本书,为什么要使用模式?”的确很奇怪,这个想法是在我培训学生如何使用设计模式的时候冒出来的。当时学生们正在做练习,我看见一位学生编写代码时使用一个类而不是接口作为基类。我问他为什么这样编写代码,他的回答是:“因为我一直都是这样做的。”这位同学的方法是错误的,但是他的回答揭示了一个很有趣的观点:当一个基类足够好的时候为什么还要使用接口呢?就在那时,我突然想到:先教授模式再教授面向对象编程(OOP),理解就会更为容易。
当学习传统的OOP技术时,老师会讲到有关形状、矩形以及其他的抽象主题。你会学到一个类如何负责它自己的数据以及如何实现这些责任。这种学习方式的问题是,它解释了面向对象编程,但是并没有解释如何利用具体的方案来解决问题。例如,如何实例化一个类型,将它的引用传递给另一个类,并将信息持久化到媒介中?传统面向对象编程的问题是,在实现解决方案时只给出了模糊的指导。
模式就不同了,它给出了一个预定义的形式,告诉你在什么时候该做什么。考虑下面的情况:学习如何烘焙蛋糕。你知道,蛋糕是由面粉、鸡蛋、牛奶、发酵粉以及一些其他原料做成的,但并不是所有蛋糕都是一样的。如果加入过多的发酵粉,蛋糕会过分地发酵胀起;如果加入过多的鸡蛋,蛋糕吃起来会像是煎蛋卷。面向对象编程就像是烘焙蛋糕一样,你知道所需的原料,也知道这些原料的作用,但是不知道这些原料的比例以及制作蛋糕的步骤。就像好的食谱能够帮助你把这些原料变成美味的蛋糕一样,模式能够帮助你将代码转换成高效的程序。
在不知道原料的作用以及它们之间是如何相互作用的情况下,也可能只根据食谱制作出蛋糕。但这种方法的问题是它无法奏效。例如,假设你正在准备一顿饭,开始是豌豆汤,主菜是胡椒薄荷沙司调味的鲑鱼、南瓜以及黄豆面条,最后是餐后甜点冰淇淋。尽管可能每道菜的味道都很好,但这个菜谱听起来并不令人胃口大开。其中的问题是模式,就像是菜谱,之间是相互作用和配合的,因此需要协调。协调模式的前提就是对面向对象编程技术有一个基本的理解。
什么是模式
模式就像菜谱,只不过用于创建模式的原料是面向对象的原理。例如,考虑下面的源代码:
类Derived实现了接口IBaseInterface,这就是典型的Bridge模式的实现。一定会有人问,为什么使用接口而不是类呢?从面向对象的角度看,它们的结果很相似。这就引发了一个问题,为什么在编程语言中要用接口呢?答案是:因为模式定义了最佳实践,而该实践已经被证明在应用程序开发中是有益的。从大量的编程经验来看,在使用基类型以及实现Bridge模式时,接口比类更有用。
模式不同于最佳实践,就像理论不同于猜想一样。理论基于已经被科学方法无数次证明的想法,科学方法是一种其他人也能够重新生成结果的实验方法。模式就像是一本食谱,它定义了一系列的步骤以及原料,当按照定义的步骤操作原料时,就能够做出相同的蛋糕。猜想是一种想法,它很可能是正确的,但是并没有被不同的人用必要的重复的实验证明。猜想是理论的先驱,意味着最佳实践是模式的先驱。
《设计模式》一书中定义的模式已经被证明是有效的,并且已经应用于多种情况。例如,利用Factory模式定义的结构,可以在不同的编程环境中,利用不同的编程语言产生相同的结果。
你能从本书获得什么
除了模式以及面向对象原理,通过本书将学习像音乐家阅读乐谱那样阅读代码。思考一下,音乐家利用只有音乐家才懂的符号来表达他们的思想。乐谱,代表了要演奏的音乐,对于不懂的人来说就像是一堆胡乱画出的符号。然而对于音乐家,它是巴赫、莫扎特、AC/DC乐队或者说唱乐手Eminem,意味深长。当阅读乐谱时,音乐家知道何时演奏他们的乐器,以及当时产生的是一种什么样的情绪。没有对乐谱的简化和抽象,音乐家阅读乐谱后只知道那是一段怎样的音乐。
本书的目的就是使你能够阅读一段代码并且明白其中的意图。在很多开源项目中,经常需要阅读其他人编写的代码。对于其他类型的项目情况就不是这样,每位编程者都使用自己喜欢的风格和技术编写代码。例如,就像是将大括号放在哪里这样的小问题也会引发激烈的争论。会有这种争论的原因是,每位编程者都在实践中或者参加会议的幻灯片中或者别人的注释中学到了一些风格和技术。阅读一段代码并不只是识别出一些关键字,而是理解在一种环境中使用一段关键字解决一个特定的问题。简言之,阅读一段代码就是在模式的环境中诠释OOP。
从哲学的层面考虑一下,一种编程语言为什么会存在?问问自己,为什么一种编程语言包含了一个特定的关键字、概念或者策略?答案是,因为那个关键字、概念或者策略在一个特定的环境中解决了一个特定的问题。但并没有解释何时才是使用这种特征的最佳时机。例如,在C#中,可以使用关键字struct、class、interface、abstract以及sealed来定义一个用于程序的类型。通常编程者知道上述关键字的技术理由以及效果。然而,编程者通常不知道每个关键字在什么情况下应该或者不应该使用。这就回到了起点,不管在什么地方使用,编程者会依赖于他们自己的习惯或风格。这并不是正确的编程方法,因为它有可能依赖于试错(trial and error)。
本书内容
本书的每一章在知识上都是循序渐进的,并且各有明确的目标。
□ 第1章揭示了面向对象编程的本质,利用C#定义和阐述了模块化和异常等概念。阅读这一章有助于更容易地理解各种OOP词汇。
□ 第2章的主要目的是定义和阐述测试驱动开发,它是一种通过一致地创建测试和源代码来开发软件的方法。这种方法的优点是保持源代码的稳定和一致。
□ 第3章将会接触到作为其他设计模式基础的基本设计模式——Factory(工厂)模式和Bridge(桥接)模式。
□ 第4章演示了将基本模式应用于架构策略的创建。上一章介绍的设计模式是通用的,但并不能用于整体架构。架构策略能够很容易地扩展和维护,阐述了如何解决应用问题。利用定义好的模式,演示了如何开始创建具有正确开端的应用程序。
□ 当实现应用程序时,主要问题经常是利用良好开发的代码使应用程序能够正常工作。接下来的需求和要求会增加新的类或者修改现有的类,而这些增加或修改可能会引发问题。第5章探讨了这个问题,提出了一些用于更容易实现应用程序的模式。
□ 在编写代码时,通常试图走一些捷径来解决问题。这个捷径可能很小但却可能有重大的影响。在第6章中给出了一些模式,定义如何处理想要走捷径的情况,其中解释的模式都很详细,似乎有些大题小作。但是,这些模式的主要目标是简化对代码的扩展和维护。
□ 在编写代码时,目标是使代码尽可能的高效。第7章教你如何编写在扩展以及维护的同时又不增加理解难度的高效代码。
□ 序列化和持久化是很多关于模式的图书回避的话题,然而它们非常重要,第8章中讨论了这个话题。这一章中阐述的模式和策略显著地简化了向其他媒体写数据的过程。
□ 最后一章通过介绍重构将所有一切包装起来。这一章的重点并不是介绍重构的所有方面,而是定义并阐述如何利用本书中定义的模式来重构现有的代码。
无封面