下载本文示例代码
在程序设计这个行当里,软件设计与其说是一种技术,不如说是一种魔法,巫术。如果一个程序员坐在电脑前开始设计一个的程序,他基本上就是像是神汉巫婆处于灵魂附体的迷幻状态:脑子中不断地反复默念各种设计口诀,10指像是提线木偶一样把一行行代码犹如咒语般地输入到计算机里.更有趣的是,如果你等他们从癫狂状态还魂以后,再问他们:"你为十么把代码,设计成这样,不设计成那样?",他们的回答可能是空前的一致"这样写代码没有bad smell"。至于你再追问下去,什么是BadSmell,为什么这个是BadSmell,哪个不是BadSmell,那么其结果一定一场充斥着继承,模式,IOC、Refectoring各种眩目词汇,到处高举高打,演变成亚里士多德誓不罢休的口水战。
熊杰把这种行为称为技艺与艺术,把程序员成称软件匠人,这与其说是一种粉饰,莫如说是一种无奈。古语说,圣人以神道设教,当你无法解释某种现象,却又坚信它的真实性,并且还想要他人承认它的存在性,那么将其超验化恐怕是唯一的办法。在这一方面把软件设计归为艺术并不比把燃烧的荆棘归为神迹来的更加高明,无论我们给这样一种混沌的状态披上什么样的华丽外衣,事实是这些外衣下面我们连裤衩都没有。我并不是说继承,模式,IOC,Refectoring这些技术没有价值,而是说当这些理论无法对某些问题进行有效解释时,这些OO技术在大量实践当中的有效性就不能成为我们可以无视这些缺失的理由。
基于这一点,本文的目的并不在于构造一种新的软件设计方法,或者是对已有的软件设计技术进行批评,而是希望以Message dispatch机制来从新解释现有的几种常用程序设计技巧,在这种新的解释中我将试图弥补某些缺失的链环虽然不可能是全部。
Orentied or Object?
今年Javaeye的Age0曾经发起过一个关于如何设计Discount 策略的问题。在讨论中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的时候,我们总是以对象为先导,以现实中对象模拟程序对象为准绳。而一旦我们以消息分派的机制为根基,那么我们则应该以面向为先导,这里的所谓面向就是如何设计能把消息最方便的分派到计算模块的类型系统。
共3页。 1 2 3 :
在程序设计这个行当里,软件设计与其说是一种技术,不如说是一种魔法,巫术。如果一个程序员坐在电脑前开始设计一个的程序,他基本上就是像是神汉巫婆处于灵魂附体的迷幻状态:脑子中不断地反复默念各种设计口诀,10指像是提线木偶一样把一行行代码犹如咒语般地输入到计算机里.更有趣的是,如果你等他们从癫狂状态还魂以后,再问他们:"你为十么把代码,设计成这样,不设计成那样?",他们的回答可能是空前的一致"这样写代码没有bad smell"。至于你再追问下去,什么是BadSmell,为什么这个是BadSmell,哪个不是BadSmell,那么其结果一定一场充斥着继承,模式,IOC、Refectoring各种眩目词汇,到处高举高打,演变成亚里士多德誓不罢休的口水战。
熊杰把这种行为称为技艺与艺术,把程序员成称软件匠人,这与其说是一种粉饰,莫如说是一种无奈。古语说,圣人以神道设教,当你无法解释某种现象,却又坚信它的真实性,并且还想要他人承认它的存在性,那么将其超验化恐怕是唯一的办法。在这一方面把软件设计归为艺术并不比把燃烧的荆棘归为神迹来的更加高明,无论我们给这样一种混沌的状态披上什么样的华丽外衣,事实是这些外衣下面我们连裤衩都没有。我并不是说继承,模式,IOC,Refectoring这些技术没有价值,而是说当这些理论无法对某些问题进行有效解释时,这些OO技术在大量实践当中的有效性就不能成为我们可以无视这些缺失的理由。
基于这一点,本文的目的并不在于构造一种新的软件设计方法,或者是对已有的软件设计技术进行批评,而是希望以Message dispatch机制来从新解释现有的几种常用程序设计技巧,在这种新的解释中我将试图弥补某些缺失的链环虽然不可能是全部。
Orentied or Object?
今年Javaeye的Age0曾经发起过一个关于如何设计Discount 策略的问题。在讨论中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的时候,我们总是以对象为先导,以现实中对象模拟程序对象为准绳。而一旦我们以消息分派的机制为根基,那么我们则应该以面向为先导,这里的所谓面向就是如何设计能把消息最方便的分派到计算模块的类型系统。
共3页。 1 2 3 :
下载本文示例代码
Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环Message dispatch,失踪的链环
阅读(134) | 评论(0) | 转发(0) |