数据元素,而方法( method,指属于这个对象的函数。它接受你传给这 个对象的消息)则列在方框的底部。通常UML的设计图只会显示类的名字 以及 public方法,所以中间部分是不显示的。如果你只对类的名字感兴 趣,那么最下面的那部分也可以不显示。 对象会提供服务 当你开发一个程序或者分析一个程序的设计时,理解对象的最佳的方式是 把它们当作“服务的提供者”。程序本身会为用户提供服务,而它通过使 用其它对象所提供的服务来完成这个工作。你的任务是制作(或者在更理 想的情况下,从现有的代码厍中找出)一组能为解决问题提供最佳服务的 对象 这么做的第一步是问“如果我可以像变魔术那样把东西从帽子里拿出来, 我该拿出些什么东西,哪些对象能立即帮我解决问题?”举例来说,假设 你要创建一个簿记程序。可能你会想应该有一些保存预设的输入界面的对 象,一组进行簿记计算的对象,以及一个能在各种打印机上打印支票和发 票的对象。有些对象或许已经有了,但是那些还没有的应该是什么样的 呢?它们应该提供哪种服务,还有它们要完成任务的话,又该用哪些对象 呢?如果你不断分析下去,最终你会发现,不是“那个对象写起来很容 易”就是“那个对象已经有了。”这是将问题分解成一组对象的一个合理 的方法 将对象视作服务的提供者还有一个额外的优点:能提高对象的内聚性 ( cohesion)。内聚性高是高质量的软件设计一个基本要求:就是说软件 的各种组件(比如对象,也可以是方法或类库)应该能很好的“组装在 起”。设计对象时常犯的一个错误就是,往对象里塞了太多的功能。举例 来说,设计支票打印模块的时候,你也许会决定设计一个能通晓所有排格 式和打印工作细节的对象。很快你就会发现这个任务太艰巨了,或许应该 用三个或是更多对象来完成这个工作。第一个对象应该是支票格式的目录 册,通过査询这个目录册可以获得该如何打印支票的信息。第二个对象, 或是一组对象,应该是能分辨各种打印机的通用的打印接口(但是对簿 记,它一窍不通——这个模块应该买来而不是自己写)。以及使用上述两 个对象所提供的服务的,能最终完成任务的第三个对象。由此每个对象都 提供一组互补的功能。在一个良好的面向对象的设计中,每个对象都应该 只作一件事,并且作好一件事,而不是去作太多的事情。就像这里看到 的,这样不仅能发现哪些对象应该买(打印机接口对象),而且能设计出今 后能复用的对象(支票格式的目录册) 将对象视作服务的提供者还是一种很了不起的简化工具。它不仅在设计过 程中有用,而且还能帮助别人理解你的代码或者复用这个对象——如果他 们认同这个对象所提供的服务的话。将对象视作服务的提供者能使对象更 容易地被用于设计。 第6页共34页
shhgs@wgqqh.com 6 ✁ ✂ 34 ✁ 6 ,ÛÜàµ(method)) ×¥V(d,A$%¡¬ ×¥V()0¾Fµb(h· U M L ([1Ëóº×/(ÑK EP p u b l i c µDE*4X×(ÄÅ¡ó¥/(ÑKef gYZbƶ(Y[ÝEX× ¡ì3×âh|3×âh([1¢£¥V(bh(µ´ PA 1Í jþ(Ïâh pº0jþàA^ç §A¥VD(jþQ ×y1¡(þn1(|F¢ |([Æím(pi*j! )3-;£Nbhjþ( ¥V ZT(3Í ÄÅÝE}ák±Y¦PlË@!Q w@!ÌZmÌ¥V; no£NÊÏBâQ$Z[ ¡%3×pqâhÝ;¡º|vwm3Ìù?[(rt»¶(¥ V3-tEpq12(¥VEP3×;FÚ]s3J]stuì u(¥VmÌ¥V|ôõm,YÌm(vwZ¦( QÊAvwmjþmA%þ(üwmÌ¥V QÊÄÅ¡XvÆJb:¡ºìíXÍ Y×¥V«9QR SÏÍ Y×¥Vôõm,Ï £3-¥V(3×¢ (µ ¥V·1jþ(m3×;½(wC7;©¥V(x (coh esi on )x©©(yÅ[13×Y %7$yÅ (Ú-Å(8Ä¥V[ÝEµ|/i)vw;(Í -zF3 9Ï[1¥V·{(3×Ó|Þ¥VË},ÉV(ô;Bâ Q$[1tu]s~(M¡[ºNO[13×;Dmè ´]sy1uv(¥Vs¡ºìí ×þÉ,|vw L×|V¥VQ ×y13×¥Vvwtuè´( ^H ×ÝEówÄ]stu(×¥V |3-¥Vvw;Ú]s3((]s$X (¥p qA3Xyy ×~vw5QàXýþ« )EPçJe ×¥VD(jþ(;b:þ(L×¥Vçòê×¥VÆ 3-e(ô;F3×(¶U¥V([1*ê×¥VÆvw ó13Åk13ÅàXJ1ÉV([} Ë<g ( ¦XÇ;ìímÌ¥Vvw5 (]s3$X¥V)àk;[1!d ;(¥V(tuè´() ¥V·1jþ(3,X9( NyAAXÇF[1^ â*màk;o¼ª¢£¡(p| ×¥VyyÄÅG Ù ×¥VD(jþ(¥V·1jþ(;ç¥V RSr=)[1
隐藏实现 将程序员分成类的创建者(c| ass creator那些创建新的数据类型的人)和 客户程序员( client programmer那些使用这些类编程的人)能帮助我 们更好地理解这个问题。客户程序员的目的是收集各种类,以便能快速开 发应用程序。而类的创建者的目的是创建一些这样的类,它们只透露一些 客户程序员必须知道的东西,其它部分则被完全隐藏了。为什么?因为隐 藏之后,客户程序员就不能访问了,也就是说类的创建者们可以根据需要 修改隐藏部分而不用担心会对其他人造成影响。通常隐藏起来的都是这个 类最脆弱的,很容易被粗心大意或不知情的客户程序员给弄坏的内脏部 分,所以把实现隐藏起来能减少程序的bug 隐藏实现,这一概念的重要性无论如何强调都不会过分。无论是那种关 系,有一个为各方都尊重的边界是非常重要的。创建一个类库之后,你就 与客户程序员建立了某种关系。他们也是程序员,他们用你的类库来组装 一个应用程序,也可能是一个更大的类库。如果所有人都能看到类的所有 成员,那么客户程序员就能对这个类作任何事,因此也谈不上什么强制性 的规则了。尽管你真的不想让客户程序员直接操控类中的某些成员,但是 如果没有访问控制的话,你也没办法禁止他去这么做。这个类只能赤裸裸 地面对整个世界。 所以要控制访问权限的首要原因就是,禁止那些客户程序员去碰他们不该 动的东西——就是那些数据类型内部运作所必须的东西。只允许他们接触 解决问题所必需的接口。实际上这也是提供给用户的一种服务,因为由此 他们能很方便的看出哪些东西对他们很重要,哪些跟他们没关系 第二个原因是允许类库设计人员能在不打搅客户程序员的情况下修改类的 内部工作方式。比如,刚开始设计这个类的时候时,为了降低开发难度, 你用了一种很简单的方法,但随后你发觉应该重写这段代码,让跑得快 些。如果这个类的接口与实现分得很清楚,并且保护得很好,那么做起来 就会很方便。 Java用了三个明确的关键词来设置类中的边界: public, private,和 protected。它们的用法和意思都相当明了。这些访问控制符表示谁能使 用由它定义的东西。 public表示后面跟着的东西谁都能用。而 private 关键词则表示,除非是类的创建者用这个类的方法进行访问,否则没人能 访问到这个元素。 private是竖在你与客户程序员之间的一堵墙。那些要 访问 private成员的人,会在编译的时候得到一个错误。 protected关键 词很像 private,它们的区别在于,继承类能够访问 protected成员,但 是不能访问 private成员。继承问题过一会再介绍 Java还有个“缺省”的访问权限,如果你没用上面三个的话,那就是指 它了。通常把它称为 package访问权限。因为同属这个 package的类 第7页共34页
shhgs@wgqqh.com 7 ✁ ✂ 34 ✁ 7 âh/((class creator YÌR(,/(ª) /0âh ✄ ✟ ✆ (client programmer YÌç Ì/áâ(ª);o r¢£ ×/0âh((4Ú/E=;s ìvâhà/(((3Ì ¦(/Aó3Ì /0âho(§A0=c ,ZÊæ u/0âhX;,[$/(ÝEî% ïð àXÀqº¥§GªÚ · 9Q(Æ × /b(RS=q¹|X[(/0âh¬( DEPñí 9Q;âh( b u g ñí 3Ö(%ÜûÄæÆXº^ÜûY wm3×ÚµÆ(»¯·%(3×/iu¡ /0âh ,´wG[âhG¡(/iQ-z 3×vâh[Ý;3×Â(/iÄÅDmªÆ;<g/(Dm YZ/0âh;¥ ×/1æò[XJZæn (&0,rF¡<(X|Ç/0âhÒ$ÉÊ/*(´Ì ÄÅmÊn(¡[R+GJ ZT ×/ó; r¶¥¤×º» DE%ÊnÈ(~%åæ+YÌ/0âhJGXw (yyYÌ,/X1Do(óèG$ £NDoî($XñJ [¬0(3jþæçò G;µ=(<!mÌ¥G%mÌ¡Gw ×åæè/i[1ª;FX]¢/0âh([Æïð/( y1µ´8Ä£[1 ×/(M,¤¥ìUz ¡,3 (µ¸¡ìvw« ¦pǧs3 ÌÄÅ ×/($Xñílmk§YZT9Q ºµ= Ja va ,L×Ï((³Q[6/*(»7p u b l i cp ri va te p rotected A(¹DÆ> Ï, ÌÊnÌG¨;ç çAO(p u b l i c G¶¡.(¨Æ;à p ri va te (³0G3¯/( ×/(µtEL0ª; g ×ÛÜp ri va te ©F¡/0âhu4(3ª«YÌ% p ri va te (ªºFá(Mg3×Ó|p rotected ( ³} p ri va teA( ¼F)öK/;_ p rotected X; p ri va te öK^3ºÁ Ja va m×Í ¬Ï(ÈÄÅ¡J¶L×(Y A,·PAÝ p a cka g e Èæ) × p a cka g e (/
可以访问这个 package中其它类的“缺省权限”的成员,但是出了这个 package,它们就都是 private的了。 复用实现 类创建完成并且测试通过之后,它就应该能(很好地)表示一段有用的 代码。但实际上代码复用并不像我们希望的那样容易;要设计一个复用性 良好的对象,需要经验和远见。代码复用是OOP语言最显著的优点之 复用代码最简单的方式是直接用这个类来创建对象,但是你也可以将那个 类的对象放到一个新的类中。我们把它称为“创建一个成员对象”。为了 能让那个新的类提供你所设计的功能,它可以由任意多个,任意类型的对 象,以任意形式组合在一起。由于你是用已有的类来合成新的类,因此这 一概念被称为合成( composition,如果这个对象是动态合成的,通常把 它称为聚合 aggregation)。通常将合成称为“有(has-a)”关系,就像 轿车有引擎 C Engine (这个讲解轿车的UML关系图,用实心菱形表示合成关系。通常如果要 让我表示这个关系的话会更简单:就是一根直线,没有菱形。l) 合成具有极大的灵活性。新类里面的成员对象通常都是 private的,因此 使用这些类的客户程序员们是无法访问这些对象的。这点能让你在不影响 已有的客户代码的前提下,修改这些类。你还可以在运行时修改这些成员 对象,并以此动态地改变程序地运行。下面要讲的继承就没有这种灵活 性,如果你用继承创建一个类,编译器肯定会加上某些编译时的限制的 由于继承在OoP中是如此重要,因此会被反复强调,以至于新手们会认 为,应该尽量使用继承。其实一味的使用继承会导致很奇怪也很复杂的设 计。相反,在创建新类的时候,你应该优先考虑使用合成,这么做会更简 单也更灵活。如果这么做的话,整个设计也会变得更加井井有条。等你有 了一点经验之后,你就会自然而然的知道继承应该用在哪里了 继承:复用接口 对象这个概念本身就是一件很有力的工具。它能让你依照概念把数据与功 能结合在一起,这样你就不用站在运行程序的计算机的角度上,而是站在 解决问题的角度上来写代码了。这个概念作为程序的基本单位,在编程语 言中用 class这个关键词表示。 第8页共34页
shhgs@wgqqh.com 8 ✁ ✂ 34 ✁ 8 ÝE × p a cka g e *§A/(Í ¬ÈÏ(!, × p a cka g eAÆ p ri va te (, 3Ô/k®¯^uAvw; (r)G3¦m( pñJpX}7(Y¦RS %[13× (¥Vî%õ\° p OOP b×Ð(wCu 3 pb (µ´Ò$ ×/Q¥V¡[ÝEY× /(¥Vkg3×R(/*PAÝÍ 3×¥VÏ, ;ÇY×R(/¡D[1(ô;AÝEç¹V×¹/(¥ VE¹´-F39ç)¡ôm(/QR(/æò 3Ö=Ý(compositionÄÅ ×¥V ì(·P AÝx aggregation)·ÝÍ m(h a s-a )Ïw} Í ±Fm+²Ï ( ש£±F( U M L wËñq³Gw·ÄÅ% ÇG ×w(º 73Ò´m³ ✄ ✠ ✆ ) AmµÂ(äåR/˶(¥V·Æ p ri va te (æò ç Ì/(/0âhÜ Ì¥V( C;Ç¡FX ôm(/0p(Æïð Ì/¡ÝEFXEïð Ì ¥VEò ìrðáâhrXEƶ%©(öKm äå ÄÅ¡öK3×/á7¶OºäJ´Ìá(Èn( ç)öKF OOP *Äò%æòº=>æE)R·ºÙ vwrçöK§ñ3¸(çöKº¹º»¼[([ 1>>FR/(M¡vww/½ç ZTº [äåÄÅ ZT(¤×[1[ºáä¾¾mÄ-¡m ,3Cõ\u¡ºý;à;(öKvwFmË, ¥V ×Ö p3Åmã(yAA;Ç¡õÖP,ô ;IF39 ¦¡XIFXEâh(123(zJàIF £N(zJQ«p, ×Ö1âh(Y ¿Fáâ * class ×(³G
然而,要是我们费尽心机编写了一个类之后,又不得不再写去一个功能类 似的全新的类的话,那就很惨了。要是能将已有的类克隆一份,然后在克 隆的类上修修补补,那就好了。而这正是继承( inheritance)的功效。只 是区别在于,如果原先那个类(称为基类 base class,父类 parent cass和超类 superclass)作过了修改,那么这个“克隆”(称为衍生 类,派生类 derived class或者继承类 in herited class或者子类 subclass, child class)也会随之发生变化。 Base Derived (在UML关系图中,箭头从派生类指向基类。你会发现,通常会有不止 个派生类。) 类不仅仅在描述一组施加在对象之上的限制;它还会同其它类发生关系。 两个类可以有一些共通的特征和行为,可能其中有一个比另一个特征更 多,能处理的消息也更多(或者处理的方式不同)。通过基类和派生类的概 念,继承表达了这种相似性。基类保存的是所有继承自它的类的共有特征 和行为。创建基类是为了表达,你对系统中某些对象的核心思想的理解 而衍生类则是从基类派生出来的,它所表达的是各种实现这个核心思想的 方式。 举例来说,有一个对垃圾分类的垃圾回收机。基类是“垃圾( trash)”, 每件垃圾都有重量,价值等等,而且可以切碎,融化或者分解。由它派生 出的一些具体的垃圾品种会有一些额外的特征(瓶子有颜色)或是行为( 罐可以压缩,铁罐可以磁化)。此外有些行为会不同(纸张的价值由它的品 种和状况决定)。通过继承,你能用类构建一个表述你要解决的这个问题 的类系( type hierarchy)。 第二个例子是经典的“ shape”案例。或许它能用于计算机辅助设计系统 或是用来模拟游戏场景。基类是“ shape”,每种形状都有大小,颜色, 位置等属性。从它那里继承下来的具体的形状——圆,矩形,三角形等- —都有额外的特征和行为。比如某些形状可以翻转。有些行为会不同,比 如计算面积的方法。整个类系体现了各种形状之间的相同和不同点。 第9页共34页
shhgs@wgqqh.com 9 ✁ ✂ 34 ✁ 9 ;à%Àrq3á«,3×/uüXXÁ«J3×ô;/ Á(cR(/(YÂ,%;ôm(/ÃÄ3ú;Fà Ä(/JïïeeY,à (öK(inheritance)(ôØó ¼F)ÄÅå/Y×/(ÝY/ base classÅ/ parent class {/ superclass)1^,ïðYZ ×Í ÃÄÏ(ÝÆ0 /Ç0/ derived class |öK/ inherited class |/ subclass, child class)[º¸uì0áN (F U M L wË*ÈÉÇ0/UY/¡ºìí·ºmX+ 3×Ç0/) /XÇÇFé3-ÊäF¥VuJ(Èn Aº§A/ì0w e×/ÝEm3Ì:(µòEÝ;§*m3×8®3×µò V;¢¢([V (|¢¢(µ´X)^Y/Ç0/(Ö öKGH, >ÁY/ù(DmöKýA(/(:mµò EY/,GH¡¥w5*´Ì¥V(ËqD|(¢£ àÆ0/0Y/Ç0!Q(ADGH(Úñí ×ËqD|( µ´ BâQ$m3×¥ÌÍ/(ÌÍ~3Y/Í ÌÍ(tra sh )Ï êÅÌÍÆm¨Î--àkÝEÏÐN|£çAÇ0 !(3ÌAT(ÌÍ2ºm3Ì;½(µò(ÑmÒÓ)|E(Ô ÕÝEWÖÕÝE×N)ò½mÌEºX(ØÙ(¨ÎçA(2 ëNO )^öK¡;/Ú3×G¡%£N( × (/w (typ e h i era rch y) ×âõ,(Í sh a p eÏOâ|A;)123Û[1w5 |QîÜÝJdY/Í sh a p eÏêëÆmÂÞÒÓ ¿6-)AYËöKÆQ(AT(ëyyßàL-y yÆm;½(µòE8Ä´ÌëÝE¿mÌEºX8 Ä12¶á(µ¤×/wTí,Úëu4(>XC
Shape draw erase() moveD getcoloro setcol oro Circle Square Triangle 将描述问题之用的术语用于解决问题也是大有裨益的,因为这样你就不再 需要那些在讲述问题和解决方案之间的中间模型了。对于对象,类层次结 构是最主要的模型,由此你从用文字描述真实世界中的系统进到了用代码 描述这个系统。实际上,大家在用OOP设计时会遇到的难题之一是,它 从头到尾太简单了。习惯于用复杂方法解决问题的人可能会一开始就被这 种简单给难住了。 当你继承了一个类时,你也创建了一个新的类。这个新的类不仅包含了已 有类的所有成员(尽管 private成员已经隐藏起来了,是不能访问的), 更重要的是它复制了基类的接口。于是所有能够传给基类的消息也都可以 传给派生类。由于我们根据能它能接受什么消息来判断这是什么类,因此 这就意味着派生类“和基类是属于同一类型的”。在上面的例子里,“圆 也是一种形状”。这种由继承而产生的类的相等性是一扇能让你理解 OoP意义的门。 既然基类和派生类具有相同的基本接口,那么这个接口的背后就必须跟着 实现。也就是当对象收到某个消息的时候,它必须能执行一些代码。如果 你只是继承了一个类,其他什么都不做,那么基类里的方法会直接带进派 生类。也就是说派生类的对象不但与基类的对象的类型相同而且行为也 样。这可没什么吸引力 你有两种办法来区分新的派生出来的类和那个原来的基类。第一种方法很 简单:直接往派生类里加新的方法。这些新的方法不属于基类的接口。也 就是说由于基类不能完成所有的工作,你必须加上更多方法。这种简单原 始的继承方法常常是解决问题的完美方案。然而你还得仔细看看基类是不 是也需要这些方法。对于OoP的设计,这种反复发现的过程是很常见 的 第10页共34页
shhgs@wgqqh.com 10 ✁ ✂ 34 ✁ 10 éu(±)£N[Âmâã(æ ¦¡XÁ î%YÌF©£NµOu4(*4,¥)¥V/ä¨I Úbå%(çò¡ Ké<ñº»*(w5tg,p é ×w5ñJÂæF OOP [1ºIg(Uu3A ÉgçÉ ,bè)µ£N(ªÝ;º3= ¬Ué, ¡öK,3×/¡[,3×R(/ ×R(/XÇ!",ô m/(Dm (rF pri vate ôõ 9Q,X;() %(An,Y/($X)Dm;_¬Y/([ÆÝE ¬Ç0/ç);A;$%ZQêv Z/æò ¹¸.Ç0/Í Y/))3/(ÏFJ¶(âËÍ ß [3ëÏ çöKà¬0(/(>-3ë;Ç¡¢£ OOP ¹(ì í;Y/Ç0/Am>(Y $XYZ ×$X(co¡. ñí[ ¥Vg´×(MAo;íE3ÌpÄÅ ¡óöK,3×/§GZÆXTYZY/Ë(µºÒ$¥tÇ 0/[$Ç0/(¥VXY/(¥V(/>àkE[3 ¦ ÝZî+ã ¡meRQ R(Ç0!Q(/Y×åQ(Y/3µ 7Ò$ÞÇ0/ËäR(µ ÌR(µX))Y/($X[ $ç)Y/X;Dm(y1¡oäJVµ å (öKµ··£N(ïµO;à¡ðu<<Y/X [î% ̵¥) OOP ([1 >ìí(^â· (