账号:
密码:
最新动态
产业快讯
CTIMES / 文章 /
面向对象设计的统独问题
大话西游记

【作者: 吳明皓】2004年04月25日 星期日

浏览人次:【12809】

在三国演义中第一回曾开宗明义的提到『话说天下大势,分久必合,合久必分。周末七国分争,并入于秦。及秦灭之后,楚、汉分争,又并入于汉。汉朝自高祖斩白蛇而起义,一统天下;后来光武中兴,传至献帝,遂分为三国。』这说明了历史上的分分合合乃是自然之势。同样的这样情形在软件界也是一样的!?所以如果您曾经留意,其实您可以发现,软件的发展从一开始单纯的应用程序(统一的状态),走向分布式组件模式如DCOM、CORBA等(独立的模式),然后现在再走向整合性 .Net 的模式(统一的状态)。


然而这个现象跟面向对象技术有什么关系呢?其实早在1968年就出现了面向对象的语言Simula68,然而却直到相隔将近二十多年后的1986年,由全禄研究中心(Xexor PARC)出版了两本有关Smalltalk语言的书后,才让面向对象技术获得重视。这其中被咸认为最主要的理由就是在于如C及Pascal等结构性语言,已经无法应付现今越趋复杂的窗口程序、主从式(Master-Detail)架构、多层次(Multi-tier)架构等系统的程序。


可是话又说回来,自从面向对象技术被获得重视之后,大多数的人都相信面向对象技术可以解决掉软件开发上大部分的问题,但是,我们从现今面向对象技术使用的范围被局限于开发工具之中,而真正的用户案例(User Case)却少见的情况来看,这个答案似乎又不能被肯定了。这其中的关键,笔者认为问题就是出在统独的演进之下,所衍生的问题不能够被有效的解决。为了解释这个论点,在接下来的章节中笔者将以实际的案例作进一步阐述。不过在这之前我们先来看一段故事:


大同电视的故事

  • 「话说三十年前,我们家的几个小萝卜头一到傍晚就会到隔壁邻居报到,为的是准时收看六点钟的卡通。虽然偶而可以混一顿晚餐吃,但是毕竟牵扯到颜面问题,笔者的老爹决定为家里面添购一台大同电视。


  • 还记得那时家里第一台电视被送来的模样,木制落地式可左右旋转的外壳,与洗衣机定时器外观类似的选台器,以及一个可以将屏幕遮蔽的对开百叶拉门。比起邻居的老电视,其样式可谓之豪华极了,让当时幼小的心中可以说是充满了一片虚荣。


  • 数年后电视机屏幕出现雪花及影像扭曲的现象,于是我们请服务站的技师来维修,而我们几个小萝卜头则挤在技师后面很热心的帮倒忙,还记得当时拆下电视机后方的甘蔗板后,只见里面的电路板上排列了许多灯泡(其实是真空管)以及密密麻麻的线路。年轻技师这摸摸、那瞧瞧,弄了半天一头大汗还是没搞定,于是他决定回去搬救兵。没多久来了一位老师傅,只见他瞧了屏幕一眼后就跟年轻的技师说:『遇到这种问题,你只要在某某块电路板上,某个电容器上用一个小螺丝起子调整一下就可以了』果然经过这么一下校正之后,电视就回复正常了。顿时让站在老师傅后边的技师,以及我们几个小萝卜头脸上升起一副敬佩的神色。


  • 后来在这三十年中家里陆续换购几台电视,而且只要发生故障,家里都会依例请服务站的人来维修,只是维修的方式,不知不觉中慢慢的开始有些变化。刚开始的时候维修的技师会带着大大的工具箱,然后凭着经验拿着电表慢慢的找,接着再用电焊枪换掉有问题的零件,所以常常维修一个案子需要大半天的时间。后来变成维修的方式改成直接更换电路版或者是整个模块,这样的方式,维修的时间就消耗很少了。而到了近年随着电子类产品的可靠度的大幅提升,维修保固的方式起了巨大的变化,变成直接更换新品,或者是直接建议报废。


  • 因此如果说这三十几年来我们透过电视见证了阿姆斯特朗登月的过程、越战后的惨剧、以及对棒球的狂热,则同样透过大同服务站的技师们,我们也见证了整个电子设备的发展史从真空管时代、演进到晶体管、然后再从晶体管时代演进到集成电路(IC)的时代历程。」



