雨中的阳光Seattle

靠着很近的Redmond

[转]使用 Eclipse 插件 Ruby Development Tools 2007年09月25日

Filed under: Uncategorized — systembug @ 2:45 上午

为什么要使用 Ruby?

为什么 Java 开发人员会关心 Ruby?Ruby 是 10 年前在日本开发出来的通用脚本语言。与流行的信念相反,它是一种纯面向对象语言。与 Java 技术不同,Ruby 没有标量,所以所有东西(包括整数)都是一类对象。Ruby 的语法很大程度上借鉴于 Smalltalk、Python 和 Ada。与 Java 语言相同的地方是,Ruby 也是一种单继承语言,但是它提供了 Java 技术所缺乏的某些高级特性,比如闭包(与 steroids 上的匿名内部类相似)和 mix-ins(与接口相似,但是它们与类的绑定不太紧密)。Ruby 也具有很高的可移植性,可以在所有主流操作系统上运行。

Ruby 现在已经很流行了,人们开始用它建立各种应用程序。因为它是解释语言而且使用动态类型,所以可以在运行时做许多极其灵活的工作,而这在 Java 中是非常困难的。动态类型和表达语法所支持的神奇功能之一是,能够在 Ruby 中创建领域特定的语言,这使开发人员能够在更高的抽象级别上工作,从而脱离语言的“原始”语法。Ruby on Rails 是一个用于创建带后端数据库的 Web 应用程序的框架,它展示了这种优雅性。Rake(Make 和 Ant 相结合的 Ruby 版本)也展示了 Ruby 的这种强大能力。

使用 Ruby 的另一个理由是,许多敏锐的开发人员已经开始使用它了。那些在 1996 年就认识到 Java 技术即将流行的开发人员(比如 Glenn Vanderburg、Bruce Tate 和 Martin Fowler)现在已经开始使用 Ruby。即使您还不打算全面转向 Ruby,现在也应该研究一下这种语言了。

对于用 Ruby 进行广泛的开发,主要的限制因素之一是缺少一个出色的开发环境(对于那些不想学习 Emacs 的人来说尤其如此)。RDT 改变了这种状况。在您喜欢的 Eclipse IDE 中使用 Ruby,这会使您感到舒适。

准备

在开始使用 Ruby 之前,必须安装(或者验证已经具有了) Ruby 解释器和库以及 Ruby Development Environment。

获得 Ruby

可以获得适合所有主流平台以及几个次要平台的 Ruby 版本。实际上,您的 Linux® 或 Cygwin 发行版可能已经包含了 Ruby。转到命令提示符下并输入 ruby -v

如果看到一个版本号,就说明 Ruby 已经有了。如果没有看到版本号,就需要获取 Ruby。先寻找适合您平台的 发行版

如果您正在使用 Windows®,就更容易了。RubyForge(与 SourceForge 功能相似)有一个称为 One-Click Ruby Installer 的项目,这个程序会在 Windows 上建立 Ruby 环境(参阅 参考资料)。它还包含几个工具,包括称为 FreeRide 的 IDE,但是如果使用 RDT,则可以不用理会大多数这些工具。

获得文档

在开始使用一种新语言时,文档和其他参考资料是非常重要的。可以在 Ruby 主 Web 站点上获得 Ruby 文档的在线参考,但是您会发现这里的文档有点儿过时了(它针对 Ruby V1.6,而当前版本是 1.8.2)。这是因为最新的文档还没有从日文翻译成英文。但是在 Ruby-doc.org 上可以找到最新的文档。它包含 API 级文档(与 Javadoc 等同)以及一些教程和书籍。(参阅 参考资料。)

如果您关心 Ruby 开发,那么应该尽快获得 Dave Thomas 和 Andy Hunt 撰写的 Programming Ruby: The Pragmatic Programmer’s Guide(参阅 参考资料)。这是对 Ruby 的标准介绍并且介绍了 Ruby 库的需求。在阅读这本书的同时,还可以阅读 Dave Thomas 撰写的 Agile Development with Ruby on Rails,其中介绍了 Ruby on Rails。

获得 RDT

既然已经在计算机上安装了可以工作的 Ruby 版本并且获得了文档,现在就需要 RDT 了(参阅 参考资料)。这个 Eclipse 插件提供了许多特性,您在编辑代码时会慢慢熟悉这些特性。RDT 是一个标准的 Eclipse 插件,具有特性和插件,所以可以将 zip 文件直接解压缩到 Eclipse 文件夹。存档文件中的路径会建立目录结构。

现在可以创建 Ruby 项目了(参见图 1):

图 1. 创建新的 Ruby 项目
创建新的 Ruby 项目

与 Java 技术相比,Ruby 对名称和目录结构的要求宽松多了。在 Ruby 中创建一个项目实际上只是创建一个目录和一个 .project 文件(这里不需要 .classpath 文件,因为 Ruby 没有类路径)。与 Java 技术相比,另一个显著差异是 Ruby 项目向导并不创建显式的 srcbin 目录。Ruby 是解释语言,所以没有输出文件夹。如果项目比较小,那么可以将 Ruby 源代码文件与项目文件放在同一个文件夹中。也可以创建自己的目录结构。您会发现,与 Java 语言相比,Ruby 不太关心目录结构。

接下来,需要一个 Ruby 源代码文件。没有专门用于创建 Ruby 源代码文件的向导。与 Java 技术不同,对于 Ruby 源代码文件的结构没有什么要求,所以要创建 Ruby 文件,只需使用项目的右击菜单创建一个新文件。

图 2. 创建 Ruby 源代码文件
创建 Ruby 源代码文件

不要忘记在文件名后面加上标准的扩展名 .rb,这是一般的 Ruby 扩展名。创建 Ruby 文件应该会打开 Ruby 透视图。

图 3. 创建 Ruby 源代码文件会打开 Ruby 透视图
创建 Ruby 源代码文件会打开 Ruby 透视图

Ruby 透视图还提供一个大纲视图,这与 Java 透视图提供的大纲视图相似。与 Java 大纲视图相似,它允许导航 Ruby 源代码文件的元素。在图 4 中,raise_salary_by 方法在大纲视图和源代码视图中高亮显示。

图 4. 大纲视图允许在源代码文件中进行导航
大纲视图允许在源代码文件进行导航

与其他复杂的插件一样,RDT 也在 Window > Preferences 对话框中增加了定制特性。这个首选项对话框如图 5 所示。

图 5. RDT 的定制首选项
RDT 的定制首选项

首选项菜单项允许修改语法高亮显示方式、格式化(在 Ruby 中标准的缩进是两个空格,不是四个,所以要做某些调整)等等。它还允许定制编辑器模板以及选择解释器。

RDT 编辑器

在 Java 技术环境中,我们已经习惯了高级的编辑器特性,这使我们在转移到没有提供这些特性的其他环境时不太适应。Ruby IDE 缺乏的特性之一是 Content Assist,这种特性针对标识符进行上下文相关的查找。RDT 编辑器对 Ruby 代码提供了 Content Assist。

图 6. RDT 编辑器提供 Content Assist
RDT 编辑器提供 Content Assist

RDT 编辑器中的 Content Assist 没有 Java 环境中那么有针对性,因为 Ruby 支持动态类型。在 Ruby 中,不能将类型赋给变量或者方法返回值。标识符上下文在运行时决定类型。Ruby 使用所谓的“duck typing” —— 也就是说,如果它接受“鸭叫”消息,那么它一定是鸭子。对于那些习惯于强类型语言的人来说,这可能像是一种阻碍,但是这种弱类型耦合使 Ruby 语言能够实现某些更强大的特性。例如,在 Ruby 中可以编写一个异常处理程序,如果调用一个不存在的方法就会触发这个程序,这个异常处理程序可以动态地合成这个方法并且调用它。在强类型语言中很难实现这种元编程。

Content Assist 弥补的特性之一是 Ruby 对标识符使用的特殊命名约定。例如,在 Ruby 中,所有成员变量在第一次使用时才存在,而且都由 @ 符号作为名称的第一个字符。如果使用 Content Assist 查找成员变量,那么可以输入 @,并且只看到成员变量。

图 7. Ruby 中的命名约定帮助实现 Content Assist
Ruby 的命名约定帮助实现 Content Assist

动态类型仍然会妨碍 Ruby 中的上下文敏感性。在图 7 中,合法的成员变量只是在上面方法声明中出现的成员变量,即 @name@salary@hire_year。Content Assist 挑选出的其他成员变量来自后面定义的另一个类。RDT 编辑器还不完善,无法过滤出所有语法上正确但是语义上不正确的条目。

运行和调试

IDE 的关键特性之一是能够在构建应用程序的环境中对应用程序进行运行和调试。RDT 支持这两种功能。

指定解释器

Ruby 是一种解释语言,所以必须将一种解释器与环境相关联,然后 RDT 才能运行或调试应用程序。这种关联是在 Windows > Preferences 对话框中 Ruby 标题下面的 Installed Interpreters 项中设置的。

图 8. 将 Ruby 解释器与环境关联起来
将 Ruby 解释器与环境关联起来

将“Location”文本域指向您使用的 Ruby 版本的 bin 目录。RDT 会找到所需的其他信息。关联了解释器之后,就可以运行应用程序了。

运行 Ruby 应用程序

运行 Ruby 应用程序与运行 Java 应用程序实际上完全一样。使用 Run 菜单创建一个 Run 配置。

图 9. 在 RDT 中建立 Run 配置
在 RDT 建立 Run 配置

在运行应用程序时,RDT 启动 Ruby 解释器并且在 Eclipse 工作区底部的一个控制台窗口中运行这个应用程序。

图 10. 在 RDT 中运行 Ruby 应用程序
在 RDT 运行 Ruby 应用程序

这个例子显示如何运行一个控制台应用程序,但是运行其他类型的应用程序(比如图形化应用程序)也是一样的。

用 RDT 进行调试

IDE 需要的最关键的特性之一是能够有效地调试应用程序。Ruby 解释器包含一个命令行调试器,但是在这个图形化工具的时代谁还想使用命令行调试器?幸运的是,Ruby 解释器还通过一个特定(可配置)的端口广播调试信息,RDT 这样的工具能够监听这个端口并且提供开发人员期望的调试支持类型。

要调试 Ruby 应用程序,需要创建一个 Debug 配置,就像前面创建 Run 配置一样。然后,在左边的空白处点击以设置断点,并使用调试器启动应用程序。与 Java 技术一样,IDE 将询问您是否想切换到 Debug 透视图。回答之后,将看到下面这样的屏幕:

图 11. 在 RDT 中调试 Ruby 应用程序
在 RDT 调试 Ruby 应用程序

RDT 提供了与 Java 技术相同的调试支持级别。左上方的窗格显示当前正在执行的线程。右上方的窗格显示变量的值。与 Java 语言中一样,可以深入到对象中,查看它们的底层成员变量值。左边中间的窗格显示正在运行的应用程序的源代码,右边中间的窗格显示大纲视图(它在这里的工作方式与在编辑器中一样,让开发人员能够通过点击标识符来进行导航)。Debug 窗口的底部显示 Ruby 解释器在端口 1098 上广播调试信息,RDT 在这个端口上监听调试信息。

调试支持是 RDT 的关键特性。即使您的编辑器有出色的 Ruby 支持,但是仍然必须依赖命令行调试器来调试应用程序。拥有一个功能全面的调试器可以显著提高生产效率。

测试

