Hello, world! — 我心中的道

10-10-24 jdon007
                   

序言

08年6月,地震之后,在家无事,下载Sun上的Java Tutorial开始了自己的Java之旅。之前看过K&R《C语言程序设计》,对编程算是有一点点基本的认识。而后数月,无意间碰到设计模式,也许是无知者无畏,没有多少编程基础的我又开始了学习设计模式,这之后,便搜索到了Jdon等关注设计模式的网站,尽管我学设计模式不是从Jdon这里学的,但Jdon无疑给了我一点暗示:我正在学的东西,多少有点用处,反正也没什么事,就学学吧。当然除此之外,Jdon还给了我两个非常重要的提示:四色原型和领域驱动设计。一年多来在Jdon上没有发过帖子(除了前段时间跟了banq的一个帖子),而如今工作也有数月,对软件开发有一点点粗浅的认识,在此写下,算是对Jdon的感谢吧。

1、何为程序:数据结构、算法与设计模式

程序 = 数据结构 + 算法,自从Niklaus Wirth提出这个公式以来,也看到一些人其进行修改或者扩充,比如修改为程序 = 对象 + 消息,比如扩充为程序 = 数据结构 + 算法 + 方法,程序 = 对象+ 消息 + 软件结构等。在这里,我也提出了“程序 = 数据结构 + 算法 + 设计模式”,下面进行解释。

数据结构的重点在于存储,即将如何存储数据:这里,我不想将数据结构仅仅理解为表、树、图,对我来说变量、对象等凡是可以容纳数据的东西,都可以理解为数据结构。

算法的重点在计算,即如何处理数据:这里我也不想将算法仅仅理解为排序、检索或者更复杂的如路由、图像处理、机器学习算法等等,对我来说函数调用、消息发送等处理信息的方法,都可以理解为算法。

不同于数据结构与算法形成“剑”之双刃之“利”,设计模式的重点在“剑”之“用”,即考虑为何及其如何封装某些算法和数据结构以支持需求的变化?[注:这里且把“程序”喻为“剑”,正如一把好剑、一身剑术可以成就一位剑客,这一“利”一“用”,也可以成就一名programmer。如果这个比喻妨碍你的思考,请放弃。]

上面将“程序”的“数据结构与算法”喻为”剑之“利”,将“程序”的“设计模式”喻为“剑”之“用”,只是为了形象化突出两者不同的一面。事实上,这个比喻也有它的弊病,它妨碍我们理解其相通之处,甚至限制了我们的思考,所以下面我尝试放弃这个比喻,以解释它们的相通之处。

其一,程序 = 数据结构 + 算法 + 设计模式,这个公式,从它们各自思考的侧重点,可以进一步推广为:程序 = 做什么 + 怎么做 + 为什么。注意“侧重点”这个词,因为事实上设计数据结构,也会考虑到怎么做和为什么?设计算法时,也会考虑到做什么和为什么?在设计模式中,同样也会考虑到做什么和怎么做?强调侧重点,就是要表明它们只是在“核心关注点”有所不同。

其二,有的人说,设计模式是站在比数据结构与算法更高的层次思考问题。事实上,也经常是如此。不过,要注意到结构型和行为型设计模式的价值除了支持变化,还有很重要的价值,就是形成更大的数据结构和算法。你是要说“设计模式”这个盒子封装了许多“数据结构和算法”,还是“数据结构和算法”这个盒子封装了许多“设计模式”呢?两者都对,也都不完全对。“Thinking out of box”,只是相对的关系,当我们从井底跳出井外,以为不再是井底之蛙,井外可能不过更大的一口井而已,而且可能是一口枯井,还不如跳回去,如鱼得水呢。此外,“一沙一世界、一花一天堂”,如能以小见大,即便"Thinking in box",亦可悟道。

当然,解释了这么多其相通之处,意也不在抹杀其差异。虽然他们都涉及到“做什么,怎么做,为什么”,在具体的情境中,核心的关注点还是有所不同的;虽然抽象“其大无外,其小无内”,在具体的情境里,抽象的层次还是有相对高低之别的。

