﻿<?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/5963.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 12:08:02 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 12:08:02 GMT</pubDate><ttl>60</ttl><item><title>一个XPer提供的一些经验</title><link>http://www.blogjava.net/ghawk/archive/2006/08/24/65536.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Thu, 24 Aug 2006 07:45:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/08/24/65536.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/65536.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/08/24/65536.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/65536.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/65536.html</trackback:ping><description><![CDATA[
		<p>前些天，和一位XPer进行了一次愉快的谈话。他向我讲述了一些感觉很有效的实践。</p>
		<div align="left">关于过程和迭代<br />他曾经参与过的项目的迭代是以月为迭代单位的，但事实上每周都会重复一个简单的过程。<br />在迭代过程中，他非常推崇<b><font color="#a52a2a">Burn-Down Charts</font></b>。这是一个Scrum的工具。通过Burn-Down Charts，能够把过程中间的变化记录下来，使过程高度可视化。等到一次迭代完成，回顾一下所有的Burn-Down Charts就能作为改进的判断依据。<br /><b><font color="#a52a2a">KPT Meeting</font></b>。所谓KPT Meeting就是 Keep-Prevent-Try metting。小组定期举行KPT会议（基本上是每周一次）。在KTP会议上，通过头脑风暴的方式每个人（<font color="#ff0000"><b>不是某几个人</b></font>）把各自认为前一阶段里做得好的方面写在Keep一栏里；做得不好的方面写在Prevent一栏里；希望尝试的写在Try一栏里。然后大家对这些项目进行评估和筛选。下一阶段中，Keep的项目继续保持，Prevent的项目应该杜绝，Try的项目进行尝试。<br /><br />工具<br />在开展这些实践的时候，交流比较频繁。首推的工具是<font color="#a52a2a"><b>Mini white board</b></font>和<font color="#a52a2a"><b>DC</b></font>。<br />选择Mini white board的原因并不是因为带有"mini"听上去会像 Mini Cooper 或者 iPod mini 那么cool。因为一块A3左右大小的白板非常适合个人或者结对使用，而且环保（省去了草稿纸）。虽然整个团队也有用于大规模交流的更大的白板，但那属于“竞争资源”，各自使用自己的白板更为方便。<br />交流结果产生后，为了不花不必要的时间去做精美的文档，一台轻便的DC往往是最合适的选择。当然，如果足够，手机上的照相功能也可以完成同样的任务。相比偷拍街上的MM，这些电子产品能够实现更大的价值。<br /><br />关于结对<br />每天进行6小时的结对编程，分3次，每次2小时。每次和不同的成员组队。在结队的时候充分利用了上面提到的工具进行交流。如果出现两个人不能解决的问题的时候，会立即向整个团队提出，这样可能导致一次stand-up meeting。即使问题不能马上解决，至少也能确保每个人都知道这个问题。<br /><br /></div>