对于 Java 开发人员,Ruby 最难掌握的特性之一是动态类型。如果您已经习惯了强类型语言,那么动态类型看起来可能会导致混乱。动态类型支持所有高级的元编程技巧,这在强类型语言中是很难实现的,甚至不可能。当然,还会失去编译时检查的安全保障。是否有办法同时获得这两种环境的优势呢?

对于语言,单元测试是必需的,但是在动态语言中它特别重要。单元测试能够比单纯编译揭示出更多的问题。实际上,您应该换个角度看待单元测试和编译之间的关系。近来,在一个软件开发专家讨论会上,软件开发思想家 Relevance 公司的 Stuart Halloway 指出,“在 5 年内,我们将看到编译成为一种比较弱的单元测试形式。”单元测试检验代码是否做了开发人员希望它做的事儿,而不只是对类型进行拼写检查。

既然单元测试在 Ruby 环境中如此重要,那么当然希望 RDT 可以使运行单元测试更容易。它确实做到了。单元测试已经包含在 Ruby 中,所以不需要下载任何额外的扩展代码。Ruby 库包含一个 TestCase 类和 TestSuite 的概念。可以像创建其他 Ruby 类一样创建单元测试,这需要对 Test::Unit::TestCase 进行子类化。清单 1 是一个称为 Employee 的示例类。

清单 1. Employee 类


            
            class Employee
            def initialize(name, salary, hire_year)
            @name = name
            @salary = salary
            @hire_year = hire_year
            end
            attr_reader :name, :salary, :hire_year
            def raise_salary_by(perc)
            @salary += (@salary * (perc * 0.01))
            end
            def to_s
            "Name is #{@name}, salary is #{@salary}, " +
            "hire year is #{@hire_year}"
            end
            end
            

对应的单元测试是:

清单 2. Employee 类的单元测试


            
            require 'test/unit/testcase'
            require 'test/unit/autorunner'
            require 'hr'
            class TestEmployee < Test::Unit::TestCase
            @@Test_Salary = 2500
            def setup
            @emp = Employee.new("Homer", @@Test_Salary, 2003)
            end
            def test_raise_salary
            @emp.raise_salary_by(10)
            expected = (@@Test_Salary * 0.10) + @@Test_Salary
            assert( expected == @emp.salary)
            end
            end
            

要运行这个单元测试,应该为单元测试类创建一个 Test::Unit 类型的 Run 配置。

图 12. RDT 包含 Test::Unit Run 配置
RDT 包含 Test::Unit Run 配置

在运行这个测试时,可以获得与 Java 单元测试相同的支持元素,包括左下角与 JUnit 相似的面板。

图 13. 在 IDE 中运行的单元测试示例
在 IDE 运行的单元测试示例

在 Ruby 中还可以创建 TestSuites。TestSuites 是定义套件方法的 Ruby 类,这个方法返回 TestSuite。TestSuite 由在每个 TestCases 中自动定义的套件组成。清单 3 是两个类的 TestSuite 示例。

清单 3. 两个类的 TestSuite 示例


            
            require 'test/unit/testsuite'
            require 'test/unit/ui/tk/testrunner'
            require 'test/unit/ui/console/testrunner'
            require 'TestEmployee'
            require 'TestManager'
            class TestSuite_AllTests
            def self.suite
            suite = Test::Unit::TestSuite.new("HR Tests")
            suite << TestEmployee.suite
            suite << TestManager.suite
            return suite
            end
            end
            #Test::Unit::UI::Tk::TestRunner.run(TestSuite_AllTests)
            Test::Unit::UI::Console::TestRunner.run(TestSuite_AllTests)
            

与前面运行单一 TestCase 的例子不同,套件作为单独的应用程序运行。Ruby 有两种显示 TestSuite 结果的方法。第一种是 Console Test Runner,它在控制台输出结果。第二种是 Tk TestRunner,它创建一个对话框来显示测试结果。Tk TestSuite 对话框见图 14。

图 14. 图形化的 TestSuite 对话框
图形化的 TestSuite 对话框

未来的发展

RDT 的当前版本是 0.50。它的开发人员正在为下一个版本 0.60 而努力。下一个版本中计划的改进包括:

  • 代码折叠 —— 可以将类和方法的代码折叠起来。
  • 大纲视图 —— 更详细,支持局部变量。
  • RI 视图 —— 从 一个 RDT 视图使用 Ruby 的 ri 实用程序。
  • 任务标记 —— 在 Ruby 注释中为可配置的关键字(比如 TODO、FIXME)创建任务。
  • 编辑器改进 —— 自动补齐方括号、圆括号和单/双引号;更好的代码辅助。
  • 审查的快捷方式 —— 对 Debug 会话期间经常使用的审查提供可配置的快捷方式,比如显示对象的所有方法、全局变量,等等。

下一个版本将更好地使用 JRuby 字节码编译器。JRuby 项目可以将 Ruby 代码编译为 Java 字节码。这意味着 RDT 的下一版本将更容易集成到 Eclipse 环境中,提供更好的支持。

结束语

Pragmatic Programmer: From Journeyman to Master 一书中最出色的建议之一是:您应该每年学习一种新的编程语言从而跟上潮流,并且通过新语言学习一些新知识。Ruby 就快要广泛流行了,这在某种程度上是由于 Ruby on Rails 项目获得了成功。到了将 Ruby 加进您的工具库的时候了。RDT 是您掌握 Ruby 的好帮手。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • Ruby 语言的官方站点 Ruby-lang.org 是通向其他 Ruby 站点和文档的门户。
  • Ruby on Rails 是一个有影响力的基于 Ruby 的 Web 应用程序框架。这个站点允许下载 rails 并且查看示例应用程序。
  • Rubyforge.org 提供 One-Click Ruby Installer,这个程序在 Windows 上建立 Ruby 环境。
  • Ruby-doc.org 上可以找到最新的 Ruby 文档。
  • Dave Thomas 撰写的 Programming Ruby, The Pragmatic Programmer’s Guide, 2nd Edition 是 Ruby 语言的指南。这本书采用一种非常容易阅读的方式编写,是新接触 Ruby 的人的首选书籍。即使您不关心 Ruby,也应该阅读这本经典的软件工程著作。
  • 如果您对 Rails 感兴趣,那么 Dave Thomas 和 David Heinemeier Hansson 撰写的 Agile Development with Rails: A Pragmatic Guide 是不错的入门书。这本书明确地指导如何用 Rails 进行开发。
  • Andrew Hunt 和 David Thomas 撰写的 The Pragmatic Programmer: From Journeyman to Master 建议开发人员应该每年学习一种新语言。
  • Glenn Vanderburg 是一位软件思想家,常常提出有意思的观点。他是最先认识到并且宣传 Java 技术的潜力的人之一,他编写了最早的高级 Java 书籍,近几年他积极支持 Ruby。
  • Bitter JavaBitter EJB 的作者 Bruce Tate 也大力支持 Ruby 并且可能从 Java 技术转向 Ruby。他的几本书指导开发人员如何从 Java 人变成 Ruby 人。请阅读 developerWorks 上他的 “Secrets of lightweight development success” 系列。
  • Martin Fowler 是软件工程领域中最响亮的名字之一。他理解深奥的概念并且能够极其透彻地表述这些概念。几年来,他大力宣传 Ruby 的潜力,促使大家转向 Ruby。他常常在自己的 blog 上介绍 Ruby。
  • developerWorks 提供了许多 Ruby 文章和教程,包括 “调试 Ruby 程序技巧 101” 和 “用 Rake 自动执行任务”。
  • 请访问 developerWorks 的 开放源码专区,这里有丰富的 how-to 信息、工具和项目更新,可以帮助您利用开放源码技术进行开发并且将其用于 IBM 产品。

获得产品和技术

  • 获得 Ruby Development Tools,这个 Eclipse 插件添加了 Ruby 支持,包括 Content Assist、调试和单元测试支持。
  • 可以从 Rubyforge.org 获得 Rake,这是 Ruby 的构建工具。Rake 提供一种用于执行构建的语言,提供了 Make 和 Ant 两者的优秀特性。它还展示了 Ruby 灵活的语法如何轻松地创建具有高度针对性的领域专用语言。
  • 使用 IBM 试用版软件 改进您的下一个开放源码开发项目,这些软件可以下载或者通过 DVD 获得。

讨论

关于作者

Neal Ford 是 ThoughtWorks 的应用架构师,这是一家 IT 专业服务公司,它网罗了世界各地的天才,致力于从软件中获得更多价值。他是许多应用、教学资料、杂志文章、课件、视频/DVD 演示的设计者和开发人员,还是 Developing with Delphi: Object-Oriented TechniquesJBuilder 3 UnleashedArt of Java Web Development 等书籍的作者。他主要关注的领域是大规模企业应用程序的构建。他还是在国际上受欢迎的演讲者,曾经在世界各地的许多开发人员会议上发言。他欢迎您通过 neal.ford@gmail.com 与他联系。

 

[转]使用 Eclipse 和 RDT 开发 Ruby 应用程序

Filed under: Eclipse,Ruby — systembug @ 2:31 上午

 

 

liubin  2004/11/29

http://www.ruby-cn.org/

1。什么是RDT

    RDT(Ruby Development Tools),一组Eclipse插件,使得Eclipse能支持Ruby开发。
    而Eclipse是一个功能强大的跨平台集成开发环境,支持对java,jsp,php等地开发。

    关于这两者的优点,可以参考google或者文末的连接。

2。安装配置

首先,安装RDT插件,可以从底下的网址下载到,然后安装。

重启Eclipse之后,然后选择新建工程,这时候我们看到了一块红宝石。

这说明已经装好了RDT,但是使用之前我们还需要指定Ruby解释器,打开preferences窗口,找到Ruby中的Installed interpreters,点击右边的添加,然后类似下图添上你的Ruby解释器的位置:

确定之后,界面如下:

3。开发程序

然后我们就可以写程序了。我们可以随便新建一个test.rb文件,内容如下:

class Test

end

t=Test.new

t.sayHello

def sayHello        puts “hello world”    end

然后,在这个文件上点击右键,选择Run->Run Ruby Application

最后执行结果如下:

如果你想调试,可以设置端点,然后,然后进行调试的方法和开发Java程序一样,比如如下的截图

4。关于代码完成

因为Ruby是动态语言,代码完成(自动补全或提示)实现起来比较困难,RDT提供的也只是基本的一些当前类中可以用的一些东西,多数为语法上的东西,比如定义方法,for循环等等。

打开这个代码完成提示框要使用ctrl+space,昏,正好是默认得切换输入法,无奈只好改了切换输入法的快捷键。

5。连接:

RDT 主页:http://rubyeclipse.sourceforge.net/

Eclipse主页: http://www.eclipse.org/

 

[转]C++程序设计之四书五经 2007年09月24日

Filed under: Uncategorized — systembug @ 6:56 上午

 

原文(上)  原文(下)

C++是一门广泛用于工业软件研发的大型语言。它自身的复杂性和解决现实问题的能力,使其极具学术研究价值和工业价值。和C语言一样,C++已经在许多重要的领域大获成功。

然 而,一个不可否认的现实是,在低阶程序设计领域,C++挤压着C同时也在承受着C的强烈反弹,而在高阶程序设计领域,Java和C#正在不断蚕食着C++ 的地盘。也许C++与C合为一体永远都是一个梦想,也许Java和C#的狂潮终将迫使C++回归本位——回到它有着根本性优势的开发领域:低级系统程序设 计、高级大规模高性能应用设计、嵌入式程序设计以及数值科学计算等。果真如此,我认为这未尝不是一件好事。

