M
mypine
Unregistered / Unconfirmed
GUEST, unregistred user!
程式设计杂谈
1 关于对象生命周期的问题
佛问沙门:人命在几间?对曰:数日间;佛言:子未知道:复问一沙门:人命在几间?对曰:饭食间;佛言:子未知
道;复问一沙门:人命在几间?对曰:呼吸间。佛言。善哉。子知道矣。——《四十二章经》
命之所在,生之所存,由人及彼,不外如此。对象之命,亦在于此,有始有终,呼吸之间!
OOP编程发展到今天,已经出现了一股不可逆流之势,如日中天的Java, 涛声依旧的C++, 风韵犹存的SmallTalk, 以及端庄贤惠的Object Pascal, 还有刚刚诞生的C#, 这对OOP Language都为OOP注入了巨大的活力。
在OOP的世界中, 多个对象的协同工作演绎了一个系统的兴衰。不管是Statefull Object还是Stateless Object, 它们都需要经历诞生—>协同(此处的”协同”指的是对象之间的交互)—>涅磐(指对象的消亡)的过程, 这便是几乎每一个对象的宿命。这个线性的过程就跟人类的生命一样, 我称它为对象的生命周期(Object Life Priods, 简称OLP)。 ——如果你的对象还没有诞生, 你便意图使它处于协同的关系之中,或者, 你的对象已经涅磐了, 你仍然意图使它处于某种协同的关系之中, 那么, 很不幸,这种对于对象的生命周期的违背使得你的系统加速了死亡的进程。
这便是OLP的原则——对象必须创建后, 才能使用; 对象在消亡后,除非创建, 不能使用。
即使没有多少人注意, 但是, 无疑, 你的每一个OOP Program中的对象会遵守上面的原则, 没有丝毫的逆反。
在一个较小的Project中, 由OLP所产生的问题可能还不太严重, 但是, 在一些稍大的Project中, 它所带来的问题有的时候可能导致Project瘫痪较短的时间, 而且, 它还会不定时的出现, 所以, 避免与解决这个问题就是今天的主题。
(1) Java对象的OLP: 熟悉Java的朋友可能大都知道, Java Object需要我们手工编码让它产生, 以及以后的协同,但是, 对于它的消亡, 我们却从不关心, 为什么呢? 因为Java Virtual Machine提供了GC(Garbage Collect)机制,保证了对于OLP原则中后者的遵守,也就是说, 你决不会用到已经消亡的Java Object,这样, 对于OLP问题, 在Java的世界中,就降低了一半的出错机会。 对于OLP的第一个原则,在实际的编码中, 通常是不大可能违背的,因为, 即使对于没有经验的Java Programmer而已,他们也会在短时间内遵守这个原则。 所以, 使用Java的Programmer可以说, 我们无需关心OLP。下面是一段Java代码,以此说明的:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
文件名:Car.Java
package OLPTest.JavaTest;
/**
* Title:
* Description:
* Copyright: Copyright (c) 2000
* Company:
* @author
* @version 1.0
*/
class Engine{
public void start(){}
public void rev(){}
public void stop(){}
}
class Wheel{
public void inflate(int psi){};
}
class Window{
public void rollup(){}
public void rolldown(){}
}
classdo
or{
public Window window=new Window();
public void open(){}
public void close(){}
}
public class Car {
public Engine engine=new Engine();
public Wheel[] wheel=new Wheel[4];
publicdo
or left=newdo
or(),
right=newdo
or();
public Car() {
for (int i = 0;
i < 4;
i++)
wheel=new Wheel();
}
public static void main(String args[]){
Car car=new Car();
car.left.window.rollup();
car.wheel[0].inflate(72);
}
}
上面的Java代码例子中,声明了Engine、Wheel、Window、Door、Car类,其中,只有Car类被输出。在Car类中,声明了一个Engine的实例engine,声明了一个Wheel类的数组,长度为四,并且创建了它,声明了两个Door类的实例,它们作为Car类的公共成员,在Car类被创建的时候也会随之被创建,但是各位观察代码,并不会发现有释放该几个对象的地方,这就是利用了Java的GC机制。另外,在Car类的静态方法main中,声明并创建了一个Car的实例,也没有释放它,在创建之后,直接使用,然后程序结束。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(2) C#对象的OLP: 新出炉的C#在这个方面极力模仿Java, 因此,C#的Programmer只要遵守了OLP的第一个原则 ,也是不用担心OLP会带来的问题的。下面是一段关于C#的代码:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
文件名:examClassLib.cs
using System;
namespace examClassLibrary
{
class Engine
{
public void start(){}
public void rev(){}
public void stop(){}
}
class Wheel
{
public void inflate(int psi){}
}
class Window
{
public void rollup(){}
public void rolldown(){}
}
classdo
or
{
public Window window=new Window();
public void open(){}
public void close(){}
}
public class Car
{
private Engine engine = new Engine();
private Wheel[] wheel = new Wheel[4];
privatedo
or left = newdo
or(),
right = newdo
or();
public Car()
{
for (int i = 0;
i < 4;
i++)
wheel=new Wheel();
}
public static void main(String[] args)
{
Car car=new Car();
car.left.window.rollup();
car.wheel[0].inflate(72);
}
}
}
参考Java语言下面的说明。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(3) Object Pascal的OLP:Object Pascal语言本身不像Java或者C#一样, 提供了GC能力, 所以, 对于OLP的两大原则我们必须时刻注意, 不能违反。在设计阶段建立的两个类型之间的关联, 在实际编码的时候, 保证在使用协同对象的时候它已经诞生, 并且, 还没有被消亡。工作于Object Pascal之上的著名的Delphi提供了一套框架VCL, VCL在对待OLP这个问题的时候, 提供了一个不知是聪明还是愚蠢的解决办法。那就是在VCL中, 自TComponent以下,在使用它们时候, 你可以形成一个对象消亡责任链。在TComponent中, 有一个属性为Owner, 它便是指向该对象的创建者(当然,在创建该对象的时候, 使用Contructor Create(Aowner: TComponent)这个构造器, 传过去的参数必须是一个有效值,注意:此处当区别于Tcontrol的Parnet属性。), 当该属性有值的时候, VCL提供了该对象的消亡可以由Owner所指于的对象消亡的能力,也就是说, 你可以使用上面的构造器创建, 而可以不用消亡它, 却不会造成内存遗漏的问题。 但是这样又会带来另外一个问题:
在上图中, 对象A负责了B与C, 如果对象A消亡的时候, 那么B与C就一起消亡了, 而这个时候, Object D仍然在使用Object C,无疑, OLP问题就产生了。这也就是使用VCL框架提供的对象消亡的能力会带来的坏处。 当然, 上面的这种设计(注意:设计上的缺陷)是非常有缺陷的,也就是说, 在OOD的时候就出现了问题,不过, 上图却是一些工程中的普遍情况,因此,除了在设计上避免之外;如果实在不能避免的话, 可以使用我下一节将要提到的方法带避免这个问题。
另外一点就是,Object Pascal语言本身所引起的:
在Object Pascal中, 每一个对象的引用实际上是一个指针, 如上图, 有变量B与C指向对象A, 如果对象A消亡了,而我们使用对象B与C的代码如下:
if Assigned(B) then
begin
{Do something:
B.XXX}
end;
或
if C <> nil then
begin
{Do Something
C.XXX}
end;
理论上而言, 当对象A消亡是, B与C应该不具有任何意义, 所以, 上面的两段代码都应该得到正确的执行, 实际上却不是如此。因为Object Pascal中的对象引用不是真正的引用,因此,一个Object Pascal对象不知道有多少个引用指向它(上图应该有两个引用),所以当对象A消亡时,仍然无任何变化,因此, 上面的两段代码中的判断语句都会得到True结果, 进而去执行里面的{Do something},而产生错误的流程, 这就是Object Pascal语言机制所带来的。(注:有时,即使在对象已经消亡的情况下,上面两段代码执行也不会出错, 当然这由这是由于内存管理带给它的, 出错是必然的, 不出错是偶然的。可以参考操作系统的内存管理就明白它为什么在对象消亡的情况下也不会出错了。)
(4) C++的OLP:其病态也与Object Pascal大致一致。不再赘述了。
(5) 解决OLP的问题的一般方法:Java及C#的OLP问题上面已经讲清楚了,大家也就明白,于它们而言, 是几乎无需要解决的。至于C++的OLP问题的解决, 下一篇文章会谈到。下面主要谈一下Object Pascal的OLP解决方案:
针对问题一, 建议大家在使用TComponent的Descendent时,除非明确知道对象的OLP,否则, 不应该使用Owner来负责释放。但是在一般情况下,如果在设计阶段合理的话, 那么对象的释放可以交由Owner。
针对问题二,如果需要多个引用指向一个对象的话, 建议大家使用Property关键字做到这一点, 而避免直接声明数据成员。如下:
TA = class
end;
TB = class
Private
FA: TA;
Public
Property A1: TA read FA;
Property A2: TA read FA;
end;
以后, 即使FA被释放, 使用A1与A2也决不会出错。
2 关于接口在多人合作开发中的建议
没有规矩,则不成方圆。——俗语
团队的合作开发在今天已经是一股不可逆转之势了。没有人可以想像仅凭一人之力而可以完成诸如Microsoft Word这样的软件了。那个裘伯君的WPS的个人英雄时代已经渐渐地淡出了历史的舞台了。
在合作开发中,开发者共事的方式通常可以分为横向合作与纵向合作两种模式。
所谓横向合作,我猜测国内的开发界目前大多都同此类,这种合作方式的巨大特征就是开发者之间的工作是平行的(这种并行不是指时间并行,是指功能上是并行的),常见的那种一个开发人员一个模块,每个模块做完了再合并在一起就是一个典型。“各人自扫门前雪,休管他人瓦上霜”是这种模式的贴切描述。当然,这种模式的正确错误与否,我也就不多言了,它已经超出了本文的讨论范围,已经是软件过程控制与软件系统分析、设计领域的问题了。
关于纵向合作,是指开发人员之间在开发时,功能上呈现出有秩序的依赖,比如说A与B两位合作,他们需要做一个类似于Power Designer(用来进行数据库的逻辑与物理设计的工具)的工具,A负责建立数据库模型的抽象,B负责流化该模型,从功能上而言,后者完全依赖于前者的工作,像这种开发者之间的合作与依赖就被我称之为纵向合作。
下面我将要谈到的就是,利用接口降低开发人员之间的耦合程度,特别是在纵向合作之中。在此处的接口的意指的是GoF的《Design Pattern》中对接口的定义,它所对应的语法范畴,在Object Pascal、Java、C#中都为:Interface,在C++中为纯虚类,另外,在Object Pascal中也可以为Pure Abstract Class(意指所有的方法都是Abstract Method)代替。说白了,就是指声明,而不是在实现上的依赖,这样的话,纵向合作的依赖就降低很多了,虽然横向合作的依赖咋看之下比纵向合作要少得多,但是它在代码重用方面却是相形见拙的,这也就提出了一个问题:关于代码重要的程度与成员之间及代码之间的依赖程度的平衡点。(该问题的讨论见以后的文章)
实际上,接口本身的特征就已经是这个问题的答案了。
接口的特征就是只谈声明,不谈实现,也就是只谈是什么,不谈怎么样去做,这样的话,大伙儿就可以根据这个是什么去自由发挥,做出自己的实现体。W3C组织制订了很多规范,但是它作为一个非盈利性的组织,不制作实现,这样就有点类似于接口。
下面举一个例子,语言用Object Pascal。
{待补}
(题外话:相信大多数人一定知道Java,但是对于Java世界的运作模式,可能就不太清楚。Java的世界是一个真正自由的世界,我想这是James Gosling本人也始料不及的。现在,Sun Microsoft与IBM及Borland、Symantec、IBM、BEA、Oracle等公司一起成立了一个JCP组织,这个组织里面包含了上面公司的一些成员。关于Java领域的一切规范都是由这个组织所制定出来的,无论是J2EE、J2SE、J2ME、JMS、JNDI等等这些全是由JCP经过一系列严格的步骤产生的。而这些规范的实现却有大大小小很多公司去做,有商业的, 有免费的,比如J2EE Servlet Container/Server,就有IBM的WebSphere,Bea的WebLogic,Borland的Borland Application Server,还有Jboss这样一个免费的东西等等,这也是声明与实现分开的一个例子。)
3 Delphi DataBase Application Architecture
将面向对象进行到底…
A
一个Delphi Programmer的痛苦是很多的,特别是Delphi Database programmer,尽管Delphi被鼓吹为最佳的Database programming language——此话不假,但是我们的痛苦并没有因此而减少。
任何一个曾经做过delphi databsae Application的Programmer都会有这样的痛苦:我们不得不为我们的application添加一个new feature,但是似乎却无从下手;我们的database structure结构改变了,所以我们不得不花大量的时间去修改我们的program,即使前者是小量的改动;我们要离开公司了,newbie来了,但是我们却无法清晰地告诉他(或她)我们程序的结构,甚至连我们自己也不明白了;又接到客户的电话了,我不得不修改界面,但是,似乎改动的地方超乎我的估计——糟糕,为什么这个button始终不变灰,终于在别人的代码里找到原因了;我们的程序已经不能修改了,它们快爆炸了;我们不得不重做——于是再一次的噩梦开始了。
很羡慕Java programmer,他们是幸福的,至少他们获得幸福的机会远大于我们,他们可以拥抱J2EE,后者可以说强制性地要求他们的Program呈现良构。还有很多open source的工具在等着他们的选择(castor, jboss, enhydra等),这些工具也给他们提供了设计良构程序的机会。
不用惊讶于二者之间的天壤之别,这是所有Delphi programmer应得的,除非我们从根本上改变。在众多的Delphi programmer中,他们了解database有时甚至超过了解Delphi本身,因为,几乎所有的Delphi programmer在进行database programming的时候,他们面向的是data,是record,Field, Primary Key, Index这些数据库概念,很可笑,在oop口号如此响亮的今天,我们却不得不面向数据。
B
关于数据库的封装问题, 这实际上牵扯到一个效率问题, 在J2EE的框架中, 提出了真正面向对象的Session Bean与Entity Bean, 以及对应于此的许多工具, 比如像IBM的sanfrancisco工程,Castor等,他们的出现标志着O/R mapping技术的成熟与走向实用, 但是在Java中, 使用它们的话,效率是比较低下的(每一个Ejb Container可以承受高达几十万个的EJB,即使在这种情况下,效率也低下), 所以如果在Delphi中实现O/R Mapping的话,其效率问题一定是一个瓶颈。(关于O/R Mapping,可以参见http://www.thoughtinc.com上面的cocobase); 另外一个实现的途径是面向对象的数据库(它就像O/R Mapping一样可以直接从数据库中Query一个对象, 或将对象流进数据库), 可是这个方面的应用非常生涩, 到目前为止, 它的应用还处在可以说是萌芽阶段,因为现有的关系数据库应用程式要转向面向对象的, 需要很大的成本, 其次就是关系型的数据库及其对应的产品非常成熟与丰富, 这也是面向对象数据库不足以流行的原因(参照http://www.odbmsfacts.com/object-database.htm以及面向对象的查询语言OQL——http://www.odmg.org/)。
附:
三层架构的状态问题
这个问题的确比较让人头痛——在delphi的Midas情况下, 但是只要有了O/R Mapping存在的话, 它就可以解决了。 必须记住的是,是不可能从逻辑上避免无状态的, 所以有状态对象的存在是必然的,问题在于如何理清哪些对象应该是stateless, 哪些是statefull的。
客户属性定制问题
不错,这是一个非常头痛的问题。如果将它转化进技术领域, 那么它就成了这样一个问题了——对象动态界面问题(所谓界面, 是指对象暴露给client的interface,比如在Delphi中的Public及Published部分, 或者是一个接口), 换句话说就是如何动态的决定一个对象的属性,在Martin fowler的《分析模式》中,有关于这个方面的详细论述,大家可以去参考一下, 在Delphi里面的实现思路就像TField这个类的定义一样, 在TField中, 给定了一个属性那就是Data Type, 另外就是Type Check,这些都可以被用来进行动态属性的设计。另外,楼上有个兄弟说得好, RTTI与Visitor的合作也有异曲同工之妙!
C一个例子
见http://www.techinsite.com.au/
4 设计模式的几点看法
无心之心,道之所存——《建筑的永恒之道》
续……
1 关于对象生命周期的问题
佛问沙门:人命在几间?对曰:数日间;佛言:子未知道:复问一沙门:人命在几间?对曰:饭食间;佛言:子未知
道;复问一沙门:人命在几间?对曰:呼吸间。佛言。善哉。子知道矣。——《四十二章经》
命之所在,生之所存,由人及彼,不外如此。对象之命,亦在于此,有始有终,呼吸之间!
OOP编程发展到今天,已经出现了一股不可逆流之势,如日中天的Java, 涛声依旧的C++, 风韵犹存的SmallTalk, 以及端庄贤惠的Object Pascal, 还有刚刚诞生的C#, 这对OOP Language都为OOP注入了巨大的活力。
在OOP的世界中, 多个对象的协同工作演绎了一个系统的兴衰。不管是Statefull Object还是Stateless Object, 它们都需要经历诞生—>协同(此处的”协同”指的是对象之间的交互)—>涅磐(指对象的消亡)的过程, 这便是几乎每一个对象的宿命。这个线性的过程就跟人类的生命一样, 我称它为对象的生命周期(Object Life Priods, 简称OLP)。 ——如果你的对象还没有诞生, 你便意图使它处于协同的关系之中,或者, 你的对象已经涅磐了, 你仍然意图使它处于某种协同的关系之中, 那么, 很不幸,这种对于对象的生命周期的违背使得你的系统加速了死亡的进程。
这便是OLP的原则——对象必须创建后, 才能使用; 对象在消亡后,除非创建, 不能使用。
即使没有多少人注意, 但是, 无疑, 你的每一个OOP Program中的对象会遵守上面的原则, 没有丝毫的逆反。
在一个较小的Project中, 由OLP所产生的问题可能还不太严重, 但是, 在一些稍大的Project中, 它所带来的问题有的时候可能导致Project瘫痪较短的时间, 而且, 它还会不定时的出现, 所以, 避免与解决这个问题就是今天的主题。
(1) Java对象的OLP: 熟悉Java的朋友可能大都知道, Java Object需要我们手工编码让它产生, 以及以后的协同,但是, 对于它的消亡, 我们却从不关心, 为什么呢? 因为Java Virtual Machine提供了GC(Garbage Collect)机制,保证了对于OLP原则中后者的遵守,也就是说, 你决不会用到已经消亡的Java Object,这样, 对于OLP问题, 在Java的世界中,就降低了一半的出错机会。 对于OLP的第一个原则,在实际的编码中, 通常是不大可能违背的,因为, 即使对于没有经验的Java Programmer而已,他们也会在短时间内遵守这个原则。 所以, 使用Java的Programmer可以说, 我们无需关心OLP。下面是一段Java代码,以此说明的:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
文件名:Car.Java
package OLPTest.JavaTest;
/**
* Title:
* Description:
* Copyright: Copyright (c) 2000
* Company:
* @author
* @version 1.0
*/
class Engine{
public void start(){}
public void rev(){}
public void stop(){}
}
class Wheel{
public void inflate(int psi){};
}
class Window{
public void rollup(){}
public void rolldown(){}
}
classdo
or{
public Window window=new Window();
public void open(){}
public void close(){}
}
public class Car {
public Engine engine=new Engine();
public Wheel[] wheel=new Wheel[4];
publicdo
or left=newdo
or(),
right=newdo
or();
public Car() {
for (int i = 0;
i < 4;
i++)
wheel=new Wheel();
}
public static void main(String args[]){
Car car=new Car();
car.left.window.rollup();
car.wheel[0].inflate(72);
}
}
上面的Java代码例子中,声明了Engine、Wheel、Window、Door、Car类,其中,只有Car类被输出。在Car类中,声明了一个Engine的实例engine,声明了一个Wheel类的数组,长度为四,并且创建了它,声明了两个Door类的实例,它们作为Car类的公共成员,在Car类被创建的时候也会随之被创建,但是各位观察代码,并不会发现有释放该几个对象的地方,这就是利用了Java的GC机制。另外,在Car类的静态方法main中,声明并创建了一个Car的实例,也没有释放它,在创建之后,直接使用,然后程序结束。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(2) C#对象的OLP: 新出炉的C#在这个方面极力模仿Java, 因此,C#的Programmer只要遵守了OLP的第一个原则 ,也是不用担心OLP会带来的问题的。下面是一段关于C#的代码:
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
文件名:examClassLib.cs
using System;
namespace examClassLibrary
{
class Engine
{
public void start(){}
public void rev(){}
public void stop(){}
}
class Wheel
{
public void inflate(int psi){}
}
class Window
{
public void rollup(){}
public void rolldown(){}
}
classdo
or
{
public Window window=new Window();
public void open(){}
public void close(){}
}
public class Car
{
private Engine engine = new Engine();
private Wheel[] wheel = new Wheel[4];
privatedo
or left = newdo
or(),
right = newdo
or();
public Car()
{
for (int i = 0;
i < 4;
i++)
wheel=new Wheel();
}
public static void main(String[] args)
{
Car car=new Car();
car.left.window.rollup();
car.wheel[0].inflate(72);
}
}
}
参考Java语言下面的说明。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
(3) Object Pascal的OLP:Object Pascal语言本身不像Java或者C#一样, 提供了GC能力, 所以, 对于OLP的两大原则我们必须时刻注意, 不能违反。在设计阶段建立的两个类型之间的关联, 在实际编码的时候, 保证在使用协同对象的时候它已经诞生, 并且, 还没有被消亡。工作于Object Pascal之上的著名的Delphi提供了一套框架VCL, VCL在对待OLP这个问题的时候, 提供了一个不知是聪明还是愚蠢的解决办法。那就是在VCL中, 自TComponent以下,在使用它们时候, 你可以形成一个对象消亡责任链。在TComponent中, 有一个属性为Owner, 它便是指向该对象的创建者(当然,在创建该对象的时候, 使用Contructor Create(Aowner: TComponent)这个构造器, 传过去的参数必须是一个有效值,注意:此处当区别于Tcontrol的Parnet属性。), 当该属性有值的时候, VCL提供了该对象的消亡可以由Owner所指于的对象消亡的能力,也就是说, 你可以使用上面的构造器创建, 而可以不用消亡它, 却不会造成内存遗漏的问题。 但是这样又会带来另外一个问题:
在上图中, 对象A负责了B与C, 如果对象A消亡的时候, 那么B与C就一起消亡了, 而这个时候, Object D仍然在使用Object C,无疑, OLP问题就产生了。这也就是使用VCL框架提供的对象消亡的能力会带来的坏处。 当然, 上面的这种设计(注意:设计上的缺陷)是非常有缺陷的,也就是说, 在OOD的时候就出现了问题,不过, 上图却是一些工程中的普遍情况,因此,除了在设计上避免之外;如果实在不能避免的话, 可以使用我下一节将要提到的方法带避免这个问题。
另外一点就是,Object Pascal语言本身所引起的:
在Object Pascal中, 每一个对象的引用实际上是一个指针, 如上图, 有变量B与C指向对象A, 如果对象A消亡了,而我们使用对象B与C的代码如下:
if Assigned(B) then
begin
{Do something:
B.XXX}
end;
或
if C <> nil then
begin
{Do Something
C.XXX}
end;
理论上而言, 当对象A消亡是, B与C应该不具有任何意义, 所以, 上面的两段代码都应该得到正确的执行, 实际上却不是如此。因为Object Pascal中的对象引用不是真正的引用,因此,一个Object Pascal对象不知道有多少个引用指向它(上图应该有两个引用),所以当对象A消亡时,仍然无任何变化,因此, 上面的两段代码中的判断语句都会得到True结果, 进而去执行里面的{Do something},而产生错误的流程, 这就是Object Pascal语言机制所带来的。(注:有时,即使在对象已经消亡的情况下,上面两段代码执行也不会出错, 当然这由这是由于内存管理带给它的, 出错是必然的, 不出错是偶然的。可以参考操作系统的内存管理就明白它为什么在对象消亡的情况下也不会出错了。)
(4) C++的OLP:其病态也与Object Pascal大致一致。不再赘述了。
(5) 解决OLP的问题的一般方法:Java及C#的OLP问题上面已经讲清楚了,大家也就明白,于它们而言, 是几乎无需要解决的。至于C++的OLP问题的解决, 下一篇文章会谈到。下面主要谈一下Object Pascal的OLP解决方案:
针对问题一, 建议大家在使用TComponent的Descendent时,除非明确知道对象的OLP,否则, 不应该使用Owner来负责释放。但是在一般情况下,如果在设计阶段合理的话, 那么对象的释放可以交由Owner。
针对问题二,如果需要多个引用指向一个对象的话, 建议大家使用Property关键字做到这一点, 而避免直接声明数据成员。如下:
TA = class
end;
TB = class
Private
FA: TA;
Public
Property A1: TA read FA;
Property A2: TA read FA;
end;
以后, 即使FA被释放, 使用A1与A2也决不会出错。
2 关于接口在多人合作开发中的建议
没有规矩,则不成方圆。——俗语
团队的合作开发在今天已经是一股不可逆转之势了。没有人可以想像仅凭一人之力而可以完成诸如Microsoft Word这样的软件了。那个裘伯君的WPS的个人英雄时代已经渐渐地淡出了历史的舞台了。
在合作开发中,开发者共事的方式通常可以分为横向合作与纵向合作两种模式。
所谓横向合作,我猜测国内的开发界目前大多都同此类,这种合作方式的巨大特征就是开发者之间的工作是平行的(这种并行不是指时间并行,是指功能上是并行的),常见的那种一个开发人员一个模块,每个模块做完了再合并在一起就是一个典型。“各人自扫门前雪,休管他人瓦上霜”是这种模式的贴切描述。当然,这种模式的正确错误与否,我也就不多言了,它已经超出了本文的讨论范围,已经是软件过程控制与软件系统分析、设计领域的问题了。
关于纵向合作,是指开发人员之间在开发时,功能上呈现出有秩序的依赖,比如说A与B两位合作,他们需要做一个类似于Power Designer(用来进行数据库的逻辑与物理设计的工具)的工具,A负责建立数据库模型的抽象,B负责流化该模型,从功能上而言,后者完全依赖于前者的工作,像这种开发者之间的合作与依赖就被我称之为纵向合作。
下面我将要谈到的就是,利用接口降低开发人员之间的耦合程度,特别是在纵向合作之中。在此处的接口的意指的是GoF的《Design Pattern》中对接口的定义,它所对应的语法范畴,在Object Pascal、Java、C#中都为:Interface,在C++中为纯虚类,另外,在Object Pascal中也可以为Pure Abstract Class(意指所有的方法都是Abstract Method)代替。说白了,就是指声明,而不是在实现上的依赖,这样的话,纵向合作的依赖就降低很多了,虽然横向合作的依赖咋看之下比纵向合作要少得多,但是它在代码重用方面却是相形见拙的,这也就提出了一个问题:关于代码重要的程度与成员之间及代码之间的依赖程度的平衡点。(该问题的讨论见以后的文章)
实际上,接口本身的特征就已经是这个问题的答案了。
接口的特征就是只谈声明,不谈实现,也就是只谈是什么,不谈怎么样去做,这样的话,大伙儿就可以根据这个是什么去自由发挥,做出自己的实现体。W3C组织制订了很多规范,但是它作为一个非盈利性的组织,不制作实现,这样就有点类似于接口。
下面举一个例子,语言用Object Pascal。
{待补}
(题外话:相信大多数人一定知道Java,但是对于Java世界的运作模式,可能就不太清楚。Java的世界是一个真正自由的世界,我想这是James Gosling本人也始料不及的。现在,Sun Microsoft与IBM及Borland、Symantec、IBM、BEA、Oracle等公司一起成立了一个JCP组织,这个组织里面包含了上面公司的一些成员。关于Java领域的一切规范都是由这个组织所制定出来的,无论是J2EE、J2SE、J2ME、JMS、JNDI等等这些全是由JCP经过一系列严格的步骤产生的。而这些规范的实现却有大大小小很多公司去做,有商业的, 有免费的,比如J2EE Servlet Container/Server,就有IBM的WebSphere,Bea的WebLogic,Borland的Borland Application Server,还有Jboss这样一个免费的东西等等,这也是声明与实现分开的一个例子。)
3 Delphi DataBase Application Architecture
将面向对象进行到底…
A
一个Delphi Programmer的痛苦是很多的,特别是Delphi Database programmer,尽管Delphi被鼓吹为最佳的Database programming language——此话不假,但是我们的痛苦并没有因此而减少。
任何一个曾经做过delphi databsae Application的Programmer都会有这样的痛苦:我们不得不为我们的application添加一个new feature,但是似乎却无从下手;我们的database structure结构改变了,所以我们不得不花大量的时间去修改我们的program,即使前者是小量的改动;我们要离开公司了,newbie来了,但是我们却无法清晰地告诉他(或她)我们程序的结构,甚至连我们自己也不明白了;又接到客户的电话了,我不得不修改界面,但是,似乎改动的地方超乎我的估计——糟糕,为什么这个button始终不变灰,终于在别人的代码里找到原因了;我们的程序已经不能修改了,它们快爆炸了;我们不得不重做——于是再一次的噩梦开始了。
很羡慕Java programmer,他们是幸福的,至少他们获得幸福的机会远大于我们,他们可以拥抱J2EE,后者可以说强制性地要求他们的Program呈现良构。还有很多open source的工具在等着他们的选择(castor, jboss, enhydra等),这些工具也给他们提供了设计良构程序的机会。
不用惊讶于二者之间的天壤之别,这是所有Delphi programmer应得的,除非我们从根本上改变。在众多的Delphi programmer中,他们了解database有时甚至超过了解Delphi本身,因为,几乎所有的Delphi programmer在进行database programming的时候,他们面向的是data,是record,Field, Primary Key, Index这些数据库概念,很可笑,在oop口号如此响亮的今天,我们却不得不面向数据。
B
关于数据库的封装问题, 这实际上牵扯到一个效率问题, 在J2EE的框架中, 提出了真正面向对象的Session Bean与Entity Bean, 以及对应于此的许多工具, 比如像IBM的sanfrancisco工程,Castor等,他们的出现标志着O/R mapping技术的成熟与走向实用, 但是在Java中, 使用它们的话,效率是比较低下的(每一个Ejb Container可以承受高达几十万个的EJB,即使在这种情况下,效率也低下), 所以如果在Delphi中实现O/R Mapping的话,其效率问题一定是一个瓶颈。(关于O/R Mapping,可以参见http://www.thoughtinc.com上面的cocobase); 另外一个实现的途径是面向对象的数据库(它就像O/R Mapping一样可以直接从数据库中Query一个对象, 或将对象流进数据库), 可是这个方面的应用非常生涩, 到目前为止, 它的应用还处在可以说是萌芽阶段,因为现有的关系数据库应用程式要转向面向对象的, 需要很大的成本, 其次就是关系型的数据库及其对应的产品非常成熟与丰富, 这也是面向对象数据库不足以流行的原因(参照http://www.odbmsfacts.com/object-database.htm以及面向对象的查询语言OQL——http://www.odmg.org/)。
附:
三层架构的状态问题
这个问题的确比较让人头痛——在delphi的Midas情况下, 但是只要有了O/R Mapping存在的话, 它就可以解决了。 必须记住的是,是不可能从逻辑上避免无状态的, 所以有状态对象的存在是必然的,问题在于如何理清哪些对象应该是stateless, 哪些是statefull的。
客户属性定制问题
不错,这是一个非常头痛的问题。如果将它转化进技术领域, 那么它就成了这样一个问题了——对象动态界面问题(所谓界面, 是指对象暴露给client的interface,比如在Delphi中的Public及Published部分, 或者是一个接口), 换句话说就是如何动态的决定一个对象的属性,在Martin fowler的《分析模式》中,有关于这个方面的详细论述,大家可以去参考一下, 在Delphi里面的实现思路就像TField这个类的定义一样, 在TField中, 给定了一个属性那就是Data Type, 另外就是Type Check,这些都可以被用来进行动态属性的设计。另外,楼上有个兄弟说得好, RTTI与Visitor的合作也有异曲同工之妙!
C一个例子
见http://www.techinsite.com.au/
4 设计模式的几点看法
无心之心,道之所存——《建筑的永恒之道》
续……