﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-GHawk-随笔分类-学习笔记</title><link>http://www.blogjava.net/ghawk/category/6367.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 19:10:32 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 19:10:32 GMT</pubDate><ttl>60</ttl><item><title>敏捷软件开发 读书笔记 （4）——OO五大原则（3.LSP——里氏替换原则） </title><link>http://www.blogjava.net/ghawk/archive/2006/01/18/28545.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Wed, 18 Jan 2006 10:12:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/01/18/28545.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/28545.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/01/18/28545.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/28545.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/28545.html</trackback:ping><description><![CDATA[<P>OCP作为OO的高层原则，主张使用“抽象(Abstraction)”和“多态(Polymorphism)”将设计中的静态结构改为动态结构，维持设计的封闭性。 
<P>“抽象”是语言提供的功能。“多态”由继承语义实现。 
<P>如此，问题产生了：“我们如何去度量继承关系的质量？” 
<P>Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说，当一个子类的实例应该能够替换任何其超类的实例时，它们之间才具有is-A关系。 
<P>该原则称为Liskov Substitution Principle——里氏替换原则。林先生在上课时风趣地称之为“老鼠的儿子会打洞”。^_^ 
<P>我们来研究一下LSP的实质。学习OO的时候，我们知道，一个对象是一组状态和一系列行为的组合体。状态是对象的内在特性，行为是对象的外在特性。LSP所表述的就是在同一个继承体系中的对象应该有共同的行为特征。 
<P>这一点上，表明了OO的继承与日常生活中的继承的本质区别。举一个例子：生物学的分类体系中把企鹅归属为鸟类。我们模仿这个体系，设计出这样的类和关系。 
<P>&nbsp;<IMG height=283 alt=lsp-fig1.jpg src="http://www.blogjava.net/images/blogjava_net/ghawk/OOD/lsp-fig1.jpg" width=451 border=0> 
<P>类“鸟”中有个方法fly，企鹅自然也继承了这个方法，可是企鹅不能飞阿，于是，我们在企鹅的类中覆盖了fly方法，告诉方法的调用者：企鹅是不会飞的。这完全符合常理。但是，这违反了LSP，企鹅是鸟的子类，可是企鹅却不能飞！需要注意的是，此处的“鸟”已经不再是生物学中的鸟了，它是软件中的一个类、一个抽象。 
<P>有人会说，企鹅不能飞很正常啊，而且这样编写代码也能正常编译，只要在使用这个类的客户代码中加一句判断就行了。但是，这就是问题所在！首先，客户代码和“企鹅”的代码很有可能不是同时设计的，在当今软件外包一层又一层的开发模式下，你甚至根本不知道两个模块的原产地是哪里，也就谈不上去修改客户代码了。客户程序很可能是遗留系统的一部分，很可能已经不再维护，如果因为设计出这么一个“企鹅”而导致必须修改客户代码，谁应该承担这部分责任呢？（大概是上帝吧，谁叫他让“企鹅”不能飞的。^_^）“修改客户代码”直接违反了OCP，这就是OCP的重要性。违反LSP将使既有的设计不能封闭！ 
<P>修正后的设计如下： 
<P>&nbsp;<IMG height=353 alt=lsp-fig2.jpg src="http://www.blogjava.net/images/blogjava_net/ghawk/OOD/lsp-fig2.jpg" width=498 border=0> 
<P>但是，这就是LSP的全部了么？书中给了一个经典的例子，这又是一个不符合常理的例子：正方形不是一个长方形。这个悖论的详细内容能在网上找到，我就不多废话了。 
<P>LSP并没有提供解决这个问题的方案，而只是提出了这么一个问题。 
<P>于是，工程师们开始关注如何确保对象的行为。1988年，B. Meyer提出了Design by Contract（契约式设计）理论。DbC从形式化方法中借鉴了一套确保对象行为和自身状态的方法，其基本概念很简单： 
<OL>
<LI>每个方法调用之前，该方法应该校验传入参数的正确性，只有正确才能执行该方法，否则认为调用方违反契约，不予执行。这称为前置条件(Pre-condition)。 
<LI>一旦通过前置条件的校验，方法必须执行，并且必须确保执行结果符合契约，这称之为后置条件(Post-condition)。 
<LI>对象本身有一套对自身状态进行校验的检查条件，以确保该对象的本质不发生改变，这称之为不变式(Invariant)。</LI></OL>
<P>以上是单个对象的约束条件。为了满足LSP，当存在继承关系时，子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松；而子类中方法的后置条件必须与超类中被覆盖的方法的后置条件相同或者更为严格。 
<P>一些OO语言中的特性能够说明这一问题： 
<UL>
<LI>继承并且覆盖超类方法的时候，子类中的方法的可见性必须等于或者大于超类中的方法的可见性，子类中的方法所抛出的受检异常只能是超类中对应方法所抛出的受检异常的子类。 
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><IMG id=Codehighlighter1_23_72_Open_Image onclick="this.style.display='none'; Codehighlighter1_23_72_Open_Text.style.display='none'; Codehighlighter1_23_72_Closed_Image.style.display='inline'; Codehighlighter1_23_72_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align=top><IMG id=Codehighlighter1_23_72_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_23_72_Closed_Text.style.display='none'; Codehighlighter1_23_72_Open_Image.style.display='inline'; Codehighlighter1_23_72_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align=top><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">class</SPAN><SPAN style="COLOR: #000000">&nbsp;SuperClass</SPAN><SPAN id=Codehighlighter1_23_72_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_23_72_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG id=Codehighlighter1_69_70_Open_Image onclick="this.style.display='none'; Codehighlighter1_69_70_Open_Text.style.display='none'; Codehighlighter1_69_70_Closed_Image.style.display='inline'; Codehighlighter1_69_70_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><IMG id=Codehighlighter1_69_70_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_69_70_Closed_Text.style.display='none'; Codehighlighter1_69_70_Open_Image.style.display='inline'; Codehighlighter1_69_70_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">void</SPAN><SPAN style="COLOR: #000000">&nbsp;methodA()&nbsp;</SPAN><SPAN style="COLOR: #0000ff">throws</SPAN><SPAN style="COLOR: #000000">&nbsp;IOException</SPAN><SPAN id=Codehighlighter1_69_70_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_69_70_Open_Text><SPAN style="COLOR: #000000">{}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR><IMG id=Codehighlighter1_116_198_Open_Image onclick="this.style.display='none'; Codehighlighter1_116_198_Open_Text.style.display='none'; Codehighlighter1_116_198_Closed_Image.style.display='inline'; Codehighlighter1_116_198_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align=top><IMG id=Codehighlighter1_116_198_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_116_198_Closed_Text.style.display='none'; Codehighlighter1_116_198_Open_Image.style.display='inline'; Codehighlighter1_116_198_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align=top></SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">class</SPAN><SPAN style="COLOR: #000000">&nbsp;SubClassA&nbsp;</SPAN><SPAN style="COLOR: #0000ff">extends</SPAN><SPAN style="COLOR: #000000">&nbsp;SuperClass</SPAN><SPAN id=Codehighlighter1_116_198_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_116_198_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">this&nbsp;overriding&nbsp;is&nbsp;illegal.</SPAN><SPAN style="COLOR: #008000"><BR><IMG id=Codehighlighter1_195_196_Open_Image onclick="this.style.display='none'; Codehighlighter1_195_196_Open_Text.style.display='none'; Codehighlighter1_195_196_Closed_Image.style.display='inline'; Codehighlighter1_195_196_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><IMG id=Codehighlighter1_195_196_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_195_196_Closed_Text.style.display='none'; Codehighlighter1_195_196_Open_Image.style.display='inline'; Codehighlighter1_195_196_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align=top></SPAN><SPAN style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">private</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">void</SPAN><SPAN style="COLOR: #000000">&nbsp;methodA()&nbsp;</SPAN><SPAN style="COLOR: #0000ff">throws</SPAN><SPAN style="COLOR: #000000">&nbsp;Exception</SPAN><SPAN id=Codehighlighter1_195_196_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_195_196_Open_Text><SPAN style="COLOR: #000000">{}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR><IMG id=Codehighlighter1_242_330_Open_Image onclick="this.style.display='none'; Codehighlighter1_242_330_Open_Text.style.display='none'; Codehighlighter1_242_330_Closed_Image.style.display='inline'; Codehighlighter1_242_330_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align=top><IMG id=Codehighlighter1_242_330_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_242_330_Closed_Text.style.display='none'; Codehighlighter1_242_330_Open_Image.style.display='inline'; Codehighlighter1_242_330_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align=top></SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">class</SPAN><SPAN style="COLOR: #000000">&nbsp;SubClassB&nbsp;</SPAN><SPAN style="COLOR: #0000ff">extends</SPAN><SPAN style="COLOR: #000000">&nbsp;SuperClass</SPAN><SPAN id=Codehighlighter1_242_330_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_242_330_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">this&nbsp;overriding&nbsp;is&nbsp;OK.</SPAN><SPAN style="COLOR: #008000"><BR><IMG id=Codehighlighter1_327_328_Open_Image onclick="this.style.display='none'; Codehighlighter1_327_328_Open_Text.style.display='none'; Codehighlighter1_327_328_Closed_Image.style.display='inline'; Codehighlighter1_327_328_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><IMG id=Codehighlighter1_327_328_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_327_328_Closed_Text.style.display='none'; Codehighlighter1_327_328_Open_Image.style.display='inline'; Codehighlighter1_327_328_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align=top></SPAN><SPAN style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">void</SPAN><SPAN style="COLOR: #000000">&nbsp;methodA()&nbsp;</SPAN><SPAN style="COLOR: #0000ff">throws</SPAN><SPAN style="COLOR: #000000">&nbsp;FileNotFoundException</SPAN><SPAN id=Codehighlighter1_327_328_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_327_328_Open_Text><SPAN style="COLOR: #000000">{}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</SPAN></SPAN></DIV><BR>
<LI>从Java5开始，子类中的方法的返回值也可以是对应的超类方法的返回值的子类。这叫做“协变”(Covariant)<BR>
<DIV style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><IMG id=Codehighlighter1_24_71_Open_Image onclick="this.style.display='none'; Codehighlighter1_24_71_Open_Text.style.display='none'; Codehighlighter1_24_71_Closed_Image.style.display='inline'; Codehighlighter1_24_71_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align=top><IMG id=Codehighlighter1_24_71_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_24_71_Closed_Text.style.display='none'; Codehighlighter1_24_71_Open_Image.style.display='inline'; Codehighlighter1_24_71_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align=top><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">class</SPAN><SPAN style="COLOR: #000000">&nbsp;SuperClass&nbsp;</SPAN><SPAN id=Codehighlighter1_24_71_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_24_71_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG id=Codehighlighter1_51_69_Open_Image onclick="this.style.display='none'; Codehighlighter1_51_69_Open_Text.style.display='none'; Codehighlighter1_51_69_Closed_Image.style.display='inline'; Codehighlighter1_51_69_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><IMG id=Codehighlighter1_51_69_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_51_69_Closed_Text.style.display='none'; Codehighlighter1_51_69_Open_Image.style.display='inline'; Codehighlighter1_51_69_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;Number&nbsp;caculate()</SPAN><SPAN id=Codehighlighter1_51_69_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_51_69_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN><SPAN style="COLOR: #000000">;<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/None.gif" align=top><BR><IMG id=Codehighlighter1_114_199_Open_Image onclick="this.style.display='none'; Codehighlighter1_114_199_Open_Text.style.display='none'; Codehighlighter1_114_199_Closed_Image.style.display='inline'; Codehighlighter1_114_199_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockStart.gif" align=top><IMG id=Codehighlighter1_114_199_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_114_199_Closed_Text.style.display='none'; Codehighlighter1_114_199_Open_Image.style.display='inline'; Codehighlighter1_114_199_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedBlock.gif" align=top></SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">class</SPAN><SPAN style="COLOR: #000000">&nbsp;SubClass&nbsp;</SPAN><SPAN style="COLOR: #0000ff">extends</SPAN><SPAN style="COLOR: #000000">&nbsp;SuperClass</SPAN><SPAN id=Codehighlighter1_114_199_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_114_199_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #008000">//</SPAN><SPAN style="COLOR: #008000">only&nbsp;compiles&nbsp;in&nbsp;Java&nbsp;5&nbsp;or&nbsp;later.</SPAN><SPAN style="COLOR: #008000"><BR><IMG id=Codehighlighter1_179_197_Open_Image onclick="this.style.display='none'; Codehighlighter1_179_197_Open_Text.style.display='none'; Codehighlighter1_179_197_Closed_Image.style.display='inline'; Codehighlighter1_179_197_Closed_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockStart.gif" align=top><IMG id=Codehighlighter1_179_197_Closed_Image style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_179_197_Closed_Text.style.display='none'; Codehighlighter1_179_197_Open_Image.style.display='inline'; Codehighlighter1_179_197_Open_Text.style.display='inline';" src="http://www.blogjava.net/images/OutliningIndicators/ContractedSubBlock.gif" align=top></SPAN><SPAN style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">public</SPAN><SPAN style="COLOR: #000000">&nbsp;Integer&nbsp;caculate()</SPAN><SPAN id=Codehighlighter1_179_197_Closed_Text style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff"><IMG src="http://www.blogjava.net/images/dot.gif"></SPAN><SPAN id=Codehighlighter1_179_197_Open_Text><SPAN style="COLOR: #000000">{<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/InBlock.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN style="COLOR: #0000ff">return</SPAN><SPAN style="COLOR: #000000">&nbsp;</SPAN><SPAN style="COLOR: #0000ff">null</SPAN><SPAN style="COLOR: #000000">;<BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedSubBlockEnd.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}</SPAN></SPAN><SPAN style="COLOR: #000000"><BR><IMG src="http://www.blogjava.net/images/OutliningIndicators/ExpandedBlockEnd.gif" align=top>}</SPAN></SPAN></DIV></LI></UL>
<P>可以看出，以上这些特性都非常好地遵循了LSP。但是DbC呢？很遗憾，主流的面向对象语言（不论是动态语言还是静态语言）还没有加入对DbC的支持。但是随着AOP概念的产生，相信不久DbC也将成为OO语言的一个重要特性之一。 
<P>一些题外话： 
<P>前一阵子《敲响OO时代的丧钟》和<A href="/raimundox/archive/2005/12/20/24851.html">《丧钟为谁而鸣》</A>两篇文章引来了无数议论。其中提到了不少OO语言的不足。事实上，遵从LSP和OCP，不管是静态类型还是动态类型系统，只要是OO的设计，就应该对对象的行为有严格的约束。这个约束并不仅仅体现在方法签名上，而是这个具体行为的本身。这才是LSP和DbC的真谛。从这一点来说并不能说明“万事万物皆对象”的动态语言和“C++，Java”这种“按接口编程”语言的优劣，两类语言都有待于改进。庄兄对DJ的设想倒是开始引入DbC的概念了。这一点还是非常值得期待的。^_^<BR>另外，接口的语义正被OCP、LSP、DbC这样的概念不断地强化，接口表达了对象行为之间的“契约”关系。而不是简单地作为一种实现多继承的语法糖。</P><img src ="http://www.blogjava.net/ghawk/aggbug/28545.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ghawk/" target="_blank">GHawk</a> 2006-01-18 18:12 <a href="http://www.blogjava.net/ghawk/archive/2006/01/18/28545.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>敏捷软件开发 读书笔记 （3）——OO五大原则（2.OCP——开闭原则）</title><link>http://www.blogjava.net/ghawk/archive/2006/01/18/28394.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Tue, 17 Jan 2006 16:26:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/01/18/28394.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/28394.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/01/18/28394.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/28394.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/28394.html</trackback:ping><description><![CDATA[<P>开闭原则很简单，一句话：“Closed for Modification; Open for Extension”——“对变更关闭；对扩展开放”。开闭原则其实没什么好讲的，我将其归结为一个高层次的设计总则。就这一点来讲，OCP的地位应该比SRP优先。 
<P>OCP的动机很简单：软件是变化的。不论是优质的设计还是低劣的设计都无法回避这一问题。OCP说明了软件设计应该尽可能地使架构稳定而又容易满足不同的需求。 
<P>为什么要OCP？答案也很简单——重用。 
<P>“重用”，并不是什么软件工程的专业词汇，它是工程界所共用的词汇。早在软件出现前，工程师们就在实践“重用”了。比如机械产品，通过零部件的组装得到最终的能够使用的工具。由于机械部件的设计和制造过程是极其复杂的，所以互换性是一个重要的特性。一辆车可以用不同的发动机、不同的变速箱、不同的轮胎……很多东西我们直接买来装上就可以了。这也是一个OCP的例子。（可能是由于我是搞机械出身的吧，所以就举些机械方面的例子^_^）。 
<P>如何在OO中引入OCP原则？把对实体的依赖改为对抽象的依赖就行了。下面的例子说明了这个过程：</P>
<P>05赛季的时候，一辆F1赛车有一台V10引擎。但是到了06赛季，国际汽联修改了规则，一辆F1赛车只能安装一台V8引擎。车队很快投入了新赛车的研发，不幸的是，从工程师那里得到消息，旧车身的设计不能够装进新研发的引擎。我们不得不为新的引擎重新打造车身，于是一辆新的赛车诞生了。但是，麻烦的事接踵而来，国际汽联频频修改规则，搞得设计师在“赛车”上改了又改，最终变得不成样子，只能把它废弃。 
<P><IMG height=183 alt=OCP-fig1.JPG src="http://www.blogjava.net/images/blogjava_net/ghawk/OOD/OCP-fig1.JPG" width=437 border=0> 
<P>为了能够重用这辆昂贵的赛车，工程师们提出了解决方案：首先，在车身的设计上预留出安装引擎的位置和管线。然后，根据这些设计好的规范设计引擎（或是引擎的适配器）。于是，新的赛车设计方案就这样诞生了。 
<P>&nbsp;<IMG height=441 alt=OCP-fig2.JPG src="http://www.blogjava.net/images/blogjava_net/ghawk/OOD/OCP-fig2.JPG" width=646 border=0>
<P>显然，通过重构，这里应用的是一个典型的Bridge模式。这个实现的关键之处在于我们预先给引擎留出了位置！我们不必因为对引擎的规则的频频变更而制造相当多的车身，而是尽可能地沿用和改良现有的车身。<BR>说到这里，想说一说OO设计的一个误区。<BR>学习OO语言的时候，为了能够说明“继承”（或者说“is-a”）这个概念，教科书上经常用实际生活中的例子来解释。比如汽车是车，电车是车，F1赛车是汽车，所以车是汽车、电车、F1赛车的上层抽象。这个例子并没有错。问题是，这样的例子过于“形象”了！如果OO设计直接就可以将现实生活中的概念引用过来，那也就不需要什么软件工程师了！OO设计的关键概念是抽象。如果没有抽象，那所有的软件工程师的努力都是徒劳的。因为如果没有抽象，我们只能去构造世界中每一个对象。上面这个例子中，我们应该看到“引擎”这个抽象的存在，因为车队的工程师们为它预留了位置，为它制定了设计规范。<BR>上面这个设计也实现了后面要说的DIP（依赖倒置原则）。但是请记住，OCP是OO设计原则中高层次的原则，其余的原则对OCP提供了不同程度的支持。为了实现OCP，我们会自觉或者不自觉地用到其它原则或是诸如Bridge、Decorator等设计模式。然而，对于一个应用系统而言，实现OCP并不是设计目的，我们所希望的只是一个稳定的架构。所以对OCP的追求也应该适可而止，不要陷入过渡设计。正如Martin本人所说：“No significant program can be 100% closed.”“Closure not complete but strategic”<BR><BR>（下一篇就要讲LSP了，我觉得这是意义最为重要的OO设计原则，它直指当今主流OO语言的软肋，点出了OO设计的精髓。）</P><img src ="http://www.blogjava.net/ghawk/aggbug/28394.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ghawk/" target="_blank">GHawk</a> 2006-01-18 00:26 <a href="http://www.blogjava.net/ghawk/archive/2006/01/18/28394.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>敏捷软件开发 读书笔记 （2）——OO五大原则（1.SRP 单一职责原则）</title><link>http://www.blogjava.net/ghawk/archive/2006/01/09/27312.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Mon, 09 Jan 2006 13:17:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/01/09/27312.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/27312.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/01/09/27312.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/27312.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/27312.html</trackback:ping><description><![CDATA[<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;一点说明：OO的五大原则是指SRP、OCP、LSP、DIP、ISP。这五个原则是书中所提到的。除此之外，书中还提到一些高层次的原则用于组织高层的设计元素，这些放到下次再写。当然，OO设计的原则可能不止这五个，希望大家多提宝贵意见，多多交流。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在学习和使用OO设计的时候，我们应该明白：OO的出现使得软件工程师们能够用更接近真实世界的方法描述软件系统。然而，软件毕竟是建立在抽象层次上的东西，再怎么接近真实，也不能替代真实或被真实替代。</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OO设计的五大原则之间并不是相互孤立的。彼此间存在着一定关联，一个可以是另一个原则的加强或是基础。违反其中的某一个，可能同时违反了其余的原则。因此应该把这些原则融会贯通，牢记在心！</P>
<P>1. SRP（Single Responsibility Principle 单一职责原则）<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;单一职责很容易理解，也很容易实现。所谓单一职责，就是一个设计元素只做一件事。什么是“只做一件事”？简单说就是少管闲事。现实中就是如此，如果要你专心做一件事情，任何人都有信心可以做得很出色。但如果，你整天被乱七八糟的事所累，还有心思和精力把每件事都作好么？<BR><IMG height=250 alt=fig-1.JPG src="http://www.blogjava.net/images/blogjava_net/ghawk/OOD/fig-1.JPG" width=127 align=left border=0><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;“单一职责”就是要在设计中为每种职责设计一个类，彼此保持正交，互不干涉。这个雕塑（二重奏）就是正交的一个例子，钢琴家和小提琴家各自演奏自己的乐谱，而结果就是一个和谐的交响乐。当然，真实世界中，演奏小提琴和弹钢琴的必须是两个人，但是在软件中，我们往往会把两者甚至更多搅和到一起，很多时候只是为了方便或是最初设计的时候没有想到。&nbsp;<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这样的例子在设计中很常见，书中就给了一个很好的例子：调制解调器。这是一个调制解调器最基本的功能。但是这个类事实上完成了两个职责：连接的建立和中断、数据的发送和接收。显然，这违反了SRP。这样做会有潜在的问题：当仅需要改变数据连接方式时，必须修改Modem类，而修改Modem类的结果就是使得任何依赖Modem类的元素都需要重新编译，不管它是不是用到了数据连接功能。解决的办法，书中也已经给出：重构Modem类，从中抽出两个接口，一个专门负责连接、另一个专门负责数据发送。依赖Modem类的元素也要做相应的细化，根据职责的不同分别依赖不同的接口。最后由ModemImplementation类实现这两个接口。<BR><IMG height=258 alt=fig-2.JPG src="http://www.blogjava.net/images/blogjava_net/ghawk/OOD/fig-2.JPG" width=661 border=0>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;从这个例子中，我们不难发现，违反SRP通常是由于过于“真实”地设计了一个类所造成的。因此，解决办法是往更高一层进行抽象化提取，将对某个具体类的依赖改变为对一组接口或抽象类的依赖。当然，这个抽象化的提取应该根据需要设计，而不是盲目提取。比如刚才这个Modem的例子中，如果有必要，还可以把DataChannel抽象为DataSender和DataReceiver两个接口。<BR>&nbsp;</P><img src ="http://www.blogjava.net/ghawk/aggbug/27312.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ghawk/" target="_blank">GHawk</a> 2006-01-09 21:17 <a href="http://www.blogjava.net/ghawk/archive/2006/01/09/27312.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>敏捷软件开发 读书笔记 （1）——设计的目标</title><link>http://www.blogjava.net/ghawk/archive/2006/01/06/26927.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Fri, 06 Jan 2006 10:17:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/01/06/26927.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/26927.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/01/06/26927.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/26927.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/26927.html</trackback:ping><description><![CDATA[<P>软件设计是一种抽象活动，设计所要实现的是产出代码。就这一点来说，任何人都会设计。但是，正如我们日常生活中所耳闻目睹或亲身经历，设计有优劣之分。 
<P>从项目管理的角度去理解，设计是为了满足涉众（Stakeholders）的需求。显然，一个设计应该满足客户对系统的功能及非功能需求。但单是满足了这一点，并不能称为一个好的设计。因为开发者同样属于涉众！而开发者的需求又是怎样的呢？至少，应该有以下几条吧： 
<UL>
<LI>老板希望软件交付后，不应该有很高的维护成本。如果开发人员为了维护而经常出差或者加班且久久不能投入新项目，显然，换了谁是老板都不愿意这种事情发生。 
<LI>开发人员呢？谁愿意放弃和家人朋友而拼死拼活在单位加班，总是有这么多麻烦事缠着你，烦不烦哪！ 
<LI>……等等 </LI></UL>
<P>所以，设计应该尽可能多地照顾到维护和变更。 
<P>为了兼顾各户满意和维护成本，设计应该不断挑战其终极目标——松耦合。不管是XP或UP，这个目标都不会改变。OO设计中的五大原则，其根本目的就是降低组件间的耦合度，避免牵一发则动全身的现象发生。降低耦合度不仅能够提高软件内在的质量，还能大大减少不必要的编译时间、减少向版本控制系统提交源码的网络开销……<BR><BR>如何鉴别设计的这一指标？软件工程中有专用的度量：CBO（Coupling Between Objects)，那是由公式计算出来的，也有很多工具支持，值得一试。（听过几次李维先生的讲座，他经常拿Together的度量功能炫耀^_^） 
<P>但是，作为一个开发人员，对手中的代码应该有适当的敏感性。毕竟，这些代码是你亲手创造的，谁不希望自己的作品得到众人的赞许？或许能换得一次加薪升职的机会^_^ 退一步，这可关系到宝贵的休息时间啊。所以，开发者应该对自己的产品有这样一种意识：及时修正设计中不合理的地方。 
<P>敏捷过程告诉我们：在代码“有味道”的时候进行重构。“有味道”是代码正在变质的标志，很遗憾，能够使代码保持原味的防腐剂还没发明。为了保证代码质量，及时重构是必要的。这就像在烧烤的时候为了防止烤焦，你得坐在炉子前经常翻动肉块一样。 
<P>如何闻出代码的味道？认真学习一下OO吧，别以为OO很简单，就是继承+封装+多态，谁都会。即使是书中记述的五大原则，想要运用自如，也得多感觉感觉才行。很多时候，我们不知不觉就把蛆虫放进了代码中…… 
<P>好了，下一篇：OO五大原则</P><img src ="http://www.blogjava.net/ghawk/aggbug/26927.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ghawk/" target="_blank">GHawk</a> 2006-01-06 18:17 <a href="http://www.blogjava.net/ghawk/archive/2006/01/06/26927.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>敏捷软件开发 读书笔记 （序——废话）</title><link>http://www.blogjava.net/ghawk/archive/2005/12/27/25553.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Tue, 27 Dec 2005 04:00:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2005/12/27/25553.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/25553.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2005/12/27/25553.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/25553.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/25553.html</trackback:ping><description><![CDATA[<P><IMG height=302 alt=7-5083-1503-0l.gif src="http://www.blogjava.net/images/blogjava_net/ghawk/Book%20Cover/7-5083-1503-0l.gif" width=240 border=0> 
<P>最近正在读这本书，喜欢影印版，是因为书中漂亮的插图。：）惭愧，如此的好书到现在才去读。<BR>准备边读边记录些心得，今天先说些废话。：P 
<P>先粗略地概览了一遍全书。本书主要分以下几个部分： 
<P>
<OL>
<LI>敏捷软件过程。主要以XP为例。这部分的最后一章，用一个对话式的小故事讲述了一个非常小的过程。给了读者关于敏捷过程的形象化的认识。</LI>
<LI>敏捷设计。这部分是个很大的看点。它讲述了设计中一些常见的问题，及其应对（用几个经典的设计原则）。</LI>
<LI>案例实践。讲述了如何利用设计模式去实践第二部分中提到的设计原则和避免设计中的“味道”。</LI></OL>
<P>之所以觉得这本书好，还与一个人有关。就是交大软件学院的林德彰老师。林先生的课，风趣幽默，能够用直观形象的语言让学生对讲课内容产生深刻的印象。（我可不是托儿，网上能搜到些林先生讲课的片断，要是怀疑，可以验证一番）。记得在软件工程这门课里，林先生给我们讲了很多有关设计原则的内容，其中就有“开闭原则（OCP）”、“里氏替换原则（LSP）”等……就把这本书当作是一本补充读物吧。 
<P>言归正传。个人感觉这本书的总体风格，就和所要讲的“敏捷”一样，并不带着厚重的学院派风味，而是更注重实践。并不是没有理论，只是把理论融入到了实践中，简化了理论的复杂性。读起来感觉很带劲儿。 
<P>废话说到这里，下一步的计划就是跟着自己的进度写读书心得了。我想把对书中内容的理解和以前在林先生的课上所学的结合在一起，导出阅读此书时的大脑活动镜像。</P><img src ="http://www.blogjava.net/ghawk/aggbug/25553.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/ghawk/" target="_blank">GHawk</a> 2005-12-27 12:00 <a href="http://www.blogjava.net/ghawk/archive/2005/12/27/25553.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>