锁定老帖子 主题:失踪的链环
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2006-10-07
-------------------------- 在程序设计这个行当里,软件设计与其说是一种技术,不如说是一种魔法,巫术.如果一个程序员坐在电脑前开始设计一个的程序,他基本上就是像是神汉巫婆处于灵魂附体的迷幻状态:脑子中不断地反复默念各种设计口诀,10指像是提线木偶一样把一行行代码犹如咒语般地输入到计算机里.更有趣的是,如果你等他们从癫狂状态还魂以后,再问他们:"你为十么把代码,设计成这样,不设计成那样?",他们的回答可能是空 前的一致"这样写代码没有bad smell".至于你再追问下去,什么是BadSmell,为什么这个是BadSmell,哪个不是BadSmell,那么其结果一定一场充斥着继承,模式,IOC,Refectoring各种眩目词汇,到处高举高打不拉上老子,亚里士多德誓不罢休的口水战. 熊杰把这种行为称为技艺与艺术,把程序员成称软件匠人.这与其说是一种粉饰,莫如说是一种无奈.古语说,圣人以神道设教.当你无法解释某种现象,却又坚信它的真实性,并且还想要他人承认它的存在性,那么将其超验化恐怕是唯一的办法.在这一方面把软件设计归为艺术并不比把燃烧的荆棘归为神迹来的更加高明.无论我们给这样一种混沌的状态披上什么样的华丽外衣,事实是这些外衣下面我们连裤衩都没有.我并不是说继承,模式,IOC,Refectoring这些技术没有价值,而是说当这些理论无法对某些问题进行有效解释时,这些OO技术在大量实践当中的有效性就不能成为我们可以无视这些缺失的理由. 基于这一点,本文的目的并不在于构造一种新的软件设计方法,或者是对已有的软件设计技术进行批评,而是希望以Message dispatch机制来从新解释现有的几种常用程序设计技巧,在这种新的解释中我将试图弥补某些缺失的链环虽然不可能是全部. Orentied or Object? 今年Javaeye的Age0曾经发起过一个关于如何设计Discount 策略的问题(http://www.iteye.com/topic/17714?page=6).在讨论中Age0每次更改需求,原先的代码就被改的面目全非,从封装,到继承,到策略模式,到组合子.这个讨论的参与者关注是最终的答案.至于从需求到答案的逻 辑过程则完全是跳跃性的.我们并不清楚,需求与代码结构之间的确切关系:当需求变更的时候,程序到底起到何种相应的变化?同样,也不清楚同样一份代码,为何遇到某种需求变更可以不进行更改,而对另外一种需求变更则需要大改?这当中的过程犹如巫术一般神秘莫测. 我认为,这种缺失其实是我们用定义来解释定义造成的.比如说,四足动物是有四条腿的动物就是一句典型废话.同样继承封装抽象是我们给OO下的定义,但是我们又不断在用这个定义来解释OO,这就是所谓的同义反复.因此,我认为要跳出这个同义反复的怪圈,必须以一种内部工作机制解释来代替现有的定义解释.而消息分派就是这一解释的非常好的候选者. 消息分派是我们生活经常遇到的事情,写信,打电话,看电视,其实都是某种类型的消息分派机制.在这些消息分派机制都有必须具备四个要素,消息体,发送者地址,接收者地址,消息分派规则.这四个概念司空见惯也就不在这里赘言.在这四个要素当中,对于消息的接收双方,地址和消息体都是可见的,而消息分派的规则可能是完全不可见的.比如说你写一封信给朋友,你只关心信的内容和朋友的地址,至于邮局怎么根据邮政编号把信分派到各分局,然后分局下面的投递员如何根据你的地址找到你的朋友,对你和你朋友来说都是不可见的.这种机制与OO极为类似,但是我们就不能仅此来断言OO就是消息分派,这是没有根据的.不过我们不妨先假设它是OO的内部工作机制,然后看它是否能够解答我们不能解答的问题. 在本节我们将首先,使用消息分派机制来解释C++,Java这样基于静态类型的OO系统.以消息分派的角度来说,静态类型的OO是属于数据导向型的消息分派. 在SICP的第二章中,对数据导向有过详细的讨论。在那章中,作者给我们展现了一个不同复数系统的计算库。简单来说,我们要为复数的极坐标式,和直角坐标式分别设计一套算法,并且将这两套库统一到一个抽象界面上来.作为C++,Java的程序员来说不难想到继承.比如说 inteface complex { add(); minus(); plus(); div(); } class Polar implements complex { add(){...} minus(){...} plus(){...} div(){...} } class Rectangular implements complex { add(){...} minus(){...} plus(){...} div(){...} } 一般的OO教科书到此就嘎然而止,但是SICP却继续深入往下剖析这样一种OO机制是如何工作的.它认为这种机制是基于表格的分派机制.Interface的调用的背后它实际是一个以类型和操作名来查找表格的过程.C++的程序员一定非常熟悉这种机制:C++就是通过虚表vtable的查找来实现继承. 对于上面这个问题,我们则可以将继承化解为一张二维表. 以消息分派角度来看,在静态类型OO系统中,收发地址是类型+方法名,消息体是参数,消息分派规则则是二维表格查找.分析到此,我们已经能够部分地解答我们的先前提出的疑问. 基于静态类型OO分派机制的一大特点是,它的分派机制是与类型严格绑定的.在这个系统里,地址和消息体是互不兼容的.这就像是我写了一份电子文档,却不能以email的形式发出,而是必须把它copy到软盘然后装在信封里面邮寄出去.在复数的例子中Polar和Rectangular是类型,复数则是进行计算的数据.在这个例子中OO描述之所以让人感觉比较恰当,是因为Polar和Rectangular在现实世界中恰巧也是一种分类而不是数据. 但是在现实中地址和消息体可能都是数据.比如说上面的Discount策略的问题,如果我们仅考虑存放在数据库中的会员信息.那么(姓名,会员卡,性别,年龄,折扣)都是可以进行计算的数据.但是如果要实现这样的需求:"会员卡为金卡的会员折扣加10%".以消息分派的观点来看,金卡这一项就成了发送的目的地址,当前折扣则是消息体,而接受方则是一个能够将当前折扣加10%的模块.这个消息要在静态类型OO中能够进行成功的工作,那么必须将会员卡这一项的每种数据枚举映射为类型(比如说银卡,钻石卡).由于在静态类型中缺乏从数据映射到类型的机制,那么这些巨大的工作量只能交给程序员去手工编码.大多数的OO程序中,真正起到计算作用的代码并不多,Discount策略问题里面真正在计算折扣的仅仅是3-4句简单的加法运算,而大量的代码都是用于数据--->类型映射上.静态类型OO中计算代码和分派代码的数量的不成比例,使得设计模式中产生了诸多数据--->类型映射技术,比如说Factory,AbstractFactory,Interpretor,Command.当数据关系的复杂到设计模式不再能应付需要时,就导致了机械化的ORMapping诞生了. 说道数据关系复杂化,这同样是一个我们经常提及但是总是模糊不清的概念.完全是程序员的个人感觉.数据关系怎么样才算是复杂化?数据关系复杂化以后,对应的类型系统又如何变化?从静态类型OO的消息分派机制来看,这应该是一个关系代数的问题.静态类型OO的消息分派机制本质上就是一种二维表的查找,那么很自然地二维表就可转换成关系代数(如果你不明白关系代数,那么关系数据库应该听说过).在关系代数中我们有一套,描述关系复杂度的方法叫做范式NF,从1NF到BNF这是所有关系数据的基石.我们通常所说的数据关系复杂化或者说需求变更带来的代码变更,大部分也就是消息分派表上的范式变化.我们知道一个关系表上,纵向插入列和横向的插入行是完全不同的.横向插入行是简单地数据添加,而纵向插入列则可能降低改变关系表的范式.OO所谓的Open Close 原则在关系代数上也可以表示为,尽可能的多在横向上添加数据,尽可能少的在列上产生多值依赖和函数依赖. 我们还是以Discount策略为例.最简单的金银卡的需求中继承的消息分派表是这样一张Member表第一列为Member类型,其余列为方法名 Card GetDiscount getname getage ....... --------------------------------------------------------- golden goldenmember.getdiscount ................ silver silvermember.getdiscount ................ 这张关系表是很典型的没有非关键字依赖也没有函数依赖的4NF关系.这时如果我们需求更该为gender为女性的在妇女节有折扣.那么就相当于是在这张表中插入一列gender类型, Card gender GetDiscount getname getage ....... ---------------------------------------------- golden Female golden_female_member.getdiscount ................ silver Female silver_female_member.getdiscount ................ golden Male golden_male_member.getdiscount ................ silver Male silver_male_member.getdiscount ................ 相当于我们从goldenmember,silvermember上继承出 golden_female_member,silver_female_member,golden_male_member,silver_male_member 那么整张表则立刻降为BCNF,因为出现了多值依赖MVD,BCNF则会导致数据的冗余.因此为了将范式提升,我们将GetDiscount从表中分解为两张表. strategy GetDiscount getname getname getgender ------------------ ------------------------------------- golden_card golden_card.getdiscount member ....................... female female.getdiscount silver_card silver_card.getdiscount female_card female_card.getdiscount 这样又能恢复到4NF的状态.综上所述通过消息分派机制我们可以将基于静态类型OO的行为转化为关系代数上.关系代数经过数十年的发展已经有了非常坚实的理论基础.如果我们能将OO系统建立在这个基础之上那么我们就可以比较好的解释数据到类型,需求到代码之间的变动过程. 如果我们把消息分派机制作为OO的根本性质,那么我认为我们就要从新审视Object Orentied 这个词汇.通常我们在讨论OO的时候,我们总是以对象为先导,以现实中对象模拟程序对象为准绳.而一旦我们以消息分派的机制为根基,那么我们则应该以面向为先导,这里的所谓面向就是如何设计能把消息最方便的分派到计算模块的类型系统. Send and Pray 在前一节中我们看到了数据导向型的消息分派机制在静态类型OO中所起的重要作用.不过现在基于另外一种消息分派机制---消息传递的动态语言大行其道.由于C++ Java的长期统治地位,一方面使得静态类型OO成为了OO的事实标准,另一方面又使得人们认为动态语言是以颠覆经典OO的面目所出现.最近在程序员社区中这种捍卫正统的辩论已经甚嚣尘上,这不能不说是一个十分讽刺事情.讽刺之处在于,最初OO先驱们(例如AlanKay)所设计的OO系统并不是数据导向型的而是消息传递型的比如Smalltalk.并非只有基于继承,抽象,封装的静态类型语言才能算OO.一般来说,任何具有 Data Abstraction和Message dispatching 能力的系统都能称为OO. 消息传递的机制的问题在SICP中紧接着数据导向型的章节之后,就做了一个简要的介绍.它认为消息传递机制与数据导向机制正好相反,数据导向机制是在一个表内按照行进行分派,而消息传递机制则以按列分派.在数据导向中,类型和操作是一个地址,而消息传递中,作为地址的"智能操作"(类型+类方法)消失了,取而代之的则是没有类型的"智能对象",而类方法则成了发往"智能对象"的消息体.什么智能对象呢?举个简单的例子如果你作为一个经理正在招聘工程师和测试人员,这个时候你并不知道招聘对象到底适合哪个类型.那么最简单的办法就是给所有的招聘对象发放一份测试,然后根据测试的结果来决定他们最终的职位.如果说数据导向型的分派机制类似于尽职的邮差负责两个地址之间的点对点精确通信的话,那么消息传递机制就类似于马路上到处投递小广告的投递者.它采取的是Send and Pray策略,既不关心消息是否能精确的传送到真正需要消息的接收者,而是以广播的方式把消息发送给所有人,然后通过回馈来确定消息接收者的类型.动态语言中就大量采用这样的机制.我们虽然仍然能像Java那样有类型和象方法.但是从消息分派的角度来这两者完全不同.Java和C++中的类型和方法是根据类型来事先确定的,但是动态语言中的方法调用则是一种对类型的尝试,其类型确定则依赖于返回结果.这就是所谓的,如果它走起路来像鸭子,那么它就是鸭子的Ducktyping. 静态类型的表格在编译期就能被编译器分析出来并进行优化,而消息传递对类型的确定则完全是运行期的,从效率上来看消息传递机制肯定是比数据导向型的要差很多.但是消息传递则有着静态类型难以比拟的优势:完全松开了类型与消息分派机制的绑定(严格的说在大多数动态语言中并没有放弃类型系统,但是类型系统在消息分派中起的作用已经大大的淡化).这样一种方式最大好处就是使得普通数据也能参与到消息分派中来,减少了不比要的数据--->类型映射.在上一节中所提到的Discount 策略问题,我们可以用Python来提供一个更为简洁的方案. class Member: def __init__(self,discounts): self.discounts=discounts self.card="none" self.gender="male" self.discount=0 def set_gender(self,gender): self.gender=gender def set_card(self,card): self.card=card def set_discount(self,discount): self.discount=discount def get_discount(self): for discount in discounts: if discount[1](self)==True: discount[2](member) return self.discount def discount_golden(member): member.discount=member.discount+10 def discount_silver(member): member.discount=member.discount+5 def discount_golden_female(member): member.discount=member.discount+2 if __name__ == "__main__": discount_rules=[(lambda member:member.card=="golden",discount_golden), (lambda member:member.card=="golden",discount_silver), (lambda member:member.card=="golden" and member.gender=="female",discount_golden_female)] member=Member(discount_rules) member.set_card("golden") member.set_gender("female") member.get_discount() 我们可以看到我们仍然使用了一个表discount_rules来进行消息的分派.但是这个discount_rules与数据导向中的表最大不同是其第一项是一个对数据进行能力测试的函数而不是类型.数据导向中的类型虽然也是某种能力测试.但是这种能力测试是靠程序员将数据显式的转化为类型告诉编译器这个类型有什么样的能力.消息传递中则是让数据"golden","female"直接参与到能力测试中去,如果一个member中的gender card数据能够通过这个函数的测试,那么就代表Tuple第二个运算函数具有计算该member的能力.这样我们就以可将这个表始终保持在一个4NF的状态,不需要像静态类型那样频繁的变动设计结构.可以这么说消息传递就是动态类型得以横扫整个程序员社区的全部奥义所在. Is a or Like a? 除了数据导向和消息传递之外,消息分派技术中还有一种在命令式语言中很少使用的技术,模式匹配.无论是数据导向还是消息传递,他们分派消息的地址和消息体是分开的,而且地址必须是精确的.但是模式匹配中地址和消息体是合一的,地址则可以是模糊的.还是以邮差员为例在其他两类消息分派中,邮差都并不考虑传递的包裹是什么,而是拿到一个地址把包裹送到对端.而在模式匹配中,邮差发现包裹是有形状的,有方型的包裹,也有圆形的包裹,还有三角形的包裹.那么邮差和接收方发达成协议,接收方做出方圆三角不同形状的邮箱,而发送方不用考虑对方的地址是什么只要把包裹包成什么样的形状.那么方型的包裹自然能塞入方型的邮箱,三角型的包裹自然能塞入三角型的邮箱. 在OO中我们对于对象之间的关系总是用IS-a来描述,在模式匹配里面则变成了 like-a.其实在现实生活中很多情况下只要依靠like-a来解决问题,is-a反而会引来很大的麻烦.比如说去超市买东西,碰到妇女节打折扣,超市店员要确定你男女肯定只是看你长得像男的还是像女的这就是like-a.如果我们非要用is-a的话,那么就必须请你去更衣室请医生在生理上仔细的检查一番.这样一种机制的好处就在于,发送方只需要关心数据是什么样子,而不需要关心对方在那里.而接收方则只需要关心具体消息体的某一个方面,而不是全部.这无疑是把收发双方的消息协议给正交分解了. 下面来考虑Discount 策略的最复杂一种需求,为了更加复杂化,我还加入了一个妇女节的规则. 适用等级:钻石卡 适用时间:2005-5-1,2005-5-31 方案:(A+B+C) || (D) || (G) 适用等级:金卡、银卡 适用时间:2005-5-1,2005-5-31 方案:(A+B+C) || (C+E) 适用等级:所有 适用时间:2005-1-1,2005-6-30 方案:(B+F) || (D) 适用等级:所有 适用性别: female 适用时间:3-8 方案: 2 公式里面的 || ,表示取折扣最多的策略,也就是说原有的排它性概念转化为取最大值。 只要条件符合的方案都会被执行,最后取折扣最多的方案。 下面是Erlang用模式匹配给出的方案. -define(A,1). -define(B,2). -define(C,3). -define(D,4). -define(E,5). -define(F,6). -define(G,7). -record(member,{name,gender,card}). sum(Discounts)->lists:foldl(fun(X,ACC)->X+ACC end,0,Discounts). max(Discounts)->lists:foldl(fun(X,ACC)when X>ACC->X; (_,ACC)->ACC end,lists:map(sum,Discounts)). discount(Member)-> {{Y,M,D},_}=calender:now_to_datetime(now()), discount(Member,{Y,M,D},[]). discount(_,[done|Discounts])-> sum(lists:map(fun max/1,Discounts)). discount(#member{gender=female}=P,{2006,3,8}=Day,ACC)-> discount(P,Day,['!',2|ACC]); discount(#member{card=diamond}=P,{2006,5,D},['!'|ACC])when D>=1,D=<31-> discount(P,{},[[[?A,?B,?C],[?D],[?G]]|ACC]); discount(#member{card=none}=P,{2006,M,_},['!'|ACC])when M>=1,M=<6 -> discount(P,{},[[[?A,?B,?C],[?D],[?G]]|ACC]); discount(P,{2006,5,D},['!'|ACC])when D>=1,D=<31-> discount(P,{},[[[?A,?B,?C],[?C,?E]]|ACC]). discount(P,D,ACC)-> discount(P,[done|ACC]). 首先我们用宏定义define 定义了A-G的不同策略.然后我们用一个结构体member,-record(member,{name,gender,card}),来定义member长什么样子,无非就是一个名字一个性别一个卡种类.然后我们定义了两个辅助函数,sum是说如果出现L=[[?A,?B,?C],[?C,?E]]这样的折扣组合,那么对L中的每一项求和形成一个新列表NewL=[[?A+?B+?C],[?C+?E]],max是说在求和后的NewL中选出一个最大值. dicount(member)所做的事情就是首先取出当前时间{Y,M,D},然后传递到一个三参数的discount函数中去,其中第三个参数为空列表用于保存所有的可能适用的折扣规则 discount(_,[done|Discounts])如果我们发现折扣列表的第一项为done,那么就带表所有的规则都匹配完毕,然后对折扣列表的每一项求最大值,然后求每一项最大值的和 discount(#member{gender=female}=P,{2006,3,8}=Day,ACC)性别为女,当前日期正好是3/8,那么就在折扣列表里面加一个折扣2然后继续进行匹配 discount(#member{card=diamond}=P,{2006,5,D},['!'|ACC])如果是钻石卡,当前正好是5月1-31日,在折扣列表中加入对应的规则 discount(#member{card=none}=P,{2006,M,_},['!'|ACC])如果没有卡,在1-6月之间的话,在折扣列表中加入对应的规则 discount(P,{2006,5,D},['!'|ACC])如果既不是钻石卡也不是没有卡的用户也就是金卡银卡,并且在5月1-31日之间的话,在折扣列表中加入对应的规则 discount(P,D,ACC)如果前面所有的规则都不匹配,那么相当于折扣规则匹配完毕进入计算总折扣的步骤. 我们可以看到模式匹配在消息分派中要比消息传递更加强调数据直接参与分派.当然从另外一方面来说,一个模式也就是一个类型,只不过这种类型是by layout的而不是静态OO语言那样by name的.因此在一些更为强大的函数式语言(比如Haskell OCaml)中为这种by layout的模式引入静态类型,并且以更为强大的type inference进行消息分派.在type inference中数据与类型在消息分派中得到了完美的结合,而且type inference能在编译期进行分析优化的特性很大程度上解决了消息传递的性能问题.但是这种消息分派的语法过于晦涩,程序设计的思路更偏向于数学推理而不是经验分析.因此type inference还处于起步之中,但是我们有理由相信它一定也会像消息传递和模式匹配那样随着计算机科学和工程技术逐步的进入应用领域的视野. 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2006-10-07
附,与dlee,buaawhl等后续讨论
buaawhl 写道 通过一番对多个讨论得观察,我提出一点。
设计模式 和 Pattern Match 不是一个级别的东西。ErLang 的 behaviour 是 设计模式 (比如,observer, listener)。 Pattern Match 和 If else, switch case 是一个级别。 设计模式的主要作用是 Template Method Reuse. 一个方法骨架部分的重用。 正如ErLang 的 behaviour,整个骨架不变,变化部分就是外面传过来的 module, function 实现 (callback)。 那么设计模式(也就是OO类层次设计)的 if else, switch (也就是message dispatch)的作用是及其有限的。 如T1文中所说,OO唯一做到自动dispatch的地方只有一个 vatable。这个vtable的作用,就是一个简单的 switch (type) case。无法应对任何稍微复杂的分派逻辑。而且,在ErLang中,module , function 就等价于这个vtable。这个层面的自动分派上,ErLang 和 OO 毫无二致。 T1上面给出的那个复杂业务逻辑的例子,复杂的 pattern match 相当于复杂的 if else, 就是用 Java OO 怎么设计都无法消除这些 if else. ErLang代码中多少pattern match, 那么java代码中就要有多少 if , else, switch case, 没有任何 OO 设计模式能够消除这些 if , else。 可能有人会采用组合子的技巧,把装配逻辑独立于类体系之外。即使是这样,独立于类体系之外的装配逻辑,里面的if , else 也是不能少的。而且ErLang采用这种组合子更是轻车熟路。只是把Pattern Match向上移动到装配逻辑里面。 所以,设计模式对于OO和FP来说,作用是完全一样的。就是把经常变化的部分,向上移动,抽取为Callback。剩下的不变底层部分就成为 Template Method。这里面 if, else 一个都没有少,只是移动了一个位置。 那么,ErLang 相对于 OO 的优势,就是语法优势。Pattern Match 简化代替了 if, else, switch。 Trustno1 写道 其实就是代码复用的问题,比如说现在的快递公司不但承接送货任务,如果你需要给MM送礼物想华丽一点,那么快地公司现在也能帮你把礼物包装一下.从接收双方来说礼物包装可以看成是一个复用,但是深入快递公司的内部运作其实还是消息分派那么回事情.我之所以不提代码复用,就是在这里消息分派并不是不存在只是你看不见.
凭空消除逻辑度不是OO能干的事情,也不是设计模式能干的事情,更不是FP能干的事情,这个重任只能交给算法来完成.比如说高斯小时候就发明的1加到100的快速算法就是如此. ------------------------------------------------------------------------------- dlee 写道 布娃娃会被potian、T1带走,我不大可能被带走,一个原因是因为资质过于鲁钝,另一个原因是大脑的兴奋灶也不在这些方面。potian也说了,不希望大家都学会了Erlang。
我觉得potian和T1似乎有点忽略软件项目开发中人的因素。《人月神话》中认为项目开发的大部分成本花在了人之间的沟通上。 OO相比FP至少有一点好处就是沟通上的成本比较低。至少在我看来,Java的OO代码可读性要比FP的代码更好,更加容易理解。 一种语言的普及,除了其本身设计的精巧之外,对于多人协作的支持也是一个重要的方面。我相信Erlang开发同样的应用,需要的人数要少得多。但是也不大可能仅由一两个人来完成。 因为几乎完全不了解,所以发表了一些有点像FUD的言论,见谅 Trustno1 写道 FP和OO之间的鸿沟到不是语法上的.虽然我承认语法习惯肯定是有锁定效应的,但是这种锁定效应肯定没有那么想像的那么大.Ruby就是一个很好的例子,Ruby的语法的复杂性绝对是可以和C++比肩的.但是现在很多Java程序员转用Ruby也没有太大的障碍.而且我认为在现代有intelligent sense这样功能的IDE中,熟悉语法会比平时快好几倍.
FP,OO之间真正的差距在于关注点的不同.就像我前面的文章里说的,OO语言无论是静态的还是动态的关注的重点都是解决消息如何分派的问题如何计算都是很次要考虑的问题. 而FP在正好相反,FP的重点在于解决如何计算的问题.在FP里面你要精心的设计数据结构和算法,数据结构和算法的复杂度如何直接会导致你代码的复杂度的高低.这一层门槛,没有经过专门的训练是比语法更难突破的问题.我现在做的一个小东西,进展就不是很快,一个module差不多已经写了3个礼拜了,当中几乎是从新写过了4-5遍,但是依然很差强人意. dlee 写道 因为OO目前在应用软件设计中占据统治地位,所以一种不能很好支持OO设计开发的语言注定只有很少的使用者。
目前开发方法中,能够与OO相抗衡的只有FP。最好是一种结合了OO和FP优点的语言,例如Ruby,甚至也包括JavaScript,这样的语言对于Java的冲击会比较大,也比较容易被广大开发者所接受。我想如果不是在Ruby中找到了很多Smalltalk的影子,Ruby能够非常优雅地支持OO设计和开发,Martin Fowler和Kent Beck也未必会力挺Ruby。 前几年与GNU的洪峰做过一些交流,洪峰对Java相当不屑,向我推荐过Scheme。但是迫于生计,我还是必须要学习Java才行。我想Erlang可能是比Scheme更好的一种FP代表性语言。 Trustno1 写道 我还是那个看法,分派和计算的问题.那种几百行程序里只有10几句计算的应用程序越来越不会有竞争力.设计模式这种笨重的分派方法最终会被Ruby所淘汰.现在分派技术越发达越向语言层靠拢,这部分技术将来会非常专业化,以至于程序员可以像现在不careGC算法一样,不care最底层的分派机制(Ruby,DSL已经有这样的端倪了).
越多的精力和时间从分派中被解放出来,那么会有更多的精力和时间投入到设计如何计算上去.这显然不会是语法上,或者设计思维上的转变,而是我们将来所要处理的对象上的改变.就像10几年前我乡下的亲戚还在关心如何除草和沤肥,但是现在他们要关心的是如何从日本引进新的素菜品种.因为除草沤肥乃至最后的收割,都有专营的公司帮他们解决了.你即使能用土方法做,效率上未必又他们快成本未必有他们便宜,吃力不讨好的事情. 至于到底那种语言语言胜出,我没有这个把握,而且即使你预见到了对自己也未必有多大的意义.还是Alan Kay的那句话"The best way to predict the future is to invent it" dlee 写道 OO所要解决的一个重要的问题其实是职责的分派,职责的分派当然也包括消息的分派。设计模式是为了让这种分派更加灵活,代码重用度更高。 OO是否就等同于消息的分派?是否在FP中,这些问题都能轻易解决?目前我还没有答案。 Trustno1 写道 FP能不能轻易解决到不是这个问题的重点,FP中不能解决可以有其他的P解决.反正无论如何这个问题肯定要被解决,肯定会被解决,肯定已经在被逐步的解决,否则我们不会从if else switch case的迷魂阵里走出来享受OO带来的各种好处.现在这个趋势只是顺延着OO正在走的路往下走,只不过随着硬件和网络的规模越来越大这个前进的速度越来越快. 我之所以关注FP是因为他的算法描述能力.在DSL越来越流行的现在,算法会越来越重要.这种大趋势下,恐怕不是靠学习一门语言就能建立起技术壁垒的,而是因该转换自己的知识结构,即便FP将来被证明是一条走不通的路,但是算法和数学肯定依然是主角. Dlee 写道 说点正经的。我们做OO开发,每天确实相当大的时间都花在了如何将不同的职责分配到不同的类中。
所谓的《企业应用架构模式》,其实核心也就是应该如何来组织和分配系统的职责。做过多年OO设计的人看这本书并不会感到很吃力。 所谓的事务脚本,基本上就是按照动词来对系统的职责进行划分(做一件事一个类),而所谓的领域模型,基本上就是按照名词来对系统的职责进行划分(一件东西一个类)。 我们每天伤脑筋的问题,可能在FP中并不存在。我前几年就讨厌整天把领域模型放在嘴上的人,现在仍然讨厌。这些东西其实并没有什么伟大神奇,靠这样一些东西混一个“大师”出来实在太容易了。 |
|
返回顶楼 | |
发表时间:2006-10-07
好文章!希望T1能够更加详细展开讲解消息分派和规则匹配,这方面我还不懂。
Java确实非常依赖静态类型定义来实现职责的分派,所以会造成类多,代码也多,真正逻辑运算很少的现象。 现在有时候也抽空看看ruby,确实接触到了很多不一样的概念,消息传递机制被表达的很明显,非常不同于Java,不过我仍然在学习过程中。 Trustno1,你可以把文章加入到你自己的博客中,便于归类和整理。 |
|
返回顶楼 | |
发表时间:2006-10-07
的确 OO在人和人的沟通上有很大的便利性。
过于纯OO的设计,则很不现实。开发过程中,职责分配只是为了更加方便的进行计算。最终也是为了计算而进行设计。 OO必须先定义一个概念、名词或者类似的定义才能进行设计。如果有不完全概念或者模糊概念存在,部分采用非OO的方式来设计,远远比完全OO的模型便捷很多。 |
|
返回顶楼 | |
发表时间:2006-10-07
OO Design Pattern 无论怎么设计,也无法解决 Pattern Match解决的复杂逻辑问题。那些逻辑还是要老老实实用if, else , switch case 来解决。Design Pattern 的作用只是移动复杂逻辑,而不能消除复杂逻辑。
FP 的设计思路,和 OO 也基本上没有大的区别。而且可以说,FP 更加灵活。 使用 FP 的主要障碍是哪里呢? 有些 FP 要保持 Non-destructive variable 特性,所有的变量都是 final。好处是利于 GC 优化。坏处是增加了一定的算法复杂度(当然,增加得有限),需要程序员在这个方面多用些功夫,避开这些限制,不能再使用从前 destructive variable 的那种设计。 Non-destructive variable 这样限制的 FP 就相当于 immutale OO。 注:destructive variable 的意思是,一个变量不是final的,可以多次赋值。 |
|
返回顶楼 | |
发表时间:2006-10-07
谢谢T1的好文章,我谈下理解:
在复杂的需求下,静态类型的OO(名词分发机制),难以构造符合bnf的名词系统,因此允许自己coding分发逻辑的需求出现,(动态语言提供叫做type inference分发机制的支持,方便coding分发逻辑)。 目标唯一的分发逻辑等同于自己构造一组有唯一解方程,难。 |
|
返回顶楼 | |
发表时间:2006-10-07
写的非常深刻,真是有洞察力,受益非浅。
第二版的SICP刚刚买到,再好好看看第二章。希望Trustno1再推荐些type inference的资料。微软的C# 3.0里面要有type inference的特性。Java里加这个功能也不远了。 |
|
返回顶楼 | |
发表时间:2006-10-07
很有意思的分析,我也很想直接加入这个讨论,但是沿着T1的消息分派的思路往下谈,一时也不知从何说起,就扯远一点吧。
通常我们做软件开发,往往是最难的事情,就是分解。 对于将要做出来的那个系统,进行合理的分解。庖丁解牛,是一个不错的“隐喻”。解牛之前,也只能看得到牛的“表象”,对于牛的身体结构并无了解,贸然去做分解切割的工作,就会吃力不讨好。但是庖丁的活毕竟还是容易的,因为不同的牛的身体结构,还是大同小异的。 更加困难的是,我们面对着一个不同的动物,以前可能没有见过,当然,我们希望他不过是牛的一个变种或者近亲,这样过去的经验,还能派上一些用处。 再进一步,这个被我们分解的动物,并不是死的,他还是活的,不但会不断的动来动去,甚至还会长出些新的器官和骨骼来。这就太恐怖了。 通常我们所做的分解的决策,一般会从上自下的进行(分解这个词,就隐含了这个意思)。这样的分解有两层意思: 一是:在本层面看来,几个部分可以相对独立的分工的。 二是:从上一层面看来,这几个部分是为了同一个目的合作的。 麻烦的就是,当这种分解逐渐细化之后,原本就不太了解的细节浮现出来了,这下才发现,原本的那种分解,并不合理,需要在某一层次上,重新分解,最糟糕的,就是需要整个系统设计推倒重来。 一般我们能够用到的分解策略,也无非就是两种,纵向的业务分解,横向的技术分解,然后再把这横向纵向的参合在一起,搞成更加复杂的网状结构。。。 从上自下的分解,可以分为两个重要的阶段,一个是与语言无关的分解,一个是与语言相关的分解。前者,可以称之为设计,后者,可以称之为实现。而T1探讨的内容,我的理解是:在实现阶段,不同语言,对于各种分解需求的支持。不同的消息分派机制,对于同样的分解、再分解需求的支持能力是不同的。这也就是各种语言,在实现同样的设计时的难易区别。当然,我们也经常会碰到这样的情况,特定语言的功能限制,会迫使我们去修改原本很自然的,语言无关的设计结构,这就是无奈但自然的现象了。 再进一步探讨,为什么设计这么困难,因为存在未知的复杂性。灵活方便的语言,有助于开发设计人员,快速、轻松的调整已有的实现、甚至设计方案。而那些使用死板、僵硬的语言的工程师们,就要为此付出更大的努力。这也正是我为什么要发明一种新的语言的原因。在我看来,语言应该更进一步,对于设计,尤其是开发过程中的再设计,提供更好的支持。 关于DJ的开发思路 http://zbw25.spaces.live.com/Blog/cns!1pA6-3FOo9yNp_4lmEHxdDqA!344.entry 为什么DJ要将Event提出作为语法概念 http://zbw25.spaces.live.com/Blog/cns!1pA6-3FOo9yNp_4lmEHxdDqA!358.entry 这样的思路,其实是一种实验主义的方法,而再次之前,软件设计,要么就是凭借工程师在丰富经验之后,具有的技术直觉。要么就是试错,再试错,在付出了巨大的成本之后,完成一次系统开发。 先说到这里吧...... |
|
返回顶楼 | |
发表时间:2006-10-07
引用 熊杰把这种行为称为技艺与艺术,把程序员成称软件匠人.这与其说是一种粉饰,莫如说是一种无奈.古语说,圣人以神道设教.当你无法解释某种现象,却又坚信它的真实性,并且还想要他人承认它的存在性,那么将其超验化恐怕是唯一的办法.在这一方面把软件设计归为艺术并不比把燃烧的荆棘归为神迹来的更加高明.无论我们给这样一种混沌的状态披上什么样的华丽外衣,事实是这些外衣下面我们连裤衩都没有.我并不是说继承,模式,IOC,Refectoring这些技术没有价值,而是说当这些理论无法对某些问题进行有效解释时,这些OO技术在大量实践当中的有效性就不能成为我们可以无视这些缺失的理由. 这个说法是不公允的。对于“什么是好的设计”,我有一个毫不先验并且更容易理解和操作的标准:DRY |
|
返回顶楼 | |
发表时间:2006-10-07
庄表伟 写道 很有意思的分析,我也很想直接加入这个讨论,但是沿着T1的消息分派的思路往下谈,一时也不知从何说起,就扯远一点吧。
通常我们做软件开发,往往是最难的事情,就是分解。 其实我并不否认功能分解上的问题.我在文章里面隐约的提到过一点"一般来说,任何具有 Data Abstraction和Message dispatching 能力的系统都能称为OO". 这里的Data Abstraction就是所谓的分解.但是我觉得这样非常感性的探讨没什么目的性,很容易流于朦朦胧胧的形而上学讨论.在就我手边现有的论文来看Data Abstraction或者说功能分解也并不是毫无章法 ,完全靠程序员的自由心证.而是有一定的内在机制的.这些资料给我的一个看法是,Data Abstraction,是一个颇具误导性的词语.我们进行分解往往一个向下取规约的过程,而不是向上抽象的过程.也就是说,语言是一个最基本的模子,我们进行Data Abstraction 或者功能分解,其实是把我们的需求向下规约成语言所有的模式.其实就是一个削足就履的过程.至于细节,恐怕我还无法回答更具体,起码要等我把手边Liskov和zilles几篇70年代关于Data Abstraction的论文看掉,才会有更加成型的想法. |
|
返回顶楼 | |