看完上面的故事后,我们大略可以领略到一个事实,那就是电子产业在进入IC时代后,所发生的巨大变革 - 产品开发周期缩短、以及功能上的越趋于丰富多变。那么也许会有人这样问为什么这样的演进呢?这因为必须归功于IC设计上的两大特性,那就是所谓的「独立性」与「可靠度」。其中「独立性」使得各模块之间可以任意的组合,「可靠度」则大幅减少系统整合测试的时间。但是反观在软件设计的领域中,即使使用的是面向对象技术,这样的特性也是并不常见,这是为了什么?为了探索这个问题,我们需要先谈一谈这其中的概念与架构。


角色与场景的交互干扰

曾经有句话是这么说的:「在面向对象的世界里,凡事皆为对象。」没错,这句话的确很贴切的描述了面向对象中的精神,所以我们在很多书籍或者是开发工具中,都可以见到他们是如何的教导人们去规划设计对象的结构(Construct)、建构它们之间的关系(RelationShip),并且更进一步的依此发展出模版(Pattern)的概念。然而可惜的是对于大多数的人而言,这些技术理论的概念并无法获得实际的应用,主要的理由则在于程序设计师们大多注重于塑模的建构,以及程序设计的技巧,却忽略了最重要的「环境」这项因素,以致于在系统愈渐庞大的时候,便发生逻辑切割上的困难,这怎么说呢?我们且先看看下面的一段例子:


第二十二回 八戒大战流沙河 木叉奉法收悟净

  • 「话说唐僧师徒三众,行过了八百黄风岭,正行处,只见一道大水狂澜,浑波涌浪。忽见岸上有一通石碑。上有三个篆字,乃流沙河,师徒们正看碑文,只听得那浪涌如山,波翻若岭,河当中滑辣的钻出一个妖精,十分凶丑: 一头红焰发蓬松,两只圆睛亮似灯......


  • 那怪一个旋风,奔上岸来,径抢唐僧,慌得行者把师父抱住,急登高岸,回身走脱。那八戒放下担子,掣出铁钯,望妖精便筑,那怪使宝杖架住。他两个在流沙河岸,各逞英雄。这一场好斗: 九齿钯,降妖杖,二人相敌河岸上......


  • 他两个来来往往,战经二十回合,不分胜负...那大圣打个忽哨,跳到前边轮起铁棒,望那怪着头一下,那怪急转身,慌忙躲过,径钻入流沙河里。......行者笑道:『贤弟呀,这桩儿我不敢说嘴。水里勾当,老孙不大十分熟。若是空走,还要捻诀,又念念避水咒,方才走得。不然,就要变化做甚么鱼虾蟹鳖之类,我才去得。若论赌手段,凭你在高山云里,干甚么蹊跷异样事儿,老孙都会,只是水里的买卖,有些儿榔杭。』八戒道:『老猪当年总督天河,掌管了八万水兵大众,倒学得知些水性......』说声去,就剥了青锦直裰,脱了鞋,双手舞钯,分开水路,使出那当年的旧手段,跃浪翻波,撞将进去,径至水底之下,往前正走。


  • 却说那怪败了阵回,方才喘定,又听得有人推得水响,忽起身观看,原来是八戒执了钯推水。那怪举杖当面高呼道:『那和尚那里走!仔细看打!』八戒使钯架住,二人整斗有两个时辰,不分胜败。这才是铜盆逢铁帚,玉磬对金钟......



在上面的例子中我们看到孙行者其实并不是法力无边的角色,虽然在陆上、空中他是一个令人敬畏的大师兄,但是到了水里之后,作战能力甚至不如八戒。为此,我们在设计系统的时候要怎样诠释孙行者在各种场景,与各种妖怪对应的情况呢?针对这个问题很多人会习惯使用「旗标」作为一个状态的识别,但是如果系统中对象种类一旦繁琐起来、而且横跨的场景又是犬牙交错的情形,那么系统中光是状态旗标的维护,恐怕就是一件巨大的工程,更遑论要建立对象之间的关连或者是模型了!这也就是许多应用软件在发展成大型系统时所面临的重要关键所在。


换句话说,也就是当系统逐渐庞大之时,我们可以发现对系统切割的困难,便来自于对象的「独立性」开始降低,「相依性」逐渐上升的缘故。而具体的影响就是对象之间相互的干扰,我们以下面的例子来解说,图一的左方是由笔者所设计的一个 TImageListBox 类别,作用在于以列表的方式显示图片,并且让用户可以浏览并挑选适当的的图档。在这个例子中,笔者简单的以 TScrollBox 作为我所要表达的容器类别(Container Class),然后再以 TImage 作为我所要的显示细项。


《图一 以列表方式显示图片的TImageListBox 类别》
《图一 以列表方式显示图片的TImageListBox 类别》