C++吸引如此之多的智力投入, 以至于这个领域的优秀作品,包括重量级的软件产品、程序库以及书籍等,数不胜数。文题“C++程序设计之四书五经” 一个不太严格的含义是:C++程序设计之四书×五经。是的,在本文(及其下篇)中,我将分门别类推荐20多本C++好书,你可以根据自己的需要选读。

TCPL和D&E

TCPL 和D&E分别是《The C++ Programming Language》和《The Design and Evolution of C++》的简称,均出自Bjarne Stroustrup之手。我将它们单列出来,首先是因为Bjarne是C++语言的创建者,然后是因为比“首先”那个原因更重要的原因:这两本书是C+ +领域毋庸置疑的杰作。说它们是C++语言圣经,并不为过。

Bjarne Stroustrup,《C++程序设计语言》影印版中文版题解

迄 今为止,TCPL是除了C++标准文献之外最权威的C++参考手册。和大多数人的看法不大一样,我认为Bjarne的文字语言并不逊色于他所创建的程序语 言,至少我喜欢这种学院气息浓厚的作品。本书对C++语言的描述轮廓鲜明、直截了当。它从C++语言创建者的角度来观察C++,这是任何别的作者和书籍做 不到的——没有任何人比Bjarne自己更清楚该怎么来使用C++。

这是一本严肃的著作,以中、高级C++开发人员为目标读者。如果你是一 名有经验的C++程序员,需要了解更加本质的C++知识,本书正是为你而写。它不是那种让你看了会不断窃喜的小书,需要用心体会,反复咀嚼。在阅读过程 中,请特别留心Bjarne先生强调了什么,又对什么一语带过。我个人比较喜欢这本书的第四部分“使用C++做设计”,这样的内容在类似的程序设计语言书 籍中很难看到——我甚至认为Bjarne应该将这部分独立出来单独写一本书。

Bjarne Stroustrup,《C++语言的设计和演化》影印版中文版

D& E是一本关于C++语言设计原理、设计决策和设计哲学的专著。它清晰地回答了C++为什么会成为今天这个样子而没有变成另外一种语言。作为C++语言的创 建者,Bjarne淋漓尽致地展示了他独到而深刻的见解。除了广受赞誉的语言特性外,Bjarne没有回避那些引起争议的甚至被拒绝的 C++特性,他一一给出了逻辑严密、令人信服的解释。内容涵盖C++的史前时代、带类的C、C++的设计规则、标准化、库、内存管理、多重继承、模板等, 对包括异常机制、运行时类型信息和名字空间在内的重要的新特性都分别进行了深入探讨。每一名C++程序员都应该可以从Bjarne的阐释中加深对手中这门 语言的认识。

需要再次提醒的是,这两本书知识浓缩,信息量极大,请不要错过Bjarne每一句看似漫不经意的话。

入门教程

学 习任何一门语言都需要一个从入门到精通、从新手到高手循序渐进的过程。不过,对于一个所谓的新手而言,究竟是一个完完全全的新手,还是一个熟悉某种别的语 言的“新手”,甚至是在某种语言程序设计领域已经颇有建树的高手,很难一概而论?不同的C++新手需要不同的入门书籍。

Andrew Koenig, Barbara E. Moo,《Accelerated C++》影印版中文版

和 市面上大多数C++教程不同,本书不是从“C++中的C”开始讲解,而是始于地道的C++特性。从一开始就使用标准库来写程序,随着讲述的逐渐深入,又一 一解释这些标准库组件所依赖的基础概念。另外,和其他C++教材不同的是,这本书以实例拉动语言和标准库的讲解,对后两者的讲解是为了给实例程序提供支 持,而不是像绝大多数C++教材那样,例子只是用作演示语言特性和标准库用法的辅助工具。

作者在C++领域的编程实践、教育培训以及技术写 作方面都是世界一流水准。我喜欢这种大量使用标准库和C++语言原生特性的清新的写作风格。在这本教材面前,几乎迄今为止的所有C++教材都黯然失色或显 得过时。尽管这本教材也许对于国内的高校教育来说有些前卫,不过我仍然极力向我的同行们推荐。顺带一提,在Bjarne和我最近的一封通信里,他这样评价 本书:对于有经验的程序员学习C++而言,这本书可能是世界上最好的一本。

Stanley B.Lippman, Josee Lajoie,《C++ Primer》影印第三版中文第四版

这 本书的名字多少有点让人误解。尽管作者声称这本书是为C++新手而写,但无论是它的厚度还是讲解的深度都暴露了似乎并非如此。也许说它是一本“从入门到精 通”的C++教程会更合适一些。我个人认为它并不适合完全不懂C++的初学者——在阅读这本书之前,你至少应该先有那么一点C或C++的背景知识,或者至 少要具有一些其他语言的编程经验。

尽管这本书省略了一些高级C++特性的讨论,但仍然可以称得上是迄今为止最全面的C++学习教程。事实 上,如果一名C++初学者能够扎扎实实地读完本书并对照《C++ Primer Answer Book》完成全部习题的话,他的水平肯定可以进入职业C++程序员的行列。我个人认为,即使你已经拥有了TCPL,这本书依然有拥有的价值,因为在许多 方面它比TCPL来得更详细、更易懂。

Stanley B. Lippman,《Essential C++》影印版候捷译文版

可 以不太严格地认为这本书是《C++ Primer》的精简版。本书一一讲述了C++中最具代表性的主题,包括过程式编程、泛型编程、基于对象编程、面向对象编程、模板编程以及异常处理等。 Stanley将门槛调低到“具有其他语言程序设计经验”的C++新手所能接受的最基本的层次,使他们能够迅速开始使用C++编程而又免于阅读《C++ Primer》那样的大部头。它以实例引导学习,力图使读者在最短的时间内把握C++的精粹。

也许换一个人来概述C++编程范型(paradigm)的方方面面需要好几百页才能说清楚,但这本小书不可思议地做到了这一点。我个人非常喜欢这种满是技术、简明扼要并且“有话好好说”的书。这本书同样具有一个明显的风格:所有程序例子全部采用标准库组件,让人耳目一新。

以 上三本书都不是为了完完全全的编程新手而写。完全的C++编程新手可以阅读Francis Glassborow的新书(尚未出版):《A Beginners Introduction to Computer Programming: You Can Do It!》。这也是Bjarne的推荐。Francis Glassborow是ACCU主席,多年来他对几乎每一本C++经典名著评头论足,他自己的这一本自然会引起C++社群的极大兴趣。

高效、健壮编程

两 年前我在负责一个省级电力调度系统项目时编写了一个网关程序,它从SCADA系统获取电力实时信息。通讯接口采用了不常用的数据库直连方式(这个网关程序 一端连接SQL Server 6.5,另一端连接Oralce 8.1.6)。由于实时测点近万,每次将全部取样更新或插入一遍显然是低效的。我在网关程序里建了一个内存库,获取到的数据首先在其中进行比较,然后决定 是否更新物理数据库(同时还做了别的更复杂的事情……),从而在效率和资源占用两方面达到了预期效果。

这个程序一直运行得很好,但在离开现 场之后的某一天,系统管理员打来电话,说大概因为网络故障等原因,有时这个网关程序会崩溃掉——它自己崩掉也就罢了,问题是它还会把Windows 2000 Advanced Server搞成“蓝屏”!(坦白地说,我还从来没看过哪个非蓄意的程序有这个“能耐”)由于当时正忙于另外一个大项目,无法去现场调试,最后只有凭经验 对内存库代码小心翼翼地封装以异常处理代码(同时也做了一些别的修改。这个程序本来不乏异常处理,可惜在开发调试期,很难模拟出真实的“异常”状况,以便 验证那些异常处理代码真的可以工作)。这样,虽然没有彻底解决问题,但程序终究不再死得那么难看了。

在这儿讲这么一段花絮有什么意思呢(当 初为那个可怕的bug朝思暮想时我可不认为这是一个“花絮”)?我想说的是,对于任何软件而言,离开强健,效率也就无从谈起。而对于C++程序员来说,也 许编写一个高效的程序并不难,但要编写一个需要7×24小时持续运行的服务端软件就不是那么容易了(实际上,只要应用服务器不当机,即使发生网络故障,即 使数据库服务器当掉,那个网关程序也应该有能力持续运行下去),需要考虑许多因素,有时这些因素甚至远远超出 C++语言和开发工具的本身。作为一名开发实际项目软件的程序员,并非非得自己碰钉子才能积累经验,只要我们足够虚心,别人的经验往往都是我们很好的借 鉴。鉴于此,我推荐以下几本书供你选读,它们可以让你从强健和效率两方面受益(当然了,它们涵盖的内容远不限于异常处理)。

