真正的Delphi面向对象编程(一)
封装了商业规则的类是真正面向对象编程的基础
这篇文章我们会涉及程序设计的各个方面,并对质疑一些我们写Delphi程序的惯用方式。这些设计方法背后的基础概念是封装:设计一组清楚定义接口(方法)类,由这些方法去操作他们的属性。这一概念将会贯串整个程序并对数据如何保存和呈现有很大影响。我愿意介绍读者学习Francis Glassborow''s关于C++的文章,尽管语言不同,但是优秀的类设计理念是和语言无关的。
现今大部分Delphi写的程序都不是面向对象的。只是语言中有对象的模型并使用了原有的或新的类,这并不意味者程序是真正的面向对象。代码的重用随着第三方控件被拖到窗口上而结束,窗口和单元之间的相互依赖却在迅速扩散。(!!!)如果将来要改变程序的基础(如切换不同数据库或从两层结构变成三层结构)会严重受阻或花费昂贵。如果是真正按面向对象的方式写的程序则会很方便而不是受限制。当然要写这样的程序需要理念的提升,而且开始的时候缺乏生产力,大部分开发团队都不情愿这样或作考虑。我希望通过这篇文章向大家示范如何能写出更好的程序。最终使系统更可靠,容易维护,风格一致,灵活,可重用,比用传统方式写的程序运行的更好。特别是对大型的程序,代码清晰并真正面向对象的程序会比传统方式写的同样程序需要更少的维护资源。
面向对象的程序更高的可靠性来自于数据和操作被封装在明确定义的类中。编译器通过强大的类型检查促使代码中正确的类、方法和属性,对未来一个改动会影响整个程序的代码不应让人有误解代码意图的可能。正确使用类会是类之间的关系是自明的,并且大部分的代码真正关注程序的关键部分(meat),而不是考虑像数据如何持久存储这样的细节问题。贯串代码的简单性和一致性将使程序的维护性显著提高。正如我们将看到的一样,广泛使用类继承增加了生产力和可靠性,并且增强了一致性。这些一致性体现在所展示的代码中,包括类的行为、数据如何存储和用户界面如何呈现数据。由于大部分功能在基类中提供,可以通过快速改变他们的行为来从根本上改变程序。(如用户交互界面从窗口驱动形式改为以html为基础)这些基类可以设计为与程序无关,这样第二个类似的程序将在生产力上立即得到推动。对一个中等的程序一组优秀的基类可以提供高达%50的显著提升,从时间,开支到可靠性。首先要强调的是切换到真正的面向对象开发并非琐碎小事,第一次应保证有丰富经验的协助,或者程序较小且没有紧急的交付期限;还要说明的是一个面向对象(OO)的解决方案并不是规定在程序中哪些类该用哪些类不该用。如果一家公司开发了自己的可视化控件,或者使用第三方的可视化控件,
核心类
设计任何面向对象程序的第一步是考虑哪些必须的类。这是绝对基本的一步,要有其他各种开发的技术保证,因为早期阶段的错误要改正将花费昂贵。在设计我们的类时我们一般努力实现低耦合高内聚-类与类之间尽量地独立,但又可以通过某种强大地方式复合。实现这一目标地一个方式是把类按照在它们程序中所扮演地不同角色来划分成不同地类别。对这些角色的正确选择将会形成一组内聚的类。
在对类的角色设计中有一个贯串始终的基本原则:把类的责任分为表现、应用和持久化存储数据(典型地在数据库中)。虽然这和三层数据库程序地划分是一样的,要提示的是这种划分的概念可以在多种环境中实现:从单片机程序到分布式多层程序。组成应用逻辑的这一组类负责最为困难的工作,例如响应用户操作和处理数据的请求。这一层类中有一部分类表现了真实世界的实体,并被系统所模型化。这些类常被称为“商业类”或“问题域类”。它们构成任何面向对象程序至关重要的部分,因为其它的类将通过某种方式支持这些类,它们成为所有开发者关注的焦点。
识别一个特定程序中有哪些商业对象一般是经验的本能,虽然这过程里面有一个完整的学科(或是艺术?)面向对象与传统技术如SSADM(1)相比的优点贯串于整个分析设计和维持实体的过程:可以通过面向对象分析(OOA)、面向对象设计(OOD)和面向对象编程(OOP)来表现每个商业对象。我们会在后面探寻识别合适的商业对象的部分技巧。首先我们假设下面这些过程已经完成。
不同层(表现、应用和持久)间的类的互相通信已经清楚定义并且相互连接。这些类的关系如图1
图中的箭头显示同一个层中的类可以调用另一个类中的方法。这张图同时说明表现层(用户界面)的类可以操作应用层或用户界面中的其他类,应用层(商业对象)可以操作应用层的类和调用持久层的方法,持久层只回应应用层的请求。
商业对象
为了演示商业对象是如何实现的,我们接下来将看到一个简化的程序,其中有库存,客户和订单实体(某个公司提供一定数量的货物,客户来买这些货物)。我们最初的分析确定需要三个商业对象表现这些实体。在我们的Delphi程序中将有三个类:TStockItem, TCustomer 和Torder。清单1中显示了这些类的公共接口。这里需要指出这些类的特性是通过属性(Property)暴露给外部的:这是一个简单而有益的类设计并且通过属性控制外部的访问。还有就是这些属性应该在类的published区域(原因会在后面提到),并被赋给合适的默认default值。虽然这些属性限定只在流化类时被用到,会给每个属性合适的初始值并使代码更具自描述性。
第一个类框架
在开发程序的初始阶段,我们已经识别并实现了三个商业对象。这三个对象扮演共同的角色:通过某种方式表现真实世界的实体。因此我们希望它们和真实世界的实体有相似的属性和适当的层次。我们将给每个对象一个共同的基类,叫做TPDObject(Problem Domain object)。随着时间推移我们会在这个类里加入公共的方法和属性,TPDObject将会不断得到扩展。需要注意的是TPDObject不要(永远不要)有任何跟特定程序有关的元素。作为一个程序无关的类它将放在一个独立的单元并形成框架的基础:一组可以在许多程序中重用的类。在后面我们的框架会有很大扩展,形成提供重要的程序无关功能的基类,并可以被迅速用于特定的系统。我们TStockItem、TCustomer 和 Torder就是从TPDObject为特定系统定制对象。
TMyAppPDObject是TPDObject的一个继承类,虽然它的实现部分是空的,但是它是一个很好的示范;如果我们为在特定程序加某些特定元素,并影响程序中所有的问题域类,这样的类层次应该是比较合适的。清单中列出了框架的初期代码。类TPDObject还只提供一个只读属性ID,它的类型是TobjectID。这个属性给了每个对象一个标识符:我们将把相同类型和ID的两个实例认为是同样的对象。这里ID被故意声明为自己的类型是为了避免被直接赋给其它标准类型的值。TobjectID的类型并没有经过特别的选择:在这里我选择了整形,在特定情况下它也可能是一个专门的类。虽然用类作ID好像是更纯的面相对象,但基于我们的问题域类在程序运行中会被创建、释放上千次考虑,为了避免创建和释放对象时额外的负荷,我并没有这样做(作为纯粹主义论者我把TobjectID包含在TPDObject中作为复合的对象)。在代码中有一对函数用于转换字符串和我们的对象标识类型,并且有一个常量作为没有赋值时的初始值。有很多课堂会提到对象标识:有的说所有的商业对象都应该在创建时被赋予一个程序内不重复的标识(甚至是GUID)。实际上,能在给定的上下文中区分不同的对象才是真正重要的,并且保持这一简单性将会在性能和存储空间上有明显的好处。
关于规范的问题:
为了强化一些设计的理念和设计类框架时的做法我将提一些问题,请读者思考它们后面的基本原理是什么。我曾提到对于真正的面向对象设计并不禁止使用特定的类,但有一个很重要的例外。这个例外是什么(答案在图1中)
((( Listing 1 - An application-specific Problem Domain unit (abridged) )))
unit ProblemDomain;
interface
uses
Framework;
type
TMyAppPDObject = class (TPDObject)
end;
TStockItem = class (TMyAppPDObject)
published
property Name: String;
property QuantityInStock: Cardinal default 0;
property TradePrice: Currency;
property RetailPrice: Currency;
end;
TCustomer = class (TMyAppPDObject) … ;
TOrder = class (TMyAppPDObject) … ;
implementation
end.
((( End Listing 1 )))
((( Listing 2 - An application-independent Framework unit )))
unit Framework;
interface
const
NotAssigned = 0;
type
TObjectID = type Integer;
TPDObject = class
private
FID: TObjectID;
public
property ID: TObjectID read FID default NotAssigned;
end;
function StrToID (Value: String): TObjectID;
function IDToStr (Value: TObjectID): String;
implementation
…
end.
((( End Listing 2 )))
(1) SSADM(Structured Systems Analysis & Systems Design)是1981年英国政府的中央电脑及电讯中心 (Central Computer and Telecommunications Agency ,简 称 CCTA ; 网 站 :http://www.ccta.gov.uk )研制的软件分析设计标准方法。
Philip Brown is a systems design consultant and active developer, presenter, and trainer. He''ll promote the benefits of strong OO techniques to deliver better applications given any opportunity. You can contact him at phil@informatica.uk.com.