<img src ="http://www.blogjava.net/ghawk/aggbug/65536.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-08-24 15:45 <a href="http://www.blogjava.net/ghawk/archive/2006/08/24/65536.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UP &amp; XP之争，意义何在？(续)</title><link>http://www.blogjava.net/ghawk/archive/2006/04/25/43027.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Tue, 25 Apr 2006 07:03:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/04/25/43027.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/43027.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/04/25/43027.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/43027.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/43027.html</trackback:ping><description><![CDATA[
		<p>虽然我没能去参加BEA的活动，但是相关的资料已经下载并且浏览过了，确实收获不少。所以，对于庄兄的这些想法我很理解。</p>
		<p>相信不只你我，大部分的人都比较认同敏捷化的过程，希望使过程变得敏捷。的确，这是个好东西，之前我也说过“敏捷过程是三赢的”这样的话。</p>
		<p>我所关心的问题是“如何能够用好XP？”。</p>
		<p>庄兄认为“汤的味道，不需要什么过程控制”，我也会认同。为什么？因为你我都是中国人。大部分中国人不会认为汤的味道需要什么过程控制。但是想想看，如果你在不同地方买到的肯德基炸鸡味道各异；同一批次生产的同型号的汽车形状各异；银行里取出来的一叠百元大钞大小不一，你不会觉得奇怪么或是有那么一点点愤怒么？</p>
		<p>西方人（甚至学习西方的日本人）对品质的重视程度却完全不同。他们不允许肯德基炸鸡的味道有很大偏差（即便你觉得无所谓）；“2毫米工程”不允许整车的总装长度发生2毫米以上的偏差（即便你觉得无所谓）；百元大钞……（我想谁都不会无所谓）。</p>
		<p>
				<font color="#ff0000">
						<strong>所以，一切质量都有标准，一切标准都应该被度量！这就是工程学的目标之一，为了实现更严格的质量标准，就需要过程控制和度量。</strong>
				</font>
		</p>
		<p>庄兄所说，用测试用例保证代码的质量其实还是采用了“测试用例”作为度量的标准。唯一的问题是：“如何确保测试用例的质量”。显然，我们不能把一把不直的尺子度量出来的结果作为可靠的参考依据。怎么解决呢？“结对编程”么？嗯，这是一个不错的方式，那么最终该信赖谁呢？是Pair中的A还是B呢？或者，是Leader么？那么又是谁提出的要求呢？是老板么？还是客户？政府？法规？市场？……问题没有终结了。</p>
		<p>不要学习哲学家的方法，提出一层又一层无法解决的问题。我们是工程师，应该试图解决问题才对！解决问题的关键在于，<font color="#ff0000"><strong>XP同样需要标准！</strong></font>为了制定标准，必要的文档是不可以少的。而且，标准本身的质量是严苛的。因为，作为标准，他不可以含糊其辞、模棱两可。在标准的基础之上，我们才可以谈什么TDD、Pair Programming之类的实践。</p>
		<p>回到争论的开端。我引用了林先生的话“UP是正楷；XP是草书。要先学好UP才能学好XP，先学XP会乱套。”我对这句话的理解如下：这句话并没有批判UP或是XP，只是指出了一个学习的顺序。我认为这句话是有实践依据的，因为UP强调的是一种经典的工程方法。软件工程本来就源于其他行业的工程实践经验。UP利用大量的文档对开发活动进行约束和记录。正是这种重量级的过程规范了规范了从PM到Coder的所有活动，有问题可以参照文档，看看自己应该怎么做。文档也可以作为日后评估这个过程的依据。随着整个团队和每个个人的经验不断积累，开发活动中的日常行为渐渐形成了一种职业习惯。然后可以通过对UP的配置，逐渐减少文档的使用量，一些没有必要的文档就可以省去，更具团队的实际能力调整过程。<font color="#ff0000"><strong>UP是可配置的，不必要的文档没有存在的理由，这一点UP和XP没有什么两样。</strong></font>当然，随着大家的职业习惯越来越好，经验越来越丰富，个人和团队就可以采用更敏捷更轻便的过程，逐渐过渡到XP上去。</p>
		<p>反过来，如果一开始就没有详尽的文档，很多活动（比如设计、版本控制）往往会脱离控制，进入一种无序的、混乱的状态。没有文档可参考，就意味着很多问题只能问人，而不同人的回答可能各异，同一个人对同一个问题的两次回答也可能不同！当然，如果整个团队的工程素养和个体的职业习惯都比较好的情况下可能不会发生类似的情况。<font color="#ff0000"><strong>但是这种工程素养和职业习惯从哪里来，可能单靠的XP是不足以培养出来的。</strong></font></p>
		<p>“UP是正楷；XP是草书。要先学好UP才能学好XP，先学XP会乱套。”这句话表明了UP和XP在一定程度上是存在冲突的，并且提出了一条路线去降低和避免这个冲突。</p>
		<p>再次需要强调的是庄兄所提到的“XP是一种思想”，这点我认同。但是我认为这个除了思想之外，还是一种“文化”。这种思想和文化也是出于软件工程多年来的实践，其中也不免有UP等其他过程。不能简单地认为“我们只要吸取历史的教训，提出新的思想和文化就不会再犯同样的错误了。”很多时候历史总是一次又一次地重演着。<font color="#ff0000"><strong>新的思想和文化如果不能被准确地理解和运用，它所带来的可能仍然是它原本想解决的问题。只有我们具备了引入这种文化的基础，才能把它变成自己的文化，否则这仍然是挂在嘴边行于表面的一种不求精髓只求模仿的伪文化、伪思想。这一点对于UP和XP的实践者来说没有什么两样。</strong></font></p>