Scott Meyers,《Effective C++》英文原版(二版三版),候捷中文版(二版三版

Scott Meyers,《More Effective C++》英文原版候捷中文版

如 果说《Effective C++》主要讨论C++中一些相对基础的概念和技巧的话,那么《More Effective C++》则着重探讨了包括异常处理在内的一系列高级技术。与前者相比,后者具有两大主要区别:其一,它包含很多时新的标准C++的内容;第二,它讨论的主 题倾向于“战略化”而非“战术化”,并且讨论得更深入、更彻底。尤其是对虚析构函数、智能指针、引用计数以及代理类(proxy classe)等技术和模式论述的深入程度,让人很难想象是出现于这样的一本小书之中。

游刃有余的技术,高超的写作技巧,Scott无疑是 世界上最优秀的C++技术作家之一。在简洁、清晰、易读等方面,这两本书都卓尔不群。总之, Scott提供的这85个可以改善编程技术和设计思维的方法,都是中、高级C++程序员必备的技能。我强烈推荐这两本书(实际上还有一本,稍后就会看 到)。

Herb Sutter,《Exceptional C++》英文版中文版

Herb Sutter,《More Exceptional C++中文版》英文版中文版

你 自认为是一名C++语言专家吗?读一读ISO C++标准委员会秘书长的这两本书再回答。在这两本书中,Herb采用了“问答”的方式指导你学习C++语言特性。对于每一个专题,Herb首先合理地设 想出你的疑问和困惑,接着又猜测出你十有八九是错误的解答,然后给你以指点并提出最佳解决方案,最后还归纳出解决类似问题的普适性原则。

这 两本书是典型的深究C++语言细节的著作,很薄,但内容密集,远远超过Scott的那两本书,读起来很费脑筋 — 我个人认为它们要比Scott的书难懂得多。若要研习这薄薄的两本书所包含的知识,至少需要花费数月的时间!(在Scott的荐序中,他坦陈不止一次陷入 GotW问题的陷阱,你应该知道这意味着什么)对于语言细节的深究有什么好处呢?尽管在大多数情况下,我们不必关心C++代码幕后的动作,然而当我们不得 不关心时,这两本书可以为我们提供很好的线索,因为它们揭示了C++语言中微妙而又至关重要的东西。

Stephen C. Dewhurst,《C++程序设计陷阱》,中国青年出版社

Stephen 的理论素养和实践经验注定这是一本值得一读的好书。Stephen曾经是贝尔实验室中第一批C++使用者。他已经使用C++成功解决了包括编译器、证券交 易、电子商务以及嵌入式系统等领域中的问题。本书汇集了作者来自开发一线的99条编程真知灼见,洞悉它们,你可以避免几乎所有常见的 C++设计和编程问题。

我甚至认为,对于C++编程菜鸟而言,阅读这本书会比阅读Scott和Herb的书更能轻松而立竿见影地获得更大的 提高。我个人很喜欢这本书的写作风格——Stephen的许多观点看似极端却无可辩驳。当然了,这种自信(以及冷幽默)来自于作者深厚的技术素养,而非自 大的偏执。

除了上面推荐的书籍外,Dov Bulka和 David Mayhew合著的《Efficient C++: Performance Programming Techniques》(《提高C++性能的编程技术》,清华大学出版社)也值得一看。这本超薄小书聚焦于高性能C++应用程序开发。两位作者都是IBM 软件专家,都工作于对性能要求极高的系统构建领域,本书是他们的经验之谈。也有人不喜欢这本书,因为它花了不少的篇幅讲述和C++无关的东西,我却恰恰因 为这一点而对这本书产生好感,正是这些东西让我开阔了眼界。

模板和泛型编程

模板和基于模板的泛型编程无疑是当今发展最活 跃的C++程序设计技术。模板的第一个革命性的应用是STL,它将模板技术在泛型容器和算法领域的运用展现得淋漓尽致,而Boost、Loki等现代程序 库则将模板技术的潜能不断发挥到极致。在模板和泛型编程领域,我推荐以下两本重量级著作。

David Vandevoorde, Nicolai M. Josuttis,《C++ Templates》影印版中文版

有 一种老套的赞美一本书的手法,大致是“没有看过这本书,你就怎么怎么地”,这里面往往夸张的成分居多。不过,倘若说“没有看过《C++ Templates: The Complete Guide》,你就不可能精通C++模板编程”,那么这个论断对于世界上绝大多数C++程序员来说是成立的。

这本书填补了C++模板书籍领 域由来已久的空白。此前,上有《Modern C++ Design》这样的专注于模板高级编程技术和泛型模式的著作,下有《The C++ Standard Library》这样的针对特定模板框架和组件的使用指南。然而,假如对模板机制缺乏深入的理解,你就很难“上下”自如。鉴于此,我向每一位渴望透彻理解 C++模板技术的朋友推荐这本书。

这本书在大陆、台湾各有一个译本,但出自不同的译者之手。当你看到这篇文章时,两个译本应该都已经上市,对于读者来说当然也就多了一种选择。侯捷先生个人网站上开放了繁体译本大部分章节,不妨先睹为快。

Andrei Alexandrescu,《C++设计新思维:泛型编程与设计模式之应用》影印版中文版

你自认为是C++模板编程高手吗?请看过这本书再回答。这是一本出自天才之手令人敬畏的杰作。泛型模式,无限延伸你的视野,足以挑战任何一名C++程序员的思维极限。

这 本书共分为两大部分,第一部分讨论了 Loki程序库采用的基础技术以及一些高级语言特性,包括基于策略的类设计、模板局部特化、编译期断言、Typelist以及小型对象分配技术等。第二部 分则着重介绍了Loki中的重要组件和泛型模式技术,包括泛化仿函数(Generalization Functor)、单件(Singleton)、智能指针、对象工厂(Object Factory)、抽象工厂(Abstract Factory)、访问者(Visitor)以及多方法(Multimethods)等。每一种技术都让人大开眼界,叹为观止。

在C++的学习方面,过犹不及往往成了不求甚解的借口。然而,面向对象并非C++的全部,模板和泛型编程亦占半壁江山。对于“严肃”的C++程序员而言,及时跟进这项早经例证的成功技术,不失为明智之举。

结语

这 些著作是如此大名鼎鼎,也许根本不缺我一个推荐。然而,纵然C++程序员队伍的发展壮大速度不像其他更时髦的语言那样迅速,新人进总是多于旧人出。除了热 忱地欢迎新人,我个人认为到了对C++书籍进行“盘点”的时候了,并且希望这样的“盘点”有益于感兴趣的读者。请保持耐心和宽厚。在下篇中,我将继续介绍 标准库、网络编程以及其他方面的C++好书。有好书相伴,这个冬天不会冷。

 

我在上篇中“盘点”了TCPL和D&E以及入门教程、高效和健壮编程、模板和泛型编程等方面共十几本C++好书。冬去春来,让我们继续C++书籍精彩之旅。

标准库

当 我还在研究院工作时,与同院另外两家研究所合作开发过一个大型水利枢纽调度集成项目。我们三家软件系统之间都要相互通信。在调试通讯模块时,细心的客户 (一名好学的系统管理员)发现对于同一通信规约的解释代码,我的不超过30行,而对方的则超过了150行且很难看懂。这位系统管理员很纳闷,我说大家编程 风格和习惯不一样,我使用了标准库,而他使用了传统C编程风格以及他所习惯的另外一些技术。

别误会!我绝无贬低这位合作伙伴的意思。事实 上,我对那些真正有着深厚的C编程功力的程序员常常怀有钦佩之心。毕竟,C++能有今天的成功在很大程度上缘于它深深地植根于C。作为一名C++程序员, 倘若不熟悉C++中的C,我往往会认为他的基本功是不扎实的,他的技术底气是不足的。

不过话又说回来,C++是一种多范型 (paradigm)编程语言,具体采用哪种编程风格,专业程序员应该知道视具体情况而定。作为一名经常需要在现场做即兴开发的项目负责人,为了短平快地 解决当务之急,我习惯尽量采用现有的库(和组件)。效率(以及强健性)久经验证的C++标准库已经摆在那儿了,何乐而不用呢?

Nicolai M. Josuttis,《The C++ Standard Library: A Tutorial and Reference》原文版中文版:《C++标准程序库:自修教程与参考手册

这 是一本百科全书式的C++标准库著作,是一本需要一再查阅的参考大全。它在完备性、细致性以及精确性方面都是无与伦比的。本书详细介绍了每一标准库组件的 规格和用法,内容涵盖包括流和本地化在内的整个标准库而不仅仅是STL。正如本书副标题所示,它首先适合作为教程阅读,尔后又可用作参考手册。

浅显易懂的写作风格使得这本书非常易读。如果你希望学习标准库的用法并尽可能地发挥其潜能,那你必须拥有这本书。正如网络上所言,这本书不仅仅应该摆在你的书橱中,更应该放到你的电脑桌上。我向每一位职业C++程序员强烈推荐。

Angelika Langer, Klaus Kreft,,《Standard C++ IOStreams and Locales: Advanced Programmer’s Guide and Reference》原文版中文版《标准C++输入输出流与本地化》

C++标准库由STL、流和本地化三部分构成。关于STL的书市面上已经有不少,但罕见流和本地化方面的专著。本书是这两个领域中最优秀的一本,迄今为止没有任何一本书比这一本更全面详尽地讨论了流和本地化。如果你不满足于停留在“会用”流库的层面,千万不要错过它。

2001年夏天,我草草翻阅过这本书的中文版,从内容到包装都给我留下了比较深刻的印象——不过负面的居多一些。2003年秋天,无意中得知某网络书店正以超低价格甩卖这本书的中译本,情不自禁,一阵唏嘘。

Scott Meyers,《Effective STL》影印版中文版

读 完Scott 的《Effective C++》和《More Effective C++》的中译本之后,我一直期待这本书的中文版。我从潘爱民先生的个人主页上了解到,他和他的合作伙伴似乎早已完成了这本书的翻译工作,可惜至今市面上 仍不得见。幸运的是,我们可以看到它的原版。

本书是使用STL的程序员必读之作。在这本书中,Scott向我们讲述STL容器和算法的工作 机制以及如何以最佳方式使用它们。和Scott的其他作品一样,这本书的写作风格清晰、精确,具有极佳的可读性。看过这本书以后,我想你也许会和我以及其 他C++程序员一样产生这样的想法:Scott什么时候会写出一本“More Effective STL”?

Matthew H. Austern,《Generic Programming and the STL: Using and Extending the C++ Standard Template Library》影印版中文版《泛型编程与STL》

关 于STL,我还提醒你留心Matthew H. Austern的《Generic Programming and the STL: Using and Extending the C++ Standard Template Library》(《泛型编程与STL》,中国电力出版社)。这本书散发着浓厚的学院气息。Andrew Koenig和Barbara Moo在《Accelerated C++: Practical Programming by Example》一书末尾郑重推荐另外两本进阶好书(除了他们自己的《Ruminations on C++》外),其中一本是TCPL,另外一本就是本书!

网络编程

在网络编程时代,C++应该扮演着怎样的角色,让ACE(Adaptive Communications Environment)来告诉你。

Douglas C. Schmidt, Stephen D. Huston,《C++ Network Programming》Volume 1: Mastering Complexity with ACE and PatternsVolume 2: Systematic Reuse with ACE and Frameworks

中文版:,《C++网络编程》卷1:运用ACE和模式消除复杂性卷2:基于 ACE 和框架的系统化复用

采 用C++进行企业级网络编程,目前ACE(以及这两本书)是一个值得考虑的选择。ACE是一个面向对象、跨平台、开放源码的网络编程框架,目标在于构建高 性能网络应用和中间件。Douglas是ACE的创始人,Stephen则已为ACE提供了数年的技术支持和顾问服务,两位都是ACE社群(是的,ACE 的影响和实际应用的程度已经形成了一个社群)的专家。

ACE并不单单被大学和研究所追捧,它已经被成功地应用于世界上成千上万个商业应用中。在电信、宇航、医药和财经领域的网络系统中,ACE已经并继续发挥着重要的作用。如果你准备开发高性能通讯系统,你应该考虑考虑这一汇集世界顶尖专家智慧的成果。

除 了使用C++面向对象设计技术和模板等高级语言特性外,ACE还运用了大量的模式。《C++网络编程》卷1和卷2并不仅仅教你关于ACE的方方面面,它还 会教给你模式和通用框架设计等高级技术等。所以,作为一名中、高级C++程序员,即使你很少进行正儿八经的C++网络程序设计,阅读这两本书同样可以从中 受益。

是的,并非所有网络应用都要使用Web服务器(以及其他应用服务器)和重量级组件模型,换个思路,它们或许也可以从轻量级的ACE组件中获益。

杂项

以下几本书所以被列入“杂项”单元,是因为我没有考虑到合适的归类方法,它们和上面的书籍一样,值得一读。

Bruce Eckel,《Thinking in C++》影印版二版三版(又名卷二)

中文《C++编程思想》二版卷一:标准C++导引 卷二:实用编程技术

《Thinking in C++》的第1版于1996年荣获“软件研发”杂志评选的图书震撼大奖。最新推出的第2版对内容进行了大幅改写和调整,以反映C++标准化带来的影响以及 近几年面向对象领域最新研究和实践成果。“输入输入流”、“多重继承”、“异常处理”和“运行时类型识别”等高级主题连同C++标准化以后增加的一些内容 则被放入第二卷中。Bruce是一名经验丰富的C++讲师和顾问,其培训和写作经验都是世界一流水准,他的作品比那些“玩票”的技术人员写的东西更能吸引 读者。事实上,在同类图书中,对于大多数读者而言,这本书的可读性要超过TCPL和《C++ Primer》。顺带一提,访问作者的站点,你可以先睹第二卷的风采。

Andrew Koenig, Barbara E. Moo,,《Ruminations on C++: A Decade of Programming Insight and Experience》原版中文版《C++沉思录》

Andrew 是世界上屈指可数的C++专家。这是一本关于C++编程思想和程序设计技术而非语言细节的著作。如果你已经具有一定的基础,这本书将教你在进行C++编程 时应该怎样思考,应该如何表达解决方案。整本书技术表达透彻,文字通俗易懂。Bjarne这样评价这本书:本书遍布“C++是什么、C ++能够做什么”的真知灼见。

Stanley B. Lippman,《Inside The C++ Object Model》影印版中文版《深度探索C++对象模型》

从 编译器的角度观察C++可以使你知其然并知其所以然。本书探讨了大量的C++面向对象程序设计的底层运作机制,包括构造函数、函数、临时对象、继承、虚 拟、模板的实例化、异常处理、运行期类型识别等,另外还介绍了一些在实现C++对象模型过程中做出的权衡折衷。喜欢刨根问底的C++程序员不要错过这本 书。

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reusable Object-Oriented software

Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,《Design Patterns: Elements of Reusable Object-Oriented software》影印版中文版《设计模式:可复用面向对象软件的基础》

设 计可复用的面向对象的软件,你需要掌握设计模式。本书并非专为C++程序员而写,但它采用了C++(以及Smalltalk)作为主要示例语言, C++程序员尤其易于从中受益。四位作者都是国际公认的面向对象软件领域专家,他们将面向对象软件的设计经验作为设计模式详细记录下来。这本书影响是如此 深远,以至于四位作者以及本书都被昵称为GoF(Gang of Four)。本书学院气息浓厚,行文风格严谨简洁,虽然它不如某些讲解模式的书籍易读,但真正要精准地理解设计模式,本书是终极权威。学习设计模式,这本 书需要一而再、再而三的咀嚼。顺带一句:请将设计模式化作开拓思维的钥匙,切莫成为封闭思维的枷锁。

John Lakos,《Large-Scale C++ Software Design》中文版《大规模C++程序设计》候捷:《STL 源码剖析》

还有一些C++好书值得一读,恕此处无法一一列出。例如John Lakos的著作《Large-Scale C++ Software Design》(《大规模C++程序设计》,中国电力出版社)和侯捷先生的《STL 源码剖析》(华中科技大学出版社)等。

《STL 源码剖析》是一本很有特色的书,但我认为它还可以更好。我个人期待侯捷先生自第一版发行以来经过对模板技术的沉淀和再思考之后,再写一本剖析得更深入、更 透彻并且更全面的“第二版”。遗憾的是,侯捷先生在完成《C++ Templates: The Complete Guide》一书的翻译后似乎决定暂时告别模板、泛型编程和STL领域。

使用C++成功开发大规模软件系统,不仅需要很好地理解大多数C++书籍中讲述的逻辑设计问题,更需要掌握《大规模C++程序设计》中讲述的物理设计技术。当然,这本书的确有点过时了,不过,如果你的精力和金钱都比较宽绰,买一本看看并无坏处。

至此,我想有必要声明一下,有一些(好)书没有得到推荐,主要原因如下:

  • 以上这些书已经足够多、足够好了。
  • 我不会推荐通过正常渠道很难购买到的书籍 ——不管是中文版还是英文版。
  • 作(译)者名气大小不影响我的推荐。我们是在看书,不是看人。
  • 我不会推荐我从来没有看过的书。我至少要看过其中的某个版本(包括电子档)。这个“看”,一般指“认真阅读”,不过有一些也只能算是“浏览”。
结语

作 为一名普通技术写译者,我深知技术创作和翻译的艰辛(和快乐),并多多少少了解一些有关技术书籍创作、翻译、制作、出版以及市场推介背后的细节。今天,我 不会再对一本看上去差强人意的图书信口开河。罗列同一本书的各种版本的用意只在于为你多提供一些信息,让你多一种选择。

在本文成文的后期, 我给Bjarne写了一封信,请教如果他来写这篇文章会怎么写。他给了我简明扼要的建议。在肯定以上列出的绝大部分图书都是世界顶尖水平的C++著作的同 时,Bjarne提醒我别忘了向专家级程序员推荐《The C++ Standard : Incorporating Technical Corrigendum No. 1》

《The C++ Standard : Incorporating Technical Corrigendum No. 1》

Bjarne 还友好地提醒我,在我的推荐列表中没有哪一本有助于C++程序员进行Windows编程——这正是我的本意。在这篇文章中,我只推荐、点评平台中立的C+ +著作(网络编程除外)——和操作系统无关,和集成开发环境无关,我甚至幻想它们和编译器也无关。你可以根据业务开发需要,选读自己喜爱的领域相关的C+ +书籍。

说到“系统无关、平台中立”,我不由得想起了“抽象层”的概念。开发实际应用的C++程序员通常工作于特定操作系统、特定开发环境 和特定业务领域之中,而对标准C++和C++标准库扎实而深刻的把握,无疑是你得以在不同的操作系统、不同的开发环境以及不同的业务领域之间纵横驰骋的 “抽象”本钱。

 

Install PadWalker for EPIC

Filed under: Uncategorized — systembug @ 6:44 上午

 

PadWalker is a module which allows you to inspect (and even change!)
lexical variables in any subroutine which called you. It will only show
those variables which are in scope at the point of the call.
PadWalker is particularly useful for debugging. It’s even used by
Perl’s built-in debugger. (It can also be used for evil, of course.)
prerequisite(WinXP):
PadWalker source: PadWalker-1.5.tar.gz
DotNet 2003 express
ActiveState perl port 5.8.8
1. download it from CPAN(http://search.cpan.org/dist/PadWalker/).
2. unzip and extract it to \lib
3. exec perl Makefile.pl
4. nmake;nmake test;nmake install

 

 

Very Simple Way.

 

[转]Lambda Expressions in C# 3.0 – A little Extension Methods and LINQ Too… 2007年09月20日

Filed under: C# — systembug @ 6:23 上午
del.icio.us Tags:

 

正在看有关Lambda表达式的相关文章,简明扼要的介绍了Lambda表达式。

 

 

Lambda Expressions in C# 3.0 – A little Extension Methods and LINQ Too…

by David Hayden

I spent some time with Lambda Expressions in C# 3.0 last night, and they are extremely cool. I haven’t played with Expression Trees yet, which sound like the coolest part of Lambda Expressions, but still, Lambda Expressions are cool just to help ease the burden of writing verbose Anonymous Methods.

Anonymous Methods

I love Anonymous Methods in C# 2.0. The idea behind anonymous methods it to write methods inline to the code so you don’t have to go through the trouble of declaring a formal named method. They are mainly used for small methods that don’t require any need for reuse.

Instead of declaring a separate method IsAbe to find Abe in a list of strings:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(IsAbe);
        Console.WriteLine(abe);
    }

    public static bool IsAbe(string name)
    {
        return name.Equals("Abe");
    }
}