下面的程序代码是 TImageListBox 的部分内容,在这个类别之中,笔者为了让用户在挑选的过程中具有标示(Mark)的功能,因此让 TImage 之间共享 TImageListBox 的 OnClick 与 OnDblClick 事件,并且将此等关系(RelationShip)发布(Published)出去,使之成为TImageListBox 的 OnSelectDblClick 事件触发机制。


TImageListBox = class(TScrollBox)
private FList: TList;
procedure OnClick(Sender: TObject);
procedure OnDblClick(Sender: TObject);
…….
Public
Procedure Clear;
property OnSelectDblClick: TNotifyEvent read FOnSelectDblClick write FOnSelectDblClick;
end;
procedure TImageListBox.Add(Item: TImageListItem);
begin
Item.OnClick := OnClick;
Item.OnDblClick := OnDblClick;
FList.Add(Item);
end;
procedure TImageListBox.Clear;
var i: Integer;
begin
for I := FList.Count-1 downto 0 do 
TComponent(FList.Items[i]).Free;
FList.Clear;
end;
…..
procedure TFmPickAPForm.ImageListBox1SelectDblClick(Sender: TObject);
begin
ImageListBox1.Clear;
end;

上述的程序代码中所陈述的技巧是大多数程序设计师所惯用的架构之一,它的好处是架构简单而且脉络十分清楚,并且大部分的情况之下,可以满足多数的需求。但是,这里面却隐藏了一个致命的错误,那就是当用户于TImageListBox 的 OnSelectDblClick 事件中呼叫它的 Clear 方法,此时系统便会出现「存取不当内存」的错误讯息。为什么会这样呢?现在您回头仔细观察上面的程序代码,您可以发现问题其实是出在 TImage 与 TImageListBox 之间所建立关连之上!原来当我们双击TImage之时虽然可以顺利触发笔者所设计的TImageListBox中的OnSelectDblClick事件,但是就编译程序的角度来看这个过程只是由一个函式之中,将参数推入堆栈,然后再呼叫另一个函式。此时若以Clear的方法清除了列表数据,则当程序完成OnSelectDblClick事件的处理函式之后,虽然可以让程序的执行点回到正确的起始地址,但这个地址却早已不是程序的控制范围,所以会出现「存取不当内存」的错误讯息是理所当然的。


在上面陈述中我们虽然是展示一个很简单的范例,但是却足以显示对象与其容器对象之间互相干扰的情形!至于干扰来自于对象之间并非「独立」的关系,因此解决之道,则是您必须移除他们之间的任何关连,而非如孙行者的例子中设定个旗标就可以解决的。不过对于经验丰富的设计师来说上述的干扰现象,只不过是在众多系统设计实务过程中所遭遇到的问题的冰山一角而已,而且这些问题大多都很难以程序技巧来加以解决。最著名的一个例子如数据库的「死锁」(Dead Lock)问题,便来自于多用户(Multi-User)或多执行程序(Multi-Process)所造成的。因此系统管理者多会以藉助规范用户的操作,来规避这个问题,但是众所皆知的这种方式在实务执行上通常不是很顺利的。


从上面的陈述中,相信读者已经可以认同程序的独立性,对于系统的稳定度有着莫大的影响!因此对于熟练的系统分析师而言,在进行系统模型塑造之时莫不致力于各模块之间的逻辑切割,务求提升模块的独立性,以期降低相互间的干扰。


然而可惜的是这样的要求通常是不容易做到的,为什么呢?如果我们回顾一下先前大同电视的例子,并且假定说IC的「独立性」与「可靠度」的特性来自于IC的封装,使得其内含的逻辑运算不得不取决于其接脚(Pin)的电气信号,那么在理论上软件的发展也应该作的到这一点。


嗯,没错!关于这一点我们的确可以在一些现今的产品上看到其类似的踪迹,例如Java与.Net等取消了对于指针(Pointer)的支持,或者强迫程序设计师使用类别(Class)等的作法,就是一个很明显的例子。但是事实上我们遵循这样的机制之后,果真可以帮我们解决「独立性」的问题吗?


如果我们回到上面TImageListBox的简单范例中,就清楚的看到即使我们在SA/SD乃至于Programming的的过程中全部采用OO的技术及理论,却依然不容易让一个程序设计师在设计之初或除错的阶段即避免掉这个问题?这其中的主要关键便出自于程序的例外(Except)发生于非设计时期(Design Time)的预期(注1.)以及错误讯息的辨识困难(注2.),而这些问题通常必须仰赖程序设计师个人的经验与功力来加以解决!


结论