<img src ="http://www.blogjava.net/ghawk/aggbug/43027.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-04-25 15:03 <a href="http://www.blogjava.net/ghawk/archive/2006/04/25/43027.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UP &amp; XP之争，意义何在？</title><link>http://www.blogjava.net/ghawk/archive/2006/04/23/42691.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Sun, 23 Apr 2006 10:28:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/04/23/42691.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/42691.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/04/23/42691.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/42691.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/42691.html</trackback:ping><description><![CDATA[
		<p>不光是做软件，凡是做产品，最后关注的总是产品的<font color="#ff0000"><strong>质量</strong></font>。</p>
		<p>举个例子，比如你做一锅汤：<br />今天你状态很好，做完后尝了尝，感觉很美味，你的家人尝了以后也有同感，喝完后感觉心情舒畅、意犹未尽。<br />隔了一个礼拜，你做同样的汤给家里人喝。做完后你尝了尝，感觉依然美味，盼望着得到家人的赏识，然而他们却说味道咸了点。你很奇怪，为什么同样自己尝过了，家里人却感觉不一样呢？是不是最近加班多了，休息不好，味觉不准了？<br />一个月过后，你要去国外出差，给家里请了个临时保姆。一天，他也做了这么个汤，做完后，他也尝了尝，感觉口味很不错，可是端上桌，家里人说这汤太辣了。原来这保姆才从湖南老家出来不久……</p>
		<p>因此，只把焦点放在最后的产品上往往是不够的。需要对“做汤的过程”加以控制。所以工程界会比较关注过程的管理，在软件领域也称作“软件生命周期管理”。</p>
		<p>再来看看UP和XP。它们都属于软件过程，只不过各有特色。</p>
		<p>再拿刚才那个做汤的例子：<br />大家都听说过德国人的厨房像化学实验室，天平、计时器、量杯……装备齐全，再配上精确的菜谱，严谨的德国人能够确保不用尝那最后一口都做出口味基本一致的汤。<br />换了中国人，大部分人都不会模仿德国人做菜的方式。解决方案很简单，让你的太太和孩子都尝那最后一口，再根据反馈调整几次，同样能做出全家人满意的汤。</p>
		<p>这个例子也许不太贴切，但是可以联想一下:<font color="#ff0000"><strong>德国人做汤倾向于UP;中国人做汤倾向于XP</strong></font>。</p>
		<p>UP和XP最终目的都是为了保证产品的质量，不同的是，两个过程所强调的方法不同。我想，没有人会说“UP的目的在于变态地追求文档的完美”、“UP是为了要程序员学会写各种各样文档”……之类的话。同时，也没人会说“XP就是不要文档只要代码”、“XP就是要变态地追求完美的代码”……这样的话。</p>
		<p>这些不正确的看法，只是人们对于这两种过程的误解。或许是来自于开发人员和项目经理的那些“不堪回首的经历”。</p>
		<p>“UP害惨了整个软件行业，让开发人员没完没了地写文档而忽略了代码，XP才是王道”这样的话，我不敢苟同，仍然有很多企业使用着UP这样的重型软件工程，就好比德国人依然喜欢把厨房弄得像个实验室。</p>
		<p>XP固然是个好东西。但是，不知道大多数人对于XP的热衷是出于对XP文化的理解，还是国人惯有的“一窝蜂”似的行为。<font color="#ff0000"><strong>不晓得一个“能够熟练阅读代码的Leader”是不是能够真正运用好XP，确保他的团队能够尽可能少地出现"Over engineering"这种违背Agile精神的东西，或是能够让他的团队保证“每周只工作40小时”这样的基本实践？</strong></font></p>
		<p>对于不同的技术和过程，应该给予冷静的分析和慎重的选择。每个过程和技术都不能以“正确”或“不正确”来定性，只能以“合适”和“不合适”来定性。因为正确或不正确是要严格证明的，而合适不合适是来源于工程实践的结果。所以，COBOL依然在金融领域起着举足轻重的作用，科学家们仍不忘Fortran，汇编和C仍然健在……</p>
		<p>另外不得不提的是文化上的差异。为什么很多时候，我们学习国外的先进技术，购买了整套生产线，引进了全套图纸，请国外专家做了详细的全程化培训，国人生产出的产品品质依然不如国外原产的？这是每个中国人都应该思考的问题……</p>
		<p> </p>