You can declare an anonymous method inline to save you the trouble:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(delegate(string name)
                        {
                            return name.Equals("Abe");
                        });

        Console.WriteLine(abe);
    }
}

It can get a lot fancier than that, but that is basically the gist. Declare the method inline to the code for easy stuff not needing reuse, because it saves some typing and puts the method closer to where it is being used which helps with maintenance.

Lambda Expressions

Lambda Expressions make things even easier by allowing you to avoid the anonymous method and that annoying statement block:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find((string name) => name.Equals("Abe"));

        Console.WriteLine(abe);
    }
}

Because Lambda Expressions are smart enough to infer variable types, I don’t even have to explicity mention that name is a string above. I can remove it for even more simplicity and write it as such:

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(name => name.Equals("Abe"));

        Console.WriteLine(abe);
    }
}

Now there is no particular reason why I have to use name as my variable. Often developers will use 1 character variable names in Lambda Expressions just to keep things short. Here we replace name with p and all works the same.

class Program
{
    static void Main(string[] args)
    {                    
        List<string> names = new List<string>();
        names.Add("Dave");
        names.Add("John");
        names.Add("Abe");
        names.Add("Barney");
        names.Add("Chuck");

        string abe = names.Find(p => p.Equals("Abe"));

        Console.WriteLine(abe);
    }
}

Using a Customer Class

I used a list of strings above, but you can just as easily use a list of objects to do the same thing. I will take an abbreviated form of a Customer Class:

public class Customer
{
    public int Id;
    public string Name;
    public string City;

    public Customer(int id, string name, string city)
    {
        Id = id;
        Name = name;
        City = city;
    }
}

 and now use Lambda Expressions to find a particular Customer with a name of “Abe”.

class Program
{
    static void Main(string[] args)
    {            
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));

        Customer abe = customers.Find(c => c.Name.Equals("Abe"));
    }
}

Again, we could make things more obvious by explicity saying that c is of type Customer, but I wouldn’t bet on many people doing it 🙂 You could write the above as follows:

class Program
{
    static void Main(string[] args)
    {            
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));

        Customer abe = customers.Find((Customer c) => c.Name.Equals("Abe"));
    }
}

Combining Extension Methods and Lambda Expressions for Help In Finding Our Customer

Let’s say we have a custom CustomerCollection Class not within our control that doesn’t provide us an easy way to find a Customer:

public class CustomerCollection : IEnumerable<Customer>
{
    IEnumerable<Customer> _customers;

    public CustomerCollection(IEnumerable<Customer> customers)
    {
        _customers = customers;
    }

    public IEnumerator<Customer> GetEnumerator()
    {
        foreach (Customer customer in _customers)
            yield return customer;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _customers.GetEnumerator();
    }
}

Let’s add an Extension Method to the CustomerCollection Class, called GetCustomer:

public static class CustomerExtensions
{
    public static Customer GetCustomer(this CustomerCollection customers, Predicate<Customer> isMatch)
    {
        foreach (Customer customer in customers)
            if (isMatch(customer))
                return customer;
        return null;
    }
}

And now, we can use the CustomerCollection Class to easily find Abe like:

class Program
{
    static void Main(string[] args)
    {                    
        CustomerCollection collection = GetCustomers();    

        // Using my GetCustomer Method Extension that accepts my Lamda Expression...
        Customer abe = collection.GetCustomer(c => c.Name.Equals("Abe"));
    }
    
    // Pretend We Don't See This :)
    static CustomerCollection GetCustomers()
    {
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));
        
        return new CustomerCollection(customers);
    }
}

But This Is LINQ!

But, of course, since CustomerCollection implements IEnumerable<T>, in this case, IEnumerable<Customer>, screw the Extension Methods and just use LINQ:

class Program
{
    static void Main(string[] args)
    {                    
        CustomerCollection collection = GetCustomers();    

        // LINQ
        var abe = collection.Single(c => c.Name.Equals("Abe"));
    }
    
    // Pretend We Don't See This :)
    static CustomerCollection GetCustomers()
    {
        List<Customer> customers = new List<Customer>();
        customers.Add(new Customer(1,"Dave","Sarasota"));
        customers.Add(new Customer(2,"John","Tampa"));
        customers.Add(new Customer(3,"Abe","Miami"));
        
        return new CustomerCollection(customers);
    }
}
 

数据结构与算法分析(C++描述) 2007年09月17日

Filed under: Uncategorized — systembug @ 5:38 上午

开始读这本书,回到理论充实阶段。

 

[转][黄忠成]Object Builder Application Block (3) 2007年09月10日

Filed under: C#,dotNet,IoC,Object Builder — systembug @ 3:19 上午

DependencyResolutionLocatorKey包装该对象实体,放入Locator中,如下所示。

private void RegisterObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
if (context.Locator != null)
    {
        ILifetimeContainer lifetime = context.Locator.Get<ILifetimeContainer>(
typeof(ILifetimeContainer), SearchMode.Local);
if (lifetime != ;null)
        {
            ISingletonPolicy singletonPolicy = context.Policies.Get<ISingletonPolicy>(
                typeToBuild, idToBuild);
if (singletonPolicy != null && singletonPolicy.IsSingleton)
            {
                context.Locator.Add(new DependencyResolutionLocatorKey(
                    typeToBuild, idToBuild), existing);
                lifetime.Add(existing);
//……………….
            }
        }
    }
}