于是这便衍生出一个焦点,那就是我们学习及利用面向对象的概念及方法,有办法或者是机会解决这个问题吗?答案是有的!不过限于篇幅,笔者以后会另文一一带出这些方法,如果读者对此有兴趣的话,请耐心等候!然而在这之前笔者需要响应文章之初笔者所提的「环境」问题。


这里所谓的「环境」泛指的是程序运行所需的条件,但是关于这类问题在Microsoft的操作系统从DOS升级至Windows后就鲜少在程序设计师的考虑范围之内,特别是在很多开发工具提供IDE的环境之后更是如此。于是这些先进的开发工具虽然可以有效的降低程序设计师的入门门坎,但是却也是推动面向对象程序设计上的最大阻碍!


我们以上述的TImageListBox为例,如果您想要设计成独立的组件,并且可以快速的除错,光是熟悉Delphi所提供的工具是不够的,因为您还需要知道Delphi的VCL(Virtual Component Library)运作方式才有办法追踪错误的所在、您必须清楚知道数据结构(Data Structure)的设计原理,才能掌握这些VCL的设计脉络、您必须熟悉编译程序理论(Complier Theorem)才可能知道错误发生的原因,此外您还需要了解Windows操作系统(Operation System)的运作模式,才能让您拥有解决方案的依据。而这些种种的背景知识(Background Knowledge)才是您真正设计独立程序代码的关键所在!所以如果我们说「设计独立的程序代码」是系统整合前的预备工作,那么充实这些背景知识,就应该是程序设计师入门的基本功了。


注释

注1:预期例外:这是一个程序设计中的一个重要技巧。所谓的「预期例外」指的是程序运行的过程中,预期某个例外(错误)有可能发生之意。因此在Delphi中我们通常会以try ... except ...end; 或者 try...finally...end; 等语法将这段预期可能发生错误的程序代码加以包裹,例如I/O的存取或者是网络的逾时处理等以避免系统的当机。


注2:对于Windows的程序设计师而言,蓝底白字的错误讯息是一个可怕的梦魇!那除了表示着系统正在当机的状态之外,更意味着程序中隐含着不知伊于胡底的臭虫!虽然近来这样情形有所改善,但是显示「存取不当内存」的错误讯息,或者是汇编语言式的除错工具,对于工程师的除错工作依然是没多大帮助的!


(作者联络方式:mhwul@pchome.com.tw)


延 伸 阅 读

面向对象实例 - 是定理还是方法
在OO的思考模式里,我们不但可以顺利切割整个应用程序的程序代码部分,就连程序中的逻辑部分,也一并切割的清清楚楚。这是一个在开发应用系统中,不容易出错的最主要关键...

以通讯角度看待 DCOM
近代面向对象技术里,除重视组件开发技术外,也愈益重视透过中继层(Middleware)来作为组件于各种不同平台间的沟通,而目前这类的中继接口,有 OMG 所支持的 CORBA, Sun 所提出的 Java 2 ee 乃至< font />的 COM、 DCOM,COM+ 乃至 .Net,我们能够感受到,现代软件的战争,已经从操作系统平台之战,转成组件中继平台之战。
面向对象的软件开发
为什么面向对象运动发展到了现在这样火爆的程度?部分是源于人们长久以来的一个希望:人们希望它像以前其他软件开发的技术一样,能够满足软件开发对于生?效率、可靠性、易维护性、易管理等方面,具有更高、更快、更强的迫切需求,除此之外,还有许多原因都促使了它的流行...

相关组织网站
Howto.Net(作者网站)
MISOO设计中心
JOOP(The Journal of Object-Oriented Programming)
相关文章
制作动态物件导向网站的软体 – XOOPS
物件导向之大话西游
comments powered by Disqus
相关讨论
  相关新闻
» 趋势科技指漏洞修补为资安预防针 企业须知4大生命周期样态
» TeamT5资安开运馆进驻资安大会 知己知彼防范於未然
» TXOne Networks揭示工控资安3大挑战 展出最新SageOne整合平台
» Fortinet资安报告:96%企业??心云端安全 单一云地整合管理平台成解方
» 宜鼎推出 iCAP Air 智慧物联空气品质管理解决方案 透过即时空品数据自主驱动决策


刊登廣告 新聞信箱 读者信箱 著作權聲明 隱私權聲明 本站介紹

Copyright ©1999-2024 远播信息股份有限公司版权所有 Powered by O3  v3.20.1.HK85T7SPOHYSTACUKP
地址:台北数位产业园区(digiBlock Taipei) 103台北市大同区承德路三段287-2号A栋204室
电话 (02)2585-5526 #0 转接至总机 /  E-Mail: webmaster@ctimes.com.tw