多模式 .NET,第一部分
Ted Neward
多年来,.NET 社区中的许多人都听说过 Microsoft 对于 Visual Studio 环境充当的“角色”:爱因斯坦(天才)、猫王(摇滚明星)和莫特(“普通”开发人员)。尽管这些角色可能对 Microsoft 尝试确切了解他们正在为哪些人构建 Visual Studio 和 Microsoft .NET 平台有用,但我发现它们并不那么有用。事实上,我逐渐意识到对于绝大部分 .NET 生态系统来说,开发人员几乎都属于两大基本(且非常老套)的阵营之一:
**C++ 开发人员。**这类开发人员了解所有“合适的”面向对象的做法,并且利用一切机会设法构建富域模型(即使在编写批处理文件时也是如此)。如果他们的资历相当老,实际上已非常专业地编写过 C++,则他们很可能专注于构建框架和可重用抽象,以致于可能从未交付任何成果。这类开发人员的特征是拥有自命不凡的态度,并且经常会发现他们自负地引用他人的“模式”来“教育那些不幸聚集在一起却不了解何谓无名的品质的人们”。
**VB 开发人员。**这类开发人员听说了有关对象、模板、过程以及多年(也许是几十年)来一直在讨论的所有其他技术的全部宣传,并且一直坚定地秉持“只要代码可以交付,我愿意做任何事情”的态度。事实上,这类开发人员太过于注重代码交付,以致于当被要求在现有表单中添加一个新按钮时,他们会从头开始重新编写全部代码。这类开发人员以其标志性的“不要告诉我工作原理,只要告诉我怎么做”态度而出名,并且经常会发现他们从 Google 中偷窃代码并粘贴到自己的程序中(有时是胡乱地),直到程序运行为止。
在大量讨厌的邮件到来之前,让我们先指出显而易见的事实:以上只是一个总体印象,我决没有针对某人或者暗示哪一方更好一些的意思。(我曾经属于第一阵营,但如果有谁说那些人要出众一些,我将会第一个跳出来反对。)
我之所以花大量篇幅说明这两大阵营的存在,是因为接下来的一系列专栏将更具体地介绍后者,即 VB 开发人员,他们未花很多时间思考软件设计。但或许令人惊讶的是,我们涉及的内容同时应与第一阵营有关系,因为随着 Visual Studio 2010 和 .NET Framework 4 的发布,事情变得复杂很多,至少在语言空间方面。如果孜孜不倦的程序员在接下来的十年中有任何机会设计软件(或扩展已设计的软件),而不会使其成为一团糟,那么将会是因为他们在多模式设计或我所称之的多模式编程 方面有良好的基础。(是的,多模式编程是一个自命不凡的名称。我的 C++ 根基表露无遗 - 控告我吧!)
多模式设计
术语“多模式设计”(和概念,如果可以说有一个作者的话)源于 James O.Coplien(Addison-Wesley Professional,1998)撰写的“Multi-Paradigm Design for C++”一书。根据书名的后部分,可以相对容易地猜测出该书最初以两个阵营中的哪一个为目标。但是,结果是语言并非谈论的重点;Coplien 的观点在十年后仍能引起共鸣:
一个隐藏的危险是术语“面向对象”已成为“合适”的同义词。在当今市场中,您可能会发现每个可想象到的模式都贴上了“对象”标签。这就导致了混合设计环境,它们是在忠于过去的基础上构建的,并且通常受到一种需求的支持:即在对旧方法和技术进行投资的基础上构建。大多数这些环境都称为“面向对象”。这种组合方法的一个不利因素是它们掩盖了面向对象的设计的一些中心原则,并用其他方法的原则取而代之。这些另人费解的设计技术组合可能会导致体系结构灾难。… 维护将变得很困难,整个结构受损,并且需要大量精力来保持系统可行。
但纯粹的对象也不是解决之道。完善的模式已被面向对象的大肆宣传边缘化。当代开发商的腔调是没有人使用过程分解会出错,即使是对于批排序例程 - 必须以某种方式将对象构建到解决方案中。这就产生了将方钉强制放入圆孔中的设计。
几乎是从最初开始,计算机科学的历史就充满了理想化的问题解决方案:不断尝试创建一种终极的问题解决方法首先产生了汇编程序,然后是编译器,并最终在“您的语言糟糕透顶,这就是原因”论调中产生了整个家庭式工业。更实际的实践者耸耸肩说:“当你只有锤子的时候,所有东西看起来都像是钉子。”随着语言变得越来越复杂,我们不知为何认识不到一个事实,那就是一种工具实际上可用于多个用途。
对我来说,几年前在雷德蒙的动态语言峰会上听 Anders Hejlsberg 畅谈 C# 3.0 时获得了对这个主题的启发。他指出了 C# 正吸收其他语言的一些思想,并阐述了以下意思:“语言的分类正逐渐消失。我们不再能够说一种语言仅仅是面向对象的语言,或者仅仅是动态语言,因为很多语言都借用了大量不同的思想。”
他的评论反应了 Coplien 十年前的观点:“C++ 的进一步发展 [超越了其前面的模式,如模块化、抽象数据类型、过程和数据结构],以便在同一起点支持过程、模块、基于对象、面向对象和泛型编程。”
C# 又更进了一步,吸收了 3.0 版本中的功能性概念以及 4.0 版本中的动态概念。Visual Basic 在很久以前就具有动态性(虽然通常会受到嘲笑),并且由于 Microsoft 要实现其与 C# 之间的“语言相似性”的目标,它支持相同的功能。没有这些隐藏在表面下的其他语言模式,诸如 LINQ 的解决方案实现起来会更加困难,并且会强制开发人员依赖于其他机制(如代码生成,它本身是元编程的一个有趣方面,将会在后面详细介绍)以“继承自基类”无法捕获的方式来捕获正常工作系统的核心共同点。
这就是我们将要探讨的核心:世界上面向对象的狂热者多年来一直坚称继承代表重用代码的最好方法。(默认情况下,类未标记为“sealed”[C#] 或“NotInheritable”[Visual Basic] 的原因就在于此。)但是,在从 WinForms 或 ASP.NET 中的 Form 类继承时,我们实际上未重写基类方法;相反,我们提供委托调用。为什么不直接重写?为什么要遭受委托调用这种打击(虽然可能很小)?为什么后台代码从基于继承的模型转为基于部分类的模型?究竟为什么要有扩展方法?
上述每个问题的答案都可以(并且已经)用面向对象的术语进行解释,但根本原因相同:并非所有东西都可以用经典的对象设计构造轻易表示。尝试发现代码的共同部分并将它们组合成一个一级构造(面向对象的编程认为一级构造是类,结构化编程认为一级构造是数据结构,而功能性编程认为一级构造是函数,等等)是软件设计的目的。如果我们可以使用更多方式来改变需要变更的代码部分,我们就可以更多地编写系统,并能够从容应对不断来电说明“只是一件很小的事情,我忘记告诉你了……”的客户。
作为一个练习,请考虑以下情况:.NET Framework 2.0 引入了泛型(参数化类型)。为什么呢?从设计的角度来看,它们起什么作用呢?(为准确起见,诸如“它为我们提供类型安全的集合”的答案并没有抓住要点 - Windows Communication Foundation 以明显不仅仅是为了提供类型安全的集合的方式大量使用了泛型。)
我们将在下一次谈到这一问题。
未完待续!
很明显,关于这个主题还有很多可以讨论的内容 - .NET Framework 中存在的每个模式都值得探讨和说明,包括代码(如果第一部分对于正从事开发的开发人员来说有任何意义的话)。后续部分即将发布,敬请关注。在我们完成之前,我希望(并且相信)您会有许多更好的设计工具来构建优秀的(这里是指好的抽象、可维护、可扩展和可重用)软件。
但现在,请重点关注您正在进行的当前设计,并看看能否找出使用其中每个不同模式的某些高级概念的设计部分 - 很显然,找出对象部分相对较容易,因此请专注于一些其他部分。您的基本代码(或 .NET Framework)中哪些部分本质上是过程或元编程?
顺便提一下,如果您想要了解某个特定主题,欢迎给我留言,我将会考虑尝试在完成此特定系列后立即将其列入计划。毕竟在真正意义上,这是你们的专栏。
祝您工作愉快!
Ted Neward 是 Neward & Associates 的负责人,这是一家专门研究企业 .NET Framework 系统和 Java 平台系统的独立公司。他曾写过 100 多篇文章,是 C# 领域最优秀的专家之一并且是 INETA 发言人,著作或合著过十几本书,包括即将出版的《Professional F# 2.0》(Wrox)。他定期提供咨询和指导。您可通过 ted@tedneward.com 与他联系,也可通过 blogs.tedneward.com 访问其博客。