个人对数据结构、算法和设计模式是交替学习的,但对设计模式更倾心。一是设计模式的数量很少,你看看单单一个字符串匹配就有成十上百种算法,机器学习算法更是举不胜举,数据结构好像少一些,但比起设计模式也不算少;二是设计模式更简单,一般说来,理解一种算法和数据结构的构造的时间不会比领悟一个设计模式的时间更少;三是设计模式更通用,不能否认有些算法和数据结构也是广为使用,比如哈希表和快速排序法,有些设计模式,比如解释器,也不够通用,但整体上说,设计模式是更通用的,举些简单的例子,如Java中的collection内置了迭代器模式,io内置了修饰器模式,而collection和io基本上是在所有的程序中都要用到的,理解了设计模式,就能以举一反三,更快更好地掌握它们;再比如常听见的MVC某种意义上可以理解为观察者模式和中介者模式的合成,IoC可以理解为运用反射机制的工厂模式等等。

学更少、更简单的东西,却有更多、更大的用处。这可真谓"Less is More",何乐不为呢?当然数据结构和算法,也不能不学,如Donald E.Knuth《计算机程序设计艺术》第三卷的排序和检索,Josh Bloch的集合框架等等还是值得系统学一学的,原因也是上面三点:少、易、通用,当然这三点也是相对浩繁的算法和数据结构来说的,此外,数据结构和算法中也可能隐藏着和设计模式同样优秀的思想。但吾生有崖,生命太短,技艺太长,我们只能先选择很小的一部分东西进行深入研究作为自己的认知基础,其余的只能根据客观要求或主观需求临阵磨枪,随机应战了;也许某天遇到某些东西我们极其感兴趣,那不妨也就呆在那里不走了,管他春夏与秋冬,躲进小楼成一统,自得其乐的生活也是令人向往的。

2、何为模式:分析、设计与实现

模式,简单说,就是反复出现的情景,在分析、设计与实现的阶段都存在着。上节可以说是从程序本身的“构成”来看“设计模式”,但这节从程序开发的“过程”来看设计模式,它在开发链中前接“分析模式”后续“实现模式”,因其广为人知,所以先说说。

谈及设计模式,不能不说其那本书《设计模式:可复用面向对象软件的基础》,从该书的书名的副标题可以看出“复用”二字,即设计模式之目的。但如何复用呢?对于面向对象,一般的书上都会提到四个最基本的概念:抽象、封装、继承和多态。设计模式的复用技术,本质上只是这四个概念的运用而已:通过“抽象”将变化与不变的部分进行分离,用接口“封装”固化不变的部分,用“继承”释放变化的部分,即“多态”。其中接口可用抽象类替代,相应地继承也有“黑箱”(即接口实现)和“白箱”继承两种。

GoF这本书个人认为最大的贡献在于两点:1)对模式的命名,2)对模式的分类。看起来没什么,但合适的命名和分类真的不太容易,即便是科学家或哲学家,对事物进行命名与分类,他们也要花费大量的精力和心血。模式的命名约定成俗,深入人心,一般难以修改;但模式的分类,却可能被后来人进行修改或完善。

曾听人说,哲学的根本问题有两个:生与死,灵与肉。GoF将设计模式分为创建型、结构型、行为型,与此颇有相似之处。不妨将“生”对应“创建型”,将“肉”对应“结构型”,将“灵”对应与“行为型”,“死”呢?怎么没有?孔子说:未知生,焉知死?试图回避死这一问题,GoF也未考虑对象之“死”。在Java世界中,GC算法是不是可以对应“死”呢?其模式倒是简单明了:“无用”即“死”。我支持GoF的这种分类,而对于Bruce Eckel在著作《Thinking in Patterns》打破这种分类,犹如看到一些人解读《老子》一书,随意重新组合章节,甚至道与德倒置,是保留不同意见的。个人建议可以按照自己的理解在保留GoF的分类框架进一步细分,发现各种模式的不同点(正交性),以便于在需要时对多个模式进行组合使用。有一篇文章《Relations between Design Patterns》主要关注各个模式的关联,可以参考。所有模式的共性不过是四个OO基本概念的运用,因此找出各个模式的个性(关键不同点)才是剩下值得去做的事情。看过一些评论说GoF这本书不好懂,但个人认为学习设计模式还是以其为中心,如某个模式看不懂,可采取迂回策略,先看看相关的资料,再回来看GoF这本书。

