当前位置:主页 > 95992222九五至尊二正文

95992222九五至尊二:一些面向对象的设计法则

05月07日作者:黑曼巴


轨则1:优先应用(工具)组合,而非(类)承袭

[ Favor Composition Over Inheritance ]

组合

(工具)组合是一种经由过程创建一个组合了其它工具的工具,从而得到新功能的复用措施。

将功能委托给所组合的一个工具,从而得到新功能。

有些时刻也称之为“聚合”(aggregation)或“包涵”(containment),只管有些作者对这些术语付与了专门的含义

例如:

聚合:一个工具拥有另一个工具或对另一个工具认真(即一个工具包孕另一个工具或是另一个工具的一部分),并且聚合工具和其所有者具有相同的生命周期。(译者注:即所谓的“同生共逝世”关系,可拜见GOF的Design Patterns: Elements of Reusable Object-Oriented Software的小序部分。)

包涵:一种特殊类型的组合,对付其它工具而言,容器中的被包孕工具是弗成见的,其它工具仅能经由过程容器工具来造访被包孕工具。(Coad)

包孕可以经由过程以下两种要领实现:

根据引用(By reference)

根据值(By value)

C++容许根据值或引用来实现包孕。

然则在Java中,统统皆为工具的引用!

组合的优点和毛病

优点:

容器类仅能经由过程被包孕工具的接口来对其进行造访。

“黑盒”复用,由于被包孕工具的内部细节对外是弗成见。

对装性好。

实现上的互相依附性对照小。(译者注:被包孕工具与容器工具之间的依附关系对照少)

每一个类只专注于一项义务95992222九五至尊二。

经由过程获取指向其它的具有相同类型的工具引用,可以在运行时代动态地定义(工具的)组合。

毛病:

从而导致系统中的工具过多。

为了能将多个不合的工具作为组合块(composition block)来应用,必须仔细地对接口进行定义。

承袭

(类)承袭是一种经由过程扩展一个已有工具的实现,从而得到新功能的复用措施。

泛化类(超类)可以显式地捕获那些公共的属性和措施。

特殊类(子类)则经由过程附加属性和措施来进行实现的扩展。

承袭的优点和毛病

优点:

轻易进行新的实现,由于其大年夜多半可承袭而来。

易于改动或扩展那些被复用的实现。

毛病:

破坏了封装性,由于这会将父类的实现细节裸露给子类。

“白盒”复用,由于父类的内部细节对付子类而言平日是可见的。

当父类的实现变动时,子类也不得不会随之变动。

从父类承袭来的实现将不能95992222九五至尊二在运行时代进行改变。

Coad规则

仅当下列的所有标准被满意时,方可应用承袭:

子类表达了“是一个…的特殊类型”,而非“是一个由…所扮演的角色”。

子类的一个实例永世不必要转化(transmute)为其它类的一个工具。

子类是对其父类的职责(responsibility)进行扩展,而非重写或废除(nullify)。

子类没有对那些仅作为一个对象类(utility class)的功能进行扩展。

对付一个位于实际的问题域(Problem Domain)的类而言,其子类特指一种角色(role),买卖营业(transaction)或设备(device)。

承袭/组合示例1

“是一个…的特殊类型”,而非“是一个由…所扮演的角色”

掉败。游客是人所扮演的一种角色。代理人亦然。

永世不必要转化

掉败。跟着光阴的成长,一个Person的子类实例可能会从Passenger改变成Agent,再到Agent Passenger。

扩展,而非重写和废除

经由过程。

不要扩展一个对象类

经由过程95992222九五至尊二。

在问题域内,特指一种角色,买卖营业或设备

掉败。Person不是一种角色,买卖营业或设备。

承袭并非适用于此处!

应用组合进行挽救!

承袭/组合示例2

“是一个…的特殊类型”,而非“是一个由…所扮演的角色” 经由过程。乘

客和代理人都是特殊类型的人所扮演的角色。

n 永世不必要转化

经由过程。一个Passenger工具将维持不变;Agent工具亦然。

n 扩展,而非重写和废除

F 经由过程。

n 不要扩展一个对象类

F 经由过程。

n 在问题域内,特指一种角色,买卖营业或设备

F 经由过程。PersonRole是一种类型的角色。

承袭适用于此处!

承袭/组合示例3

n “是一个…的特殊类型”,而非“是一个由…所扮演的角色”

F 经由过程。预订和购买都是一种特殊类型的买卖营业。

n 永世不必要转化

F 经由过程。一个Reservation工具将维持不变;Purchase工具亦然。