以上流程是当该对象实体尚未建立时的流程,假如以BuildUp建立的对象已经存在于Locator中,那么SingletonStrategy的BuildUp方法将直接传回Locator中的对象实体。

public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
    DependencyResolutionLocatorKey key = new DependencyResolutionLocatorKey(
        typeToBuild, idToBuild);
if (context.Locator != null && context.Locator.Contains(key, SearchMode.Local))
    {
        TraceBuildUp(context, typeToBuild, idToBuild, “”);
return context.Locator.Get(key);
    }
return base.BuildUp(context, typeToBuild, existing, idToBuild);
}

PS:注意,SingletonStrategy在该对象已经存在于Locator中时,是直接回传,并不会调用后面如MethodExecutionStrategy、PropertySetterStrategy等Strategy。

5-2、TypeMappingStrategy

前面的章节早已使用过TypeMappingStrategy这个对象了,它主要负责『类型/id』的对应,例如将IDataProcessor接口类型的建立,替换成PromptDataProcessor类型,如程序28所示。

程序28

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_TypeMappingTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new TypeMappingStrategy());
            context.InnerChain.Add(new CreationStrategy());
            ITypeMappingPolicy policy = new TypeMappingPolicy(typeof(TestObject), null);
            context.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null);
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            ITestInterface obj1 = (ITestInterface)context.HeadOfChain.BuildUp(
                context, typeof(ITestInterface), null, null);
            obj1.SayHello();
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public interface ITestInterface
    {
void SayHello();
    }
public class TestObject : ITestInterface
    {
public void SayHello()
        {
            Console.WriteLine(“TEST”);
        }
    }
}

TypeMappingStrategy必须搭配TypeMappingPolicy对象使用,TypeMappingPolicy是一个实现ITypeMappingPolicy接口的对象,构造函数声明如下。

public TypeMappingPolicy(Type type, string id)

第一个参数是映像的实体类型,以本例来说就是TestObject,第二个参数是识别id,接着将其加入context.Policies中,如下所示。

context.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null)

当TypeMappingStrategy的BuildUp方法被调用时,它会以『类型/id』取得对应的ITypeMappingPolicy对象,透过它来取得对应的类型,之后将使用这个类型调用下一个Strategy的BuildUp方法,这就是Type Mapping的流程。

PS:注意,Type Mapping类型必须兼容,如接口->实现、基础类别->衍生类别。

5-3、BuildAwareStrategy

BuildAwareStrategy可以于实现IBuilderAware接口对象建立或释放时,调用对应的OnBuildUp或OnTearDown方法,如程序29所示。

程序29

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_BuildAwareTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new CreationStrategy());
            context.InnerChain.Add(new BuilderAwareStrategy());
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            TestObject obj = (TestObject)context.HeadOfChain.BuildUp(context,
typeof(TestObject), null, null);
            context.HeadOfChain.TearDown(context, obj);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject : IBuilderAware
    {
        #region IBuilderAware Members
public void OnBuiltUp(string id)
        {
            Console.WriteLine(“Object is build up”);
        }
public void OnTearingDown()
        {
            Console.WriteLine(“Object is TearDown”);
        }
        #endregion
    }
}

与其它的Strategy对象不同,BuilderAwareStrategy并不需要Policy对象的协助,它只是判断建立的对象是否实现了IBuilderAware接口。

5-4、BuildUp的第三、四个参数

截至目前为止,我们的例子在调用BuildUp方法时,第三及四个参数都传入null,这两个参数的用途究竟为何呢?这要先从BuildUp方法的宣告谈起。

object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild);

当我们于调用BuildUp方法指定existing为一对象实体时,CreationStrategy将不会建立任何新的对象,只会进行Singleton模式对象的相关动作,然后就调用下一个Strategy对象的BuildUp方法,简单的说!在CreationStrategy后的Strategy仍然会运行,例如Method Injection、Setter Injection都会再次运行,程序30可以协助读者理解这两个参数的用途。

程序30

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_ExistingTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new CreationStrategy());
            ConstructorPolicy policy = new ConstructorPolicy(new ValueParameter(typeof(string), “id”));
            context.Policies.Set<ICreationPolicy>(policy, typeof(TestObject), null);
            TestObject obj = (TestObject)context.HeadOfChain.BuildUp(context,
typeof(TestObject), null, null);
            TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context,
typeof(TestObject), obj, null);
if (obj == obj2)
                Console.WriteLine(“is same object.”);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject
    {
private string _id;
public string ID
        {
get
            {
return _id;
            }
        }
public TestObject(string id)
        {
            _id = id;
        }
    }
}

BuildUp的第四个参数则主导着ObjectBuilder的类型识别及对象识别机制,请先看程序31的例子。

程序31

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_IDTesting
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new CreationStrategy());
            context.InnerChain.Add(new PropertySetterStrategy());
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            PropertySetterInfo pi1 = new PropertySetterInfo(“ID”, new ValueParameter<string>(“ID1”));
            PropertySetterPolicy pp1 = new PropertySetterPolicy();
            pp1.Properties.Add(“ID”, pi1);
            context.Policies.Set<IPropertySetterPolicy>(pp1, typeof(TestObject), “TO1”);
            PropertySetterInfo pi2 = new PropertySetterInfo(“ID”, new ValueParameter<string>(“ID2”));
            PropertySetterPolicy pp2 = new PropertySetterPolicy();
            pp2.Properties.Add(“ID”, pi2);
            context.Policies.Set<IPropertySetterPolicy>(pp2, typeof(TestObject), “TO2”);
            TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),null, “TO1”);
            TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject),null, “TO2”);
            Console.WriteLine(obj1.ID);
            Console.WriteLine(obj2.ID);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject
    {
public string _id;
public string ID
        {
get
            {
return _id;
            }
set
            {
                _id = value;
            }
        }
    }
}

在这个例子中,我们建立了两个PropertySetterPolicy对象,分别以ID2、ID2为id加到了context.Policies中,当CreationStrategy建立对象时,它是以下面的程序代码来取得对应的Policy对象。

private object BuildUpNewObject(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
{
     ICreationPolicy policy = context.Policies.Get<ICreationPolicy>(typeToBuild, idToBuild);
     ………………
}

这段程序代码告诉我们一个重点,ObjectBuidler是以『类型/id』来做类型识别动作,也就是说TestObject+”ID1”、TestObject+”ID2”被ObjectBuilder视为两个不同的对象建立动作,你可以分别为其设定专属的Policy对象,也可以于调用BuildUp方法时,指定不同的id来建立同类型,但不同id的对象。另一个会使用『类型/id』来做识别的是DependencyResolutionLocatorKey对象,我们之前常使用它来完成Injection动作,而SingletonStrategy、DependencyParameter也都是运用它来完成所需完成的工作,其构造函数如下所示。

public DependencyResolutionLocatorKey(Type type, string id)

这意味着,当我们使用SingletonStrategy时,可以利用『类型/id』来建立两个同类型但不同id的Singleton对象,如程序32所示。

程序32

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_SingletonTwoTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilderContext context = new MyBuilderContext();
            context.InnerChain.Add(new SingletonStrategy());
            context.InnerChain.Add(new CreationStrategy());
            context.Policies.Set<ISingletonPolicy>(new SingletonPolicy(true), typeof(TestObject), “ID1”);
            context.Policies.Set<ISingletonPolicy>(new SingletonPolicy(true), typeof(TestObject), “ID2”);
            context.Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
            TestObject obj1 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject), null, “ID1”);
            TestObject obj2 = (TestObject)context.HeadOfChain.BuildUp(context, typeof(TestObject), null, “ID2”);
if (obj1 == obj2)
                Console.WriteLine(“Singleton”);
            Console.Read();
        }
    }
internal class MyBuilderContext : BuilderContext
    {
public IReadWriteLocator InnerLocator;
public BuilderStrategyChain InnerChain = new BuilderStrategyChain();
public PolicyList InnerPolicies = new PolicyList();
public LifetimeContainer lifetimeContainer = new LifetimeContainer();
public MyBuilderContext()
            : this(new Locator())
        {
        }
public MyBuilderContext(IReadWriteLocator locator)
        {
            InnerLocator = locator;
            SetLocator(InnerLocator);
            StrategyChain = InnerChain;
            SetPolicies(InnerPolicies);
if (!Locator.Contains(typeof(ILifetimeContainer)))
                Locator.Add(typeof(ILifetimeContainer), lifetimeContainer);
        }
    }
public class TestObject
    {
    }
}

这个例子将TestObject+”ID1”、TestObject+”ID2”设定为两个不同的Singleton对象,所以当首次建立并指定id时,所建立出来的两个对象是相异的,也就是说,可以利用『类型/id』来建出两个Singleton系统。

5-5、StrategyList

在本文一开始的范例中,我们使用Builder对象来建立对象,它使用了一个StrategyList对象来处理Strategy串行,这个对象提供了两个重要的方法,一是MakeStrategyChain,它会将StrategyList中的Strategy输出成BuilderStrategyChain对象,这是一个实现了IBuilderStrategyChain接口的对象,也是IBuilderContext所要求的Strategy串行对象。第二个方法是MakeReverseStrategyChain,它会将内含的Strategys反相排序后输出成BuilderStrategyChain对象,这个动作是为了准备TearDown时所需的Strategy串行,还记得前面提过,TearDown的Strategy顺序应该与建立时完全相反,这样才能让对象与其相关的子对象适当的释放。

5-6、TStageEnum

StrategyList是一个泛型对象,它接受一个Enum类型,会依照Enum中所定义的元素来建立Strategy串行或是反相排序,要了解这个设计的原意,我们得先看看ObjectBuilder中所预定义,用来指定给StrategyList的列举。

public enum BuilderStage
{
    PreCreation,
    Creation,
    Initialization,
    PostInitialization
}

读者可以查觉,这与我们先前将Strategy分成四种类型的方式相呼应,StrategyList会依据PreCreation、Creation、Initialization、PostInitialization的顺序来产生BuilderStrategyChain对象,这样就不会因为错置Strategy的顺序,导致程序不正常(例如,先加入CreationStrategy再加入TypeMappingStrategy时,TypeMappingStrategy将无法运作)。Builder对象充份展示了BuilderStage与StrategyList的运用方式。

public Builder(IBuilderConfigurator<BuilderStage> configurator)
{
    Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
    Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
    Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
    Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
    Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);
    Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
if (configurator != nulll)
        configurator.ApplyConfiguration(this);
}

只要传入的BuilderStage是正确的,不管TypeMappingStrategy是加在CreationStrategy前面或后面,皆可正常运作。不过同一类型的Strategy,但有顺序需求的情况下,仍然要小心调整顺序,程序32示范了运用BuilderStage所带来的优点。