不同于设计模式的关注点在如何设计模型以支持需求的变化,分析模式的关注点如何将需求转化为模型。Martin Fowler的《分析模式--可复用的对象模型》、Peter Coad的《对象模型--策略、模式、应用》等等关于分析模式的著作,都尚未拜读,加上经验实在匮乏,所以本不想说。可是对于Peter Coad的《Java Modeling in Color with UML》一书中的四色模型,一见如故,极其简单的四色模型,在我心里已经将其作为自己关于分析模式的核心认知,这里说说自己对四色原型的理解(再次感谢Jdon让我遇见了四色模型)。

四色模型中有四个最核心的概念(刚好设计模式也是运用了OO的四个基本概念,无巧不成书呀),分别是MI(Moment Interval)、Role、PPT(Party Place Thing)、Desc(Description), 不同设计模式运用的OO的Abstraction、Encapsulation、Inheritance、Polymorphism四个基本概念在中文有很好的翻译:抽象、封装、继承、多态。这四个词,直译起来,似乎没什么感觉:瞬间-间隔时间、角色、聚会场所-物件、描述。查阅一些资料,其描述我不是十分满意,这里我尝试用简短的言语解释其本质:MI描述活动,Role代表参与活动的事物;PPT代表未参与活动的事物,Desc描述事物。举个例子,比如一件商品,商品本身就是PPT,商品的类别信息、厂家等商品的元信息就是Desc,如果这件商品为顾客所购买,此件商品就变成了Role(当然顾客此时也是Role),而顾客购买商品这个活动就是MI。

世界可以简单理解为由事物及其运动(MI)构成,事物又有动静之分,动即参与活动之事物(Role),静即未参与活动之事物(PPT),此外,可能还需要描述事物的信息(Desc)。为了将原型形象化,我们可以根据色彩的冷暖动静之感,将活动(MI)涂上活跃的红色,将参与活动的事物(Role)涂以暖色黄色,将未参于活动的事物(PPT)涂以安静的绿色,将描述事物的信息(Desc)涂以冷色蓝色。这样四色原型就诞生了,通过四色原型,我们可以像孩子拼接积木一样,一块一块地拼接出需求模型的原型了。

对于《Java Modeling in Color with UML》这本书,我想模仿Gof的书名《Design Patterns: Elements of Reusable Object-Oriented Software》,给其取个别名,以彰显其价值。《Analysis Patterns: Make Requirement Prototype Dynamics》,中文名可以翻译为《分析模式:构建需求原型动力学》或者《分析模式:动态构建需求原型》。其中副标题的首字母对应四色原型的MI、Role、PPT、Desc四个词的首字母。

在说实现模式之前,稍微总结一下设计模式与分析模式的异同点。复用是模式共有的特点,但设计为支持变化的目标,往往采用“间接”的手段来实现,以增强其灵活性;而分析则为了真实地将需求映射为模型,手段越“直接”越好,简单直接的方式,更能刻画真实的需求。如上面所述,它们各自有一本极好的书来描述它们,都只有六章,都很薄,可反复阅读。

分析模式将需求转化为原型,设计模式支持需求模型的变化,而实现模式为何而生?实现模式关注的是代码本身,我们在分析和设计上付出的努力,终究要落实在代码上。分析和设计再好的模型,也可能被充满smell的代码掩盖或破坏。Kent Beck在《实现模式》一书提出了77个实现模式,6条原则和3种价值观。77个实现模式涉及一些具体的代码规范的建议,暂且不论;6条原则,局部化影响、最小化重复、将逻辑与数据捆绑、对称性、声明式表达和变化率,也暂且不议;3种价值观:沟通、简单、灵活,我也只想说说其中的一点:沟通,因为个人认为这是实现模式最根本的意义所在。Kent Beck在书中说:过了20年,把别人当作空气一样的编程方式才在我眼中褪尽了颜色。作为一位programmer,代码的作用不仅在于与机器交流,更重要的是与别的programmer交流,将“沟通”的价值观贯彻到代码的编写中,是值得每个programmer坚持去做的事情。Kent Beck的这本也极其薄,可以翻翻。