<img src ="http://www.blogjava.net/ghawk/aggbug/42691.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-04-23 18:28 <a href="http://www.blogjava.net/ghawk/archive/2006/04/23/42691.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对"UP是正楷，XP是草书"的反思</title><link>http://www.blogjava.net/ghawk/archive/2006/03/01/33025.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Wed, 01 Mar 2006 08:25:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2006/03/01/33025.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/33025.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2006/03/01/33025.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/33025.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/33025.html</trackback:ping><description><![CDATA[
		<p>“UP是正楷，XP是草书。先学好了UP，才能学好XP；先学XP再学UP就会乱套。 ” </p>
		<p>
		</p>
		<p>老师曾这么说。最近，对这句话有了深刻的体会。 </p>
		<p>软件过程是一个以人为中心的活动。人是项目中最难确定和控制的因素。休息的质量、情绪的起伏都会影响整个活动。为了尽可能地约束这种个体的不确定行为和减少开发过程中不必要的误会。"UP"采用了大量的文档来对整个开发过程进行控制。这些文档主要分为以下几类： </p>
		<ul>
				<li>计划文档——项目的开发计划、迭代计划、测试计划等。 
</li>
				<li>技术文档——项目的设计文档、某个操作的说明文档等。 
</li>
				<li>记录文档——日常的会议纪要、每日进度反馈、评估报告等。 </li>
		</ul>
		<p>文档成了UP活动的主要部分。在UP中，往往大量的资源用于文档的制作。这些文档的目的是为了尽可能减少不必要的沟通成本和误会，也为了在发生问题的时候能够尽快确定原因找到解决方法。 </p>
		<p>
		</p>
		<p>而正是因为如此繁重的资源消耗，导致真正的设计和代码只占到了总开销的很少部分。这对很多人来说不可理解，甚至觉得本末倒置。于是很多敏捷方法诞生了，最具代表性也是对UP思想最具颠覆性的就属XP了。 </p>
		<p>
		</p>
		<p>对外，XP以快速的反应速度来响应客户的需求；对内，XP以高质量的代码和设计来确保尽可能不产生不必要的文档和资源开销。 </p>
		<p>
		</p>
		<p>从表面上看，在当今，XP确实是一种非常理想的开发过程。 </p>
		<p>
		</p>
		<p>但是，从没有过程到XP往往会非常失败。这是为什么？问题的关键还在于人。 </p>
		<p>
		</p>
		<ul>
				<li>无过程--&gt;UP --&gt;XP </li>
		</ul>
		<p>UP利用文档来约束和规范人们的开发活动。当一个没有经验的团队经历UP后，就等于把性格各异、习惯差别不同的人<font color="#a52a2a">统一</font>成了“相对较一致的开发人员”。 </p>
		<p>他们有一致的编码习惯，有共同的用语，有严格的规则。随着经验的积累，这个团队间的默契越来越高。此时，如果过程由UP向XP切换，付出的代价就会相对较低。 </p>
		<p>
		</p>
		<ul>
				<li>无过程--&gt;XP--&gt;UP </li>
		</ul>
		<p>XP主张快速反应。如果一个没有经验的团队在一开始就尝试XP，那么后果可能是惨痛的。因为一个没有经验的团队其成员间的相互了解颇少，对于一件事，往往十个人有十种想法。当缺少文档约束时，在以代码和设计为中心的活动中，成员之间往往因为水平的参差不齐导致无休止的讨论甚至争论，代码被不必要地频繁改动。这是因为，<font color="#ff0000"><strong>在团队建设早期，成员之间往往连最基本的尊重和信任都不存在。</strong></font> 这种无意义的活动往往会严重影响项目的正常进行。 </p>
		<p>
		</p>
		<p>所以，学习和应用过程不仅仅是个体的事，而是整个团队的事。只有当团队采用严格文档化的过程并且经过磨合后，才能渐渐向轻量级的过程迁移，逐渐将不必要的文档删减掉，采用更灵活的过程。但是，此时并不是“没有文档”而是“心中有文档”。</p>