n 扩展,而非重写和废除

F 经由过程。

n 不要扩展一个对象类

F 经由过程。

n 在问题域内,特指一种角色,买卖营业或设备

F 经由过程。是一种买卖营业。

承袭适用于此处!

承袭/组合示例4

n “是一个…的特殊类型”,而非“是一个由…所扮演的角色”

F 掉败。预订不是一种特殊类型的observable。

n 永世不必要转化

F 经由过程。一个Reservation工具将维持不变。

n 扩展,而非重写和废除

F 经由过程。

n 不要扩展一个对象类

F 掉败。Observable便是一个对象类。

n 在问题域内,特指一种角色,买卖营业或设备

F 不适用。Observable是一个对象类,并非一个问题域的类。。

承袭并非适用于此处!

承袭/组合总结

n 组合与承袭都是紧张的重用措施

n 在OO开拓的早期,承袭被过度地应用

n 跟着光阴的成长,我们发明优先应用组合可以得到重用性与简单性更佳的设计

n 当然可以经由过程承袭,以扩充(enlarge)可用的组合类集(the set of composable classes)。

n 是以组合与承袭可以一路事情

n 然则我们的基础轨则是:

优先应用工具组合,而非(类)承袭

[ Favor Composition Over Inheritance ]

轨则2:针对接口编程,而非(接口的)实现

[ Program To An Interface, Not An Implementation ]

接口

n 接口是一个工具在对其它的工具进行调用时所知道的措施聚拢。

n 一个工具可以有多个接口(实际上,接口是工具所有措施的一个子集)

n 类型是工具的一个特定的接口。

n 不合的工具可以具有相同的类型,而且一个工具可以具有多个不合的类型。

n 一个工具仅能经由过程其接口才会被其它工具所懂得。

n 某种意义上,接口因此一种异常局限的要领,将“是一种…”表达为“一种支持该接口的…”。

n 接口是实现插件化(pluggability)的关键

实现承袭和接口承袭

n 实现承袭(类承袭):一个工具的实现是根据另一个工具的实现来定义的。

n 接口承袭(子类型化):描述了一个工具可在什么时刻被用来替代另一个工具。

n C++的承袭机制既指类承袭,又指接口承袭。

n C++经由过程承袭纯虚类来实现接口承袭。

n Java对接口承袭具有零丁的说话构造要领-Java接口。

n Java接口构造要领加倍易于表达和实现那些专注于工具接口的设计。

接口的好处

n 优点:

F Client不必知道其应用工具的详细所属类。

F 一个工具可以很轻易地被(实现了相同接口的)的另一个工具所调换。

F 工具间的连接不必硬绑定(hardwire)到一个详细类的工具上,是以增添了机动性。

F 疏松藕合(loosens coupling)。

F 增添了重用的可能性。

F 前进了(工具)组合的机率,由于被包孕工具可所以任何实现了一个指定接口的类。

n 毛病:

F 设计的繁杂性略有增添

(译者注:接口表示“…像…”(LikeA)的关系,承袭表示“…是…”(IsA)的关系,组合表示“…有…”(HasA)的关系。)

接口实例

n 该措施是指其它的一些类可以进行交通对象的驾驶,而不必关心其实际上是(汽车,轮船,潜艇或是其它任何实现了IManeuverabre的工具)。

轨则3:开放-封闭轨则(OCP)

软件组成实体应该是可扩展的,然则弗成改动的。

[ Software Entities Should Be Open For Extension, Yet Closed For Modification ]

开放-封闭轨则

n 开放-封闭轨则觉得我们应该试图去设计出永世也不必要改变的模块。

n 我们可以添加新代码来扩展系统的行径。我们不能对已有的代码进行改动。

n 相符OCP的模块需满意两个标准:

F 可扩展,即“对扩展是开放的”(Open For Extension)-模块的行径可以被扩展,以必要满意新的需求。

F 弗成变动,即“对变动是封闭的”(Closed for Modification)-模块的源代码是不容许进行篡改的。

n 我们能若何去做呢?

F 抽象(Abstraction)

F 多态(Polymorphism)

F 承袭(Inheritance)

F 接口(Interface)

n 一个软件系统的所有模块弗成能都满意OCP,然则我们应该努力最小化这些不满意OCP的模块数量。

n 开放-封闭轨则是OO设计的真正核心。

n 相符该轨则便意味着最高等级的复用性(reusability)和可掩护性(maintainability)。

OCP示例

n 斟酌下面某类的措施:

n 以上函数的事情是在制订的部件数组中谋略各个部件价格的总和。