上面所说的三本很薄的书,可以作为关于分析、设计、实现模式的认知基础,值得收藏。

3、何为软件:架构及框架

从简单意义上讲,软件和程序之间基本可以划上等号,但软件还考虑到开发、维护的时间与金钱等诸多现实因素,经常使用各种框架(SSH, Jersey/Guice/Ibatis等)以节约开发时间,应用项目管理(Maven)、版本控制(SVN)、日志记录(Log4j)等以规范开发流程等。

在组织各种框架时,需要考虑框架之间如何集成,由什么作为中心向边界扩张。比如领域驱动设计,就是以领域为中心,暂不考虑界面,不考虑持久化,试图在不受外在技术的干扰下,认识需求的本质,并寻找合适的概念去表达。至于用户体验(界面)和性能(持久化),虽然也极其重要,但最好还是在认识领域的本质之后再考虑。软件开发除了考虑从哪里入手(比如领域驱动),还要考虑如何保证每一步都走得正确(比如测试驱动)等。

架构试图在整体上把握软件的各个层次与部件,而框架则试图从不同的层次与切面支撑着架构。框架本身则由程序的集合构成,可以说包含了设计模式、数据结构与算法三个成分,分析、设计、实现三个过程。这样看来,似乎软件考虑的更多是宏观问题—如架构与框架,而程序考虑的则是微观问题-模式、数据结构、算法等,有点粗糙,姑且这么说吧。

那么到底什么是软件?什么是软件的灵魂?这应该是一个众说纷纭的问题。 就像我们要偶尔灵魂出窍,看看自己这副臭皮囊除了吃喝拉撒,还能干什么?我们也要让软件的“魂”偶尔出窍,以接近软件的真相。和对程序的描述一样,Niklaus Wirth对软件的描述同样令人印象深刻:一切可以是软件,但软件不是一切。

道可道:走为上

道可道,来自《老子》第一句话,走为上,来自《孙子》的最后一计。而我将二者贯穿起来,只是想表达一个想法:道是可以走的。仔细说来,道并非不可言说,不可实践,相反道是可以言说,可以实践的,只是可能不那么容易而已。顺道而行,我们应该可以节约一点点时间的。将自己的想法写于此,也希望能有助于加快后来者前进的步伐。这就权作结束语吧。

联系方式:AchieveIdea@gmail.com

[该贴被jdon007于2010-10-25 00:01修改过]

                   

15
SpeedVan
2010-10-25 12:05

2010年10月24日 23:52 "jdon007"的内容
程序 = 数据结构 + 算法 + 设计模式 ...

这···其实单从程序,软件的定义,只要动动脑筋,定义也很难有错。但我们在面向对象和面向过程上考虑的话,可以发现两者的切入点(思想)是不一致的,所以不应该把两种不一致的思想合起来去描述一样东西。要么你就用底层的思维去描述,要么你就用OO思维去描述。混起来只会四不像。

而关于算法和数据结构,还是以公认定义为好,否则很难交流。(自己理解可以,但别把这些作为描述公认的定义,自己一套,大众一套,肯定会出入的。当然,若果你是怀疑或者否定公认定义,也就另外一回事了)

2010年10月24日 23:52 "jdon007"的内容

不同于数据结构与算法形成“剑”之双刃之“利”,设计模式的重点在“剑”之“用”,即考虑为何及其如何封装某些算法和数据结构以支持需求的变化? ...

不敢认同,软件开发是一个“造”的过程,“利”与“用”都没有对“造”进行剖析,所以不应该从那个角度去理解。个人认为,数据结构与算法只是“剑”之“材料”,OO是“造剑”的世界观,而分析设计思想则是“造剑”的指导思想,至于造的“剑”如何用,则是对软件如何使用而已,与造无关。最后一句和我思维有出入。设计模式的切入点根本不再封装这个词上,也不在算法和数据结构上。封装是OO思维所带入的,在使用OO思维时就已经存在,即考虑一个类的建立。设计模式是思想,某种代码只是一种表现形式而已。