程序32

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_StrategyListTest
{
class Program
    {
static void Main(string[] args)
        {
            MyBuilder builder = new MyBuilder();
            ITypeMappingPolicy policy = new TypeMappingPolicy(typeof(TestObject), null);
            builder.Policies.Set<ITypeMappingPolicy>(policy, typeof(ITestInterface), null);
            ITestInterface obj1 = builder.BuildUp<ITestInterface>(new Locator(), null, null);
            Console.Read();
        }
    }
public class MyBuilder : BuilderBase<BuilderStage>
    {
public MyBuilder()
            : this(null)
        {
        }
public MyBuilder(IBuilderConfigurator<BuilderStage> configurator)
        {
            Strategies.AddNew<CreationStrategy>(BuilderStage.Creation);
            Strategies.AddNew<TypeMappingStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<SingletonStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<ConstructorReflectionStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<PropertyReflectionStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<MethodReflectionStrategy>(BuilderStage.PreCreation);
            Strategies.AddNew<PropertySetterStrategy>(BuilderStage.Initialization);
            Strategies.AddNew<MethodExecutionStrategy>(BuilderStage.Initialization);
            Strategies.AddNew<BuilderAwareStrategy>(BuilderStage.PostInitialization);
            Policies.SetDefault<ICreationPolicy>(new DefaultCreationPolicy());
if (configurator != null)
                configurator.ApplyConfiguration(this);
        }
    }
public interface ITestInterface
    {
    }
public class TestObject : ITestInterface
    {
    }
}

5-6、PolicyList

BuilderContext所定义的Policies对象类型为PolicyList,PolicyList对象以Dictionary对象来储存设计者所加入的Policy对象,其中用来作为键值的BuilderPolicyKey类别构造函数如下。

public BuilderPolicyKey(Type policyType, Type typePolicyAppliesTo, string idPolicyAppliesTo)

第一个参数为policyType,也就是ICrationPolicy、ITypeMappingPolicy等之类,第二个参数是对应的类型,第三个参数则是id。设计者可以调用PolicyList.Set方法来加入一个Policy至内部的存储器中,该方法会依据传入的参数建立BuilderPolicyKey做为键值,然后将Policy加到Dictionary中,如下所示。

public void Set(Type policyInterface, IBuilderPolicy policy, Type typePolicyAppliesTo,
string idPolicyAppliesTo)
{
    BuilderPolicyKey key = new BuilderPolicyKey(policyInterface, typePolicyAppliesTo, idPolicyAppliesTo);
lock (lockObject)
    {
        policies[key] = policy;
    }
}

另一个泛型类型的Set方法也可以达到同样的效果。

public void Set<TPolicyInterface>(TPolicyInterface policy, Type typePolicyAppliesTo,
string idPolicyAppliesTo) where TPolicyInterface : IBuilderPolicy
{
     Set(typeof(TPolicyInterface), policy, typePolicyAppliesTo, idPolicyAppliesTo);
}

设计者可以透过PolicyList.Get方法来取得对应的Policy对象,该方法如下所示。

public TPolicyInterface Get<TPolicyInterface>(Type typePolicyAppliesTo, string idPolicyAppliesTo)
    where TPolicyInterface : IBuilderPolicy
{
return (TPolicyInterface)Get(typeof(TPolicyInterface), typePolicyAppliesTo, idPolicyAppliesTo);
}
public IBuilderPolicy Get(Type policyInterface, Type typePolicyAppliesTo, string idPolicyAppliesTo)
{
    BuilderPolicyKey key = new BuilderPolicyKey(policyInterface,
        typePolicyAppliesTo, idPolicyAppliesTo);
lock (lockObject)
    {
        IBuilderPolicy policy;
if (policies.TryGetValue(key, out policy))
return policy;
        BuilderPolicyKey defaultKey = new BuilderPolicyKey(policyInterface, null, null);
if (policies.TryGetValue(defaultKey, out policy))
return policy;
return null;
    }
}

SetDefault则可以用一个Policy来提供给所有类型使用,Get方法在找不到对应『类型/id』对应的Policy时,就会以该Policy回传。

六、Locator

ObjectBuilder利用Locator对象来实现Service Locator,也利用Locator来进行Dependency Injection,在ObjectBuilder的架构上,Locator有两种类型,一是Readonly Locator,顾名思义,这类Locator只允许读取、不允许新增。二是ReadWriteLocator,它是允许新增、读取类的Locator。我们可以从Visual Studio 2005的Class Diagram来观察ObjectBuilder中的Locator阶层架构。

图7

6-1、Readonly Locator

ObjectBuidler定义了一个IReadableLocator接口,所有的Locator都必须直接或间接实现此接口,内建实现此接口的类别是ReadableLocator,它是一个抽象类。真正完成实现可用的是ReadOnlyLocator,这个Locator只允许读取,不允许新增。

6-2、ReadWrite Locator

ObjectBuilder中支持读与写的Locator是ReadWriterLocator,与ReadOnlyLocator一样,它也是一个抽象类,真正完成实现的是Locator类别。附带一提,虽然Locator定义了蛮清楚的阶层,但是BuilderContext只支持实现IReadWriterLocator接口的Locator。

6-3、WeakRefDictionary and Locator

Locator类别是我们一直都在使用的Locator,它是一个继承自ReadWriterLocator的类别,值得一提的是,它使用一个WeakRefDictionary来储存设计者所放入Locator的对象,WeakRefDictionary内部对于每个元素都会以WeakReference封装,这意味着,Locator中的元素并无法保证一直都存在,因为CLR会在内存拮据时,先行释放WeakRefernce所封装的对象,这点读者必须谨记。

七、Extending ObjectBuilder

ObjectBuilder除了支持三种Dependency Injection模式、Service Locator之外,最大的魅力应该来自于具高度延展性的架构,设计者可以透过撰写Strategy、Policy、Locator等类别来参与对象的建立动作,本章以两个范例来证明这点,一是EventSetterStrategy,它提供Event Injection功能,二是PoolStrategy,提供Pool模式的对象建立。

7-1、EventSetterStrategy

ObjectBuidler提供了Constructor Injection、Interface Injection(Method Ijection)、Setter Injection(Property Injection)三种Injection模式,虽然ObjectBuilder只提供了Propety式的Setter Injection,不过我们可以藉助于ObjectBuilder高度的延展性架构,让ObjectBuidler也能支持Event Injection。

  • IEventSetterInfo

Event Injection与Property Injection同属Setter Injection模式,两者运作的模式也极为相似,ObjectBuilder在Property Injection部份是由ProperySeterInfo、PropertySetterPolicy及PropertySetterStrategy三个类别所构筑而成,我们可以依循这个既定架构,实现Event Injection功能。首要必须定义一个IEventSetterInfo接口,这相对于IPropertySetterInfo接口之于Property Injection。

程序33

public interface IEventSetterInfo
{
object GetValue(IBuilderContext context, Type type, string id, EventInfo propInfo);
     EventInfo SelectEvent(IBuilderContext context, Type type, string id);
}

IEventSetterInfo接口定义了两个方法,SelectEvent方法是用来取得欲Injection事件的EventInfo对象,EventSetterStrategy会调用此方法来取得欲Injection事件的EventInfo对象,然后透过EventInfo.AddHandler来进行注入动作,这个注入动作所使用的值是透过调用IEventSetterInfo.GetValue方法来取得,此接口的实现程序代码如34。

程序34

public sealed class EventSetterInfo : IEventSetterInfo
{
private string _name = null;
private IParameter _value = null;
    #region IEventSetterInfo Members
public object GetValue(IBuilderContext context, Type type, string id, EventInfo propInfo)
    {
return _value.GetValue(context);
    }
public EventInfo SelectEvent(IBuilderContext context, Type type, string id)
    {
return type.GetEvent(_name);
    }
    #endregion
public EventSetterInfo(string name, IParameter value)
    {
        _name = name;
        _value = value;
    }
}

  • IEventSetterPolicy

前面提过,Strategy是与类型无关的设计,因此需要Policy的协助,我们所设计的EventSetterStrategy也是一样,Event Injection必须具备针对不同『类型/id』进行Event Injection的能力,所以必须设计一个IEventSetterPolicy接口,该接口必须直接或间接继承自IBuilderPolicy接口,这是ObjectBuilder对于Policy的规范。

程序35

public interface IEventSetterPolicy : IBuilderPolicy
{
    Dictionary<string, IEventSetterInfo> Events { get;}
}

针对同一『类型/id』对象可能需要注入一个以上的事件,此接口定义了一个Dictionary<string,IEventSetterInfo>对象,让设计者可以指定一个以上的Event Injection动作,36是此接口的实现。

程序36

public sealed class EventSetterPolicy : IEventSetterPolicy
{
private Dictionary<string, IEventSetterInfo> _events = new Dictionary<string, IEventSetterInfo>();
     #region IEventPolicy Members
public Dictionary<string, IEventSetterInfo> Events
     {
get
         {
return _events;
         }
     }
     #endregion
}

  • EventSetterStrategy

完成了基础类别的设计与实现后,剩下的就是Strategy,也就是EventSetterStrategy的设计与实现了,设计上,EventSetterStrategy只有一个任务,就是于BuildUp方法被调用时,透过『类型/id』经由context.Locator取得对应的IEventSetterPolicy对象,再透过它取得欲进行注入动作的IEventSetterInfo对象,接着调用IEventSetterInfo.SelectEvent方法取得EventInfo对象,最后调用IEventSetterInfo.GetValue取得欲注入的Event Handler对象,然后调用EventInfo.AddHandler方法完成注入动作。

程序37

public class EventSetterStrategy : BuilderStrategy
{
public override object BuildUp(IBuilderContext context, Type typeToBuild,
object existing, string idToBuild)
    {
if (existing != null)
            InjectEvents(context, existing, idToBuild);
return base.BuildUp(context, typeToBuild, existing, idToBuild);
    }
private void InjectEvents(IBuilderContext context, object obj, stringg id)
    {
if (obj == null)
return;
        Type type = obj.GetType();
        IEventSetterPolicy policy = context.Policies.Get<IEventSetterPolicy>(type, id);
if (policy == null)
return;
foreach (IEventSetterInfo eventSetterInfo in policy.Events.Values)
        {
            EventInfo eventInfo = eventSetterInfo.SelectEvent(context, type, id);
if (eventInfo != null)
            {
if (TraceEnabled(context))
                    TraceBuildUp(context, type, id, “Event Setter”, eventInfo.Name);
                eventInfo.AddEventHandler(obj,
                    eventSetterInfo.GetValue(context, type, id, eventInfo) as Delegate);
           }
        }
    }
}

  • Testing

EventSetterStrategy的使用方式与PropertySetterStrategy相似,如38所示。

程序38

using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace EventSetterTest
{
class Program
    {
static void Main(string[] args)
        {
            Builder builder = new Builder();
            builder.Strategies.AddNew<EventSetterStrategy>(BuilderStage.Initialization);
            IEventSetterPolicy policy = new EventSetterPolicy();
            EventHandler handler = new EventHandler(CallHandler);
            policy.Events.Add(“Call”, new EventSetterInfo(“Call”,
new ValueParameter(typeof(EventHandler), handler)));
            builder.Policies.Set<IEventSetterPolicy>(policy, typeof(TestObject), null);
            TestObject obj = builder.BuildUp<TestObject>(new Locator(), null, null);
            obj.RaiseCall();
            Console.ReadLine();
        }
static void CallHandler(object sender, EventArgs args)
        {
            Console.WriteLine(“Called”);
        }
    }
public class TestObject
    {
private EventHandlerList _events = new EventHandlerList();
private static object _onCall = new object();
public event EventHandler Call
        {
            add
            {
                _events.AddHandler(_onCall, value);
            }
            remove
            {
                _events.RemoveHandler(_onCall, value);
            }
        }
protected virtual void OnCall(EventArgs args)
        {
            EventHandler handler = (EventHandler)_events[_onCall];
if (handler != null)
                handler(this, args);
        }
public void RaiseCall()
        {
            OnCall(EventArgs.Empty);
        }
    }
}