n 若Part是一个基类或接口且应用了多态,则该类可很轻易地来适应新类型的部件,而不必对其进行改动。

n 其将相符OCP

n 然则在谋略总价格时,若财务部颁布主板和内存应应用额外用度,则将若何去做。

n 下列的代码是若何来做的呢?

n 这相符OCP吗?

n 当每次财务部提出新的计价策略,我们都不得不要改动totalPrice()措施!这并非“对变动是封闭的”。显然,策略的变化便意味着我们不得不要在一些地方改动代码的,是以我们该若何去做呢?

n 为了应用我们第一个版本的totalPrice(),我们可以将计价策略合并到Part的getPrice()措施中。

n 这里是Part和ConcretePart类的示例:

n 然则现在每当计价策略发生改变,我们就必须改动Part的每个子类!

n 一个更好的思路是采纳一个PricePolicy类,经由过程对其进行承袭以供给不合的计价策略:

n 看起来我们所做的便是将问题推迟到另一个类中。然则应用该办理规划,我们可经由过程改变Part工具,在运行时代动态地来设定计价的策略。

n 另一个办理规划是使每个ConcretePart从数据库或属性文件中获取其当前的价格。

单选轨则

单选轨则(the Single Choice Principle)是OCP的一个推论。

单选轨则:

无论在什么时刻,一个软件系统必须支持一组备选项,抱负环境下95992222九五至尊二,在系统中只能有一个类能够知道全部的备选项聚拢。

轨则4:Liskov调换轨则(LSP)

应用指向基类(超类)的引用的函数,必须能够在不知道详细派生类(子类)工具类型的环境下应用它们。

[ Function Thar Use Referennces To Base(Super) Classes Must Be Able To Use Objects Of Derived(Sub) Classes Without Knowing It ]

Liskov调换轨则

n 显而易见,Liskov调换轨则(LSP)是根据我所熟知的“多态”而得出的。

n 例如:

n 措施drawShape应该可与Sharp超类的任何子类一路事情(或者,若Sharp为Java接口,则该措施可与任何实现了Sharp接口的类一路事情)

n 然则当我们在实现子类时必须要审慎对待,以确保我们不会无意中违抗了LSP。

n 若一个函数未能满意LSP,那么可能是由于它显式地引用了超类的一些或所有子类。这样的函数也违抗了OCP,由于当我们创建一个新的子类时,会不得不进行代码的改动。

LSP示例

n 斟酌下面Rectangle类:

n 现在,Square类会若何呢?显然,一个正方形是一个四边形,是以Square类应该从Rectangle类派生而来,对否?让我们看一看!

n 察看可得:

F 正方形不必要将高和宽都作为属性,然则总之它将承袭自Rectangle。是以,每一个Square工具会挥霍一点内存,但这并不是一个主要问题。

F 承袭而来的setWidth()和setHeight()措施对付Square而言并非真正地得当,由于一个正方形的高和宽是相同。是以我们将必要重写setWidth()和setHeight()措施。不得不重写这些简单的措施有可能是一种不恰当的承袭应用要领。

n Square类如下:

n 看起来都还不错。然则让我们查验一下!

n 测试法度榜样输出:

n 看上去似乎我们违抗了LSP!

n 这里的问题出在哪里呢?编写testLsp()措施的法度榜样员做了一个合理的假设,即改变Rectangle的宽而维持它的高不变。

n 在将一个Square工具通报给这样一个措施时孕育发生了问题,显然是违抗了LSP

n Square和Rectangle类是互雷同等和合法的。只管法度榜样员对基类作了合理的假设,但其所编写的措施仍旧会导致设计模型的掉败。

n 不能孤登时去看待办理规划,必须根据设计用户所做的合理假设来看待它们。

n 一个数学意义上的正方形可能是一个四边形,然则一个Square工具不是一个Rectangle工具,由于一个Square工具的行径与一个Rectangle工具的行径是不同等的!

n 从行径上来说,一个Square不是一个Rectangle!一个Square工具与一个Rectangle工具之间不具有多态的特性。

总结

n Liskov调换轨则(LSP)清楚地注解了ISA关系整个都是与行径有关的。

n 为了维持LSP(并与开放-封闭95992222九五至尊二轨则一路),所有子类必须相符应用基类的client所期望的行径。

n 一个子类型不得具有比基类型(base type)更多的限定,可能这对付基类型来说是合法的,然则可能会由于违抗子类型的此中一个额外限定,从而违抗了LSP!

n LSP包管一个子类老是能够被用在其基类可以呈现的地方!

最近关注

热点内容

更多