设计模式中的某个模式,只是针对某个情况下设计的解决方案,不是考虑其通用不通用的问题,他和具体业务没有任何关系,它的身份相当于“公式”,当用它能解决问题时就用,不能用就不能用,而且设计模式就是从大多设计中提取出来的,没必要考虑其通用性。

关于设计模式的定位,设计模式是一种设计思想,在OO世界里思考单位是对象,设计模式也不例外,所以在谈论设计模式时,却引入算法和数据结构是不科学的。举个不恰当的比喻吧:在社会交往中,我们考虑的单位是人,不是人的内脏和构成。(一个角度,一套认识。若果你硬要考虑“内脏”,我也没办法)

至于其他方面,大概上和LZ差不多吧,感谢交流。

[该贴被SpeedVan于2010-10-25 12:07修改过]

jdon007
2010-10-25 14:51

其实我关于数据结构与算法的定义,应该还是符合Niklaus Wirth的原意的,可以找找Niklaus Wirth的访谈录看看。此外,Niklaus Wirth认为面向对象与过程化并本没有本质的不同,这在他的访谈录中也说到了。与面向对象、面向过程(命令式语言)本质不同的应该是函数式、面向进程、逻辑式等类型的语言(声明式语言)。正是出于对前人的尊重,所以我才没有去随意改造他们提出的概念,不然我会直接按常识说:程序 = 做什么 + 怎么做 + 为什么。在GoF书中,他们也说设计模式本身并不难,难得只是恰到好处地应用,即要考虑“为什么”的问题。

比喻只是比喻而已,放弃本体以喻体来讨论,不同的比喻方式,可能会出现偏差或冲突,所以这里就暂且不再用比喻说了。

个人认为OO并没有引入什么了不起的思想,它只是承载了一些了不起却十分平常的思想。比如抽象,任何一种语言都是某种角度对现实世界的模拟和抽象;比如,封装,过程化中就没有封装了吗?只是封装的方式不同罢了。至于设计模式的切入点,这个还真可能是封装,Dennis Ritchie 曾很中肯地说,“OO是一种很好的想法,通过界面来隐藏具体的实现细节。可是它可能有点过火了,一切都是界面,可能很难看到程序本质的终结”。如果没记错,针对接口(界面)编程,在Gof书中应该是列为第一原则吧。

不谈设计,只谈模式,模式如果不能复用,也就是说如果它不具备一定的通用性,就没什么特别的价值了。当然每个模式,都有其具体的应用场景,但这个具体并不是说它不可以反反复复、经常地使用用吧。

说到这,关于设计模式的定位,在文中我从程序本身的构成,与程序开发的过程,对其进行“静态”与“动态”的定位,并没有觉得不妥。

至于讨论设计模式时,要不要讨论算法与数据结构,Gof在书中说“结构型模式涉及到如何组合类和对象以获得更大的结构”,“行为模式涉及到算法和对象间职责的分配”。在文中我已从“关注点”和“抽象层次”两个方面论及两者的相通与相异之处,就不再复述了。

[该贴被jdon007于2010-10-25 14:53修改过]

SpeedVan
2010-10-25 16:54

先说清楚的是,面向对象和面向过程是思想,只有当出现具体规则时,才会成为语言,类似java,c#。思想归思想,实现归实现。

思想之间肯定有着区别,面向对象和面向过程,是一种世界观,他们都是对世界的一种认识方式。但是他们的“切入点”是不同的,一个对象,一个是基本类型。其实换句话说,面向对象的单元更粗粒度。若果你在用算法和数据结构,你已经不是用OO思维来思考了。所以在谈OO的时候,明显不能加入数据结构和算法等内容了。

若果你谈到他们的相似性,的确他们很相似,因为本来他们就是对同一个世界,不同粒度的认识。

我以前举了一个例子:树形。

树形:客观实在;