图8是此程序的运行结果。

图8

7-2、PoolStrategy

GoF的书中,提出了三种对象管理Pattern,一是Singleton,意味着对象一旦建立后,就存放于某个存储器中,之后所有要求对象的建立动作,都将会获得同样的对象实体,在ObjectBuilder中实现这个Pattern的就是SingletonStrategy。第二个Pattern是SingleCall模式,意味所有的对象建立动作都会获得一个新的对象实体,跟new、create等语言所定义的对象建立模式相同,在Service模式中,SingleCall也意味着Service对象会在要求到达时建立,结束后就立即的释放,这两个模式都可以用ObjectBuilder轻易的实现。第三种Pattern就是Pool,也就是说在特定存储器中维持一定数量的对象实体,当要求对象建立动作时,系统会遍寻存储器中的对象,如果有对象标示为未使用状态,那么系统就回传该对象,并将该对象标示为使用中,本节将实现一个PoolStrategy,让ObjectBuilder可以具备Pool的能力。

  • PoolFactory

Pool Pattern的核心就是一个可以于存储器中管理对象的能力,此处使用笔者书中所设计的PoolFactory类别来完成这个目的。

程序39

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace Orphean.WinFormHelper.Framework.Factorys
{
///<summary>
/// a interface to be implement by Object Factory,
/// DAL use object factory to speed object constructing.
///</summary>
public interface IObjectFactory
    {
///<summary>
/// acquire a object.
///</summary>
///<param name=”type”>object Type</param>
///<returns>object</returns>
object AcquireObject(Type type);
///<summary>
/// release a object.
///</summary>
///<param name=”obj”>a object to releasing</param>
void ReleaseObject(object obj);
    }
public sealed class PoolObjectFactory : IObjectFactory, IDisposable
    {
class PoolData
        {
public bool InUse = falsee;
public object obj;
        }
private IList _storage;
private int _max = 100;
private bool _limit = false;
private IBuilderContext _context = null;
public PoolObjectFactory(IBuilderContext context, int max, bool limit, IList storage)
            : this(context)
        {
            _max = max;
            _limit = limit;
            _storage = storage;
        }
public PoolObjectFactory(IBuilderContext context)
        {
            _context = context;
        }
private PoolData GetPoolData(object obj)
        {
lock (_storage.SyncRoot)
            {
for (int i = 0; i < _storage.Count; i++)
                {
                    PoolData p = (PoolData)_storage[i];
if (p.obj == obj)
return p;
                }
            }
return null;
        }
private object GetObject(Type type)
        {
lock (_storage.SyncRoot)
            {
if (_storage.Count > 0)
                {
if (((PoolData)_storage[0]).obj.GetType() != type)
throw new Exception(
string.Format(“the Pool Factory only for Type :{0}”, _storage[0].GetType().Name));
                }
for (int i = 0; i < _storage.Count; i++)
                {
                    PoolData p = (PoolData)_storage[i];
if (!p.InUse)
                    {
                        p.InUse = true;
return p.obj;
                    }
                }
if (_storage.Count > _max && _limit)
throw new Exception(“max limit is arrived.”);
object obj = _context.HeadOfChain.BuildUp(_context, type, null, null);
                PoolData p1 = new PoolData();
                p1.InUse = true;
                p1.obj = obj;
                _storage.Add(p1);
return obj;
            }
        }
private void PutObject(object obj)
        {
            PoolData p = GetPoolData(obj);
if (p != null)
                p.InUse = false;
        }
        #region IObjectFactory Members
public object AcquireObject(Type type)
        {
return GetObject(type);
        }
public void ReleaseObject(object obj)
        {
if (_storage.Count > _max)
            {
if (obj is IDisposable)
                    ((IDisposable)obj).Dispose();
                PoolData p = GetPoolData(obj);
lock (_storage.SyncRoot)
                    _storage.Remove(p);
return;
            }
            PutObject(obj);
        }
        #endregion
        #region IDisposable Members
public void Dispose()
        {
lock (_storage.SyncRoot)
            {
for (int i = 0; i < _storage.Count; i++)
                {
                    PoolData p = (PoolData)_storage[i];
if (p.obj is IDisposable)
                        ((IDisposable)p.obj).Dispose();
                }
            }
        }
        #endregion
    }
}

本文的重点在于ObjectBuilder的应用与延伸,所以此处就不在赘述PoolFactory的实现细节。

  • IPoolPolicy

PoolStrategy在架构上与SingletonStrategy类似,我们必须设计一个IPoolPolicy接口,该接口的定义如程序40。

程序40

public interface IPoolPolicy : IBuilderPolicy
{
bool IsPool { get;}
}

此接口只定义了一个Pool属性,用来告诉PoolStrategy那个『类型/id』是需要Pool,那个又是不需要的,虽然设计者可以针对要Pool的『类型/id』来指定IPoolPolicy,如果有特定对象不需要Pool动作,那就不指定IPoolPocy即可,但是我们无法排除一种情况,那就是系统里大多数对象都需要Pool,仅有特定的对象不需要Pool,此时要特别对一个个对象设定IPoolPolicy的话,会相当的繁琐。此时设计者可以以SetDefault来加入IPoolPolicy对象,将所有对象标示为可Pool,再针对不需要Pool的对象来指定IPoolPolicy。程序41是实现此接口的程序代码列表。

程序41

public class PoolPolicy : IPoolPolicy
{
private bool _isPool = false;
    #region IPoolPolicy Members
public bool IsPool
    {
get
        {
return _isPool;
        }
    }
    #endregion
public PoolPolicy(bool isPool)
    {
        _isPool = isPool;
    }
}

  • PoolStrategy

PoolStrategy必须在BuildUp方法运用PoolFactory来取得要求的对象,在设计上,我们会为每个『类型/id』建立独立的PoolFactory对象,这意味着每个『类型/id』的对象数量是独立管理的。

程序42

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
using Orphean.WinFormHelper.Framework.Factorys;
namespace OB_PoolStrategy
{
public class PoolStrategy : BuilderStrategy
    {
private WeakRefDictionary<object, object> _factoryMap =
new WeakRefDictionary<object, object>();
private bool _poolObjectCreating = false;
public override object BuildUp(IBuilderContext context, Type typeToBuild,
object existing, string idToBuild)
        {
if (!_poolObjectCreating)
            {
                IPoolPolicy policy = context.Policies.Get<IPoolPolicy>(typeToBuild, idToBuild);
if (policy != null && policy.IsPool)
                {
                    PoolLocatorKey key = new PoolLocatorKey(typeToBuild, idToBuild);
                    PoolObjectFactory factory = null;
if (context.Locator.Contains(key))
                    {
                        factory = context.Locator.Get<PoolObjectFactory>(key);
lock (this)
                        {
                            _poolObjectCreating = true;
try
                            {
                                existing = factory.AcquireObject(typeToBuild);
                            }
finally
                            {
                                _poolObjectCreating = false;
                            }
                        }
                    }
else
                    {
                        factory = new PoolObjectFactory(context, 15, false, new ArrayList());
                        _poolObjectCreating = true;
try
                        {
                            existing = factory.AcquireObject(typeToBuild);
                        }
finally
                        {
                            _poolObjectCreating = false;
                        }
                        context.Locator.Add(key, factory);
                    }
if (!_factoryMap.ContainsKey(existing))
                        _factoryMap.Add(existing, factory);
                }
            }
return base.BuildUp(context, typeToBuild, existing, idToBuild);
        }
public override object TearDown(IBuilderContext context, object item)
        {
if (_factoryMap.ContainsKey(item))
            {
                PoolObjectFactory factory = _factoryMap[item] as PoolObjectFactory;
if (factory != null)
                    factory.ReleaseObject(item);
                _factoryMap.Remove(item);
            }
return base.TearDown(context, item);
        }
    }
public sealed class PoolLocatorKey
    {
private Type type;
private string id;
public PoolLocatorKey()
            : this(null, null)
        {
        }
public PoolLocatorKey(Type type, string id)
        {
this.type = type;
this.id = id;
        }
public string ID
        {
get { return id; }
        }
public Type Type
        {
get { return type; }
        }
public override bool Equals(object obj)
        {
            PoolLocatorKey other = obj as PoolLocatorKey;
if (other == null)
return false;
return (Equals(type, other.type) && Equals(id, other.id));
        }
public override int GetHashCode()
        {
int hashForType = type == null ? 0 : type.GetHashCode();
int hashForID = id == null ? 0 : id.GetHashCode();
return hashForType ^ hashForID;
        }
    }
}

在BuildUp方法被调用时,PoolStrategy会透过context.Policies取得『类型/id』对应的IPoolPolicy对象,判断此次建立动作是否使用Pool,是的话就以『类型/id』至Locator中取出PoolFactory,如果Locator已经有该PoolFactory时,就直接调用PoolFactory.AcquireObject方法来取得对象实体,如果Locator中无对应的PoolFactory时,就建立一个并放入Locator中。在这个建立流程中有几个重点,第一!我们将PoolFactory储存在Locator中,因此需要一个类似DependencyResolutionLocatorKey的对象,用来做为由Locator取出PoolFactory的键值,这个对象必须覆载Equal、GetHashCode两个方法,因为Locator会调用这两个方法来比对键值,这个对象就是PoolLocatorKey。第二!PoolFactory在存储器中没有可使用对象时,会调用BuilderContext.HeadChain.BuildUp方法来建立该对象,这会引发重进入的问题,BuilderContext.HeadChain.BuildUp方法将会再次触发PoolStrategy的BuildUp,而这里又会再次调用BuilderContext.HeadChain.BuildUp,造成重入的问题,所以此处利用一个旗标:poolObjectCreating来解决这个问题。第三!PoolStrategy必须在TearDown方法被调用时,调用PoolFactory.ReleaseObject来将该对象归还,此时会遭遇到一个问题,因为TearDown方法只会传入对象实体,没有id的信息,这使得PoolStrategy无法于此处取得对应的PoolFactory对象,为了解决此问题,PoolStrategy宣告了一个_factoryMap对象,它是一个WeakRefDictionary<object, object>类别对象,在对象实体于BuildUp方法被建立后,PoolStrategy会将object/PoolFactory成对放入_factoryMap中,这样就能于TearDown时以对象实体至_factoryMap中取出对应的PoolFactory对象了。

  • Testing

PoolStrategy的使用方式与SingletonStrategy类似,程序43是应用的程序代码列表。

程序43

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.ObjectBuilder;
namespace OB_PoolStrategy
{
class Program
    {
static void Main(string[] args)
        {
            Builder builder = new Builder();
            builder.Strategies.AddNew<PoolStrategy>(BuilderStage.PreCreation);
            IPoolPolicy policy = new PoolPolicy(true);
            builder.Policies.Set<IPoolPolicy>(policy, typeof(TestObject), null);
            Locator locator = new Locator();
            TestObject obj1 = builder.BuildUp<TestObject>(locator, null, null);
            TestObject obj2 = builder.BuildUp<TestObject>(locator, null, null);
            builder.TearDown<TestObject>(locator, obj1);
            builder.TearDown<TestObject>(locator, obj2);
            TestObject obj3 = builder.BuildUp<TestObject>(locator, null, null);
if (obj3 == obj1 || obj3 == obj2)
                Console.WriteLine(“Pooled”);
            Console.ReadLine();
        }
    }
public class TestObject
    {
    }
}

图9是执行结果。

图9