<img src ="http://www.blogjava.net/ghawk/aggbug/33025.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-03-01 16:25 <a href="http://www.blogjava.net/ghawk/archive/2006/03/01/33025.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><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>敏捷软件开发 读书笔记 （序——废话）</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><item><title>过程的代价</title><link>http://www.blogjava.net/ghawk/archive/2005/12/14/23772.html</link><dc:creator>GHawk</dc:creator><author>GHawk</author><pubDate>Wed, 14 Dec 2005 01:57:00 GMT</pubDate><guid>http://www.blogjava.net/ghawk/archive/2005/12/14/23772.html</guid><wfw:comment>http://www.blogjava.net/ghawk/comments/23772.html</wfw:comment><comments>http://www.blogjava.net/ghawk/archive/2005/12/14/23772.html#Feedback</comments><slash:comments>5</slash:comments><wfw:commentRss>http://www.blogjava.net/ghawk/comments/commentRss/23772.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/ghawk/services/trackbacks/23772.html</trackback:ping><description><![CDATA[<P>这个月刚进入公司，加入了一个10人左右的团队，用Java做一个网站后台。<BR><BR>客户是日本公司，他们做了项目的大部分分析(Requirements, Use cases, Domain model...)。我们负责的是详细设计和开发。我是项目开始几星期后才进的公司。Schedule也已经为我分配好了。大家都按照schedule上的安排工作着。<BR><BR>上星期开会的时候得知，日本这次采用的是agile过程。而我们的schedule更类似于RUP这样的过程。RUP这个学院派和Agile这个造反派狭路相逢，问题也就出现了。<BR><BR>大家工作都很卖力，为了能按进度提交制品，有时还通宵达旦解决问题。我们这支团队的战斗力和信心是不容怀疑的。可是大家努力的结果换来的却是用户的抱怨。大家都困惑不解。问题究竟出在哪儿？<BR><BR>日方在项目中强调的是Agile过程，我们采用的则是传统的过程。一开始，两个过程方法之间的差异并不大；对我们提交的制品，客户也没有什么异议。但是，直到客户提出问题之前，我们所提交的制品都是一些设计文档。而我们的制品也仅限于此——没有一个可用的EAR包、没有写过 test case。很明显，我们犯了agile的大忌。<BR><BR>Agile所强调的是快速的构建、轻量级的迭代、TDD等。由于之前没有写test case，详细设计也没有test case可以参照。设计本身是不是合理，是不是testable也不得而知。致使在设计test case的时候无从下手，很多类甚至都没有办法测试。整个架构的可行性很难估算。<BR><BR>往后考虑。一次大规模的重构可能是少不了的。虽然agile过程本身提倡以TDD为基础的重构。但是现在的重构可能造成的代价已经不是一次轻量级的增量迭代了。<BR><BR>说到这里，总结几点，希望能在以后的工作中引起注意：<BR>1. Agile很难管理，项目早期应该对各种风险有尽可能全面的评估，schedule的设置中应该定义好 test case 和 build 的时间点。<BR>2. 设计不必太详细，用频繁的测试和重构完善设计。<BR>3. Test case 优先设计，这样在架构中就会对testability有足够多的考虑。<BR>4. 团队内部对共同的难题应该及早进行讨论和解决，问题的解决方案应该传递到每个组员，尽可能保证团队的能力同步。</P><img src ="http://www.blogjava.net/ghawk/aggbug/23772.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-14 09:57 <a href="http://www.blogjava.net/ghawk/archive/2005/12/14/23772.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>