数据结构中的树形(树):面向过程;

设计模式中的树形(组合模式):面向对象;

千万别以为最根本是数据结构的树,若果那样想的话,就可能出现感到面向对象依赖面向过程。实质上,他们的交集只是客观实在(树形)。主要原因在于封装这一概念,对于对象的内部,我们是不可以直接获得,只能够通过方法。而加上面向对象的最小单元是对象,所以在面向对象世界观中,算法和数据结构这些已经抛离(不能向下了),而加入来的是设计模式和对象集概念(不知道能不能对应起来,反正就是相似但不一样的东西)。例如若果你用面向过程语言实现面向对象(C++例子),那么你现在就是用OO了,而非面向过程了,只不过,是不纯的OO罢了。

>>结构型模式涉及到如何组合类和对象以获得更大的结构

“结构型模式”是存在于面向对象的,“结构”是指客观世界的结构,这两者都不是指数据结构。

>>行为模式涉及到算法和对象间职责的分配

这里的算法和对象间的职责只是一个词,可以理解为方法,因为使用时根本不会去考虑其如何完成的。

当然当中的算法可以自己写,但写算法的时候,并不是在用OO思维思考问题,只是为OO提供支持而已(类似API、动态链接库一类的技术支持)

最后:

当你理解1+1=2;对于面向对象来说是错误的;那么你就理解OO思维为什么不同于面向过程了。

(numOne.add(numOne)).equalObject(numTwo);所以为什么说java是不纯的OO语言,也就这个道理了。

两个世界观的尽管相似,但不是相等的东西。这是我对OO和面向过程这两个思想,最为根本的理解。

(对于我说的,OO是一种世界观,这仅以交流)

[该贴被SpeedVan于2010-10-25 16:56修改过]

jdon007
2010-10-25 18:40

Niklaus Wirth 认为面向过程与面向对象的主要术语是同义词:

variable object

procedure method

parameter message

calling a procedure sending a message

data type object class

将程序=数据结构+算法,这一公式用在OO和PO两种编程范式上,可以得到,面向对象 = 对象 + 发送消息;面向过程 = 变量 + 调用过程。如果对对象、变量的理解不过于僵化的话,应该可以看出它们的一致性。主要的差异是,对象将变量与过程捆绑在一块了,致使调用过程与发送消息的形式不同,但这并非本质性的差异,它们都属于“命令式编程范式”,真正与它们“水火不容”的是“声明式编程范式”。当然,“命令式”与“声明式”虽水火不容,还是存在共性的,那就是程序的本质。这属于另外的话题,就不说了。

调用过程时,也可以不考虑过程怎么实现,就像发送消息时,不考虑消息怎么样被处理。

还有经常听人说(教科书)的按照纯粹的面向对象方法,x+y,必须表示为x.add(y) 。在访谈录中Niklaus Wirth对此不以为然。至于我的理解呢,发送消息的形式,何必如何僵化呢?x + y,为什么就不能理解为x向y发送了+的信息呢?举个生动点的例子,你对一美女说:我们相恋吧!按照面向对象,发送消息的形式似乎要这样: 美女.相恋(我)。而“我们相恋吧”这种形式的消息表示难道不比上种形式的消息更自然呢?当然在语法上强制保持消息形式的一致性,也是有好处的。这只是“形而下”的区别,而绑定带来的一些衍生的语言特性,也非常有用或有趣,但这个还是改变不了它们在本质上的一致。

在数据结构与算法,加上“设计模式”这一个东西,是考虑如何使用算法和数据结构,在面向对象中即如何组织对象、分发消息;在面向过程中即如何构造变量,调用过程等;考虑的是为何及其如何组织的问题。

再次说明,我对数据结构与算法的定义虽略显自由,但并没有脱离其根本含义。在文中已从“关注点”和“抽象层次”进行了解释。对于习惯OO的人来说,也许将程序定义为,程序 = 对象 + 发送消息 + 模式,其中模式考虑为何及如何组织对象与分发消息,更容易被接受。

[该贴被jdon007于2010-10-25 18:43修改过]

17Go 1 2 3 4 ... 17 下一页