




已阅读5页,还剩9页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
面向对像设计原则一. 类设计五原则1. 单一职责原则(Single Responsibility Principle SRP)SRP中,把职责定义为变化的原因。如果你能想到多个动机去改变一个类,那么这个类就具有多于一个的职责。这里说的变化的原因,只有实际发生时才有意义。可能预测到会有多个原因引起这个类的变化,但这仅仅是预测,并没有真的发生,这个类仍可以认为是具有单一职责,不需要分离职责。SRP就是告诉我们,一个类,只有一个引起它变化的原因,只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。 在面向对像开发过程中,遵守SRP的好处是显而易见的。它可以让我们的代码更简单,更安全,更易于扩展。许多设计模式的运用可以帮我们遵守SRP。例如:proxy, strategy, chain of responsibility等。以proxy为例:有一个DBManager,在进行数据库操作时,有形如这样的接口, DoAction()。做了如下实现:DoAction ()If( CheckPermit(m_id) )DoAction();先进行一个ID验证,然后如果可以做操作则DoAction。在实现数据库操作的过程中,又考虑权限验证的问题,这显然是逻辑交错。会出现什么问题?如果现在需要对不同类型的用户进行不同类型的权限验证,比如外网用户用QQ号和密码验证,内网用户查询不需要验证,修改用员工号和动态密钥验证。这样的扩展对于上述代码来说实现的代价就是破坏原有代码结构,同时新功能使逻辑进一步交错,更加混乱。如何解决?采用proxy模式解决此问题。实现CheckPermitProxy,并由它负责权限验证,并持有DBManager指针.然后以如下代码进行操作:CheckPermitProxy(DBOPManger().DoAction();这样,如果我们数据库对于内网用户,是一套验证机制,对于外网用户是另一套验证机制,则只需要实现两个不同的proxy, CheckPermitProxyInner, CheckPermitProxyOuter,使用者的代码也十分简单:CheckPermitProxyInner(DBOPManger().DoAction();CheckPermitProxyOuter(DBOPManger().DoAction();这样的设计,另一个好处就是代码字面上完全自解释。一目了然。2. 开闭原则(the Open Closed Principle OCP)软件系统中包含的各种组件,例如模块(Modules)、类(Classes)以及功能(Functions)等等,应该在不修改现有代码的基础上,引入新功能。开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。遵守开闭原则,我们应该尽量使用面向对像技术,例如继承,多态,接口等。当然,开闭原则同样适用于非面向对象的项目。有一段关于画几何图形的代码:struct SquareShapeType itsType;double itsSide;Point itsTopLeft;/ These functions are implemented elsewhere/void DrawSquare(struct Square*)void DrawCircle(struct Circle*);typedef struct Shape *ShapePointer;void DrawAllShapes(ShapePointer list, int n)int i;for (i=0; iitsType)case square:DrawSquare(struct Square*)s);break;case circle:DrawCircle(struct Circle*)s);break; 当我们要在DrawAllShapes里增加三角形时,就得改变该函数的代码。不符合OCP。当然,此类情况只需要简单运用多态,就可以使DrawAllShapes函数的代码稳定。只有真正稳定下来的代码才可以保证其安全性。class Shapepublic:virtual void Draw() const = 0;class Square : public Shapepublic:virtual void Draw() const;class Circle : public Shapepublic:virtual void Draw() const;void DrawAllShapes(Set& list)for (Iteratori(list); i; i+)(*i)-Draw();OCP在软件开发过程中直接影响产品质量。假设公司A开发了应用库windows.a,这个应用库随着版本的更新,功能越来越多。现有公司B,用了这个库的1.0版本,之后公司A开发了1.1版本,增加了一些功能。如果公司A没有遵守开闭原则,新加的功能不是扩展而是通过改变原来的代码实现的,比如改变了某个运用的调用过程,这会导致使用者一旦更新了新版本的应用库则也需要更改相应的调用代码或是原有可用功能出现了问题。这影响了A公司产品的用户体验。显然不是公司A想要看见的。开闭原则在软件开发中另一个重要的价值就是使代码稳定,BUG收敛。任何新功能的开发,不应该影响到现有的功能。新功能只能是扩展现有的代码实现,而不能更改现有的代码。新功能的开发,带来新的BUG,大多是在常理之中。但新功能的开发,导致原有功能的BUG则是一个项目无法承受的。开发新功能的代价,决定了一个软件项目可以走多远。因此,几乎所有的设计模式都有这方面的考虑。例如:decorator, adapter, builder, strategy, state, bridge等。都体现了以稳定的代码应对无穷变化的思想。下面,以decorator为例说明:为何要出现装饰者这样的设计模式呢?为何不直接使用继承实现?在什么场合下装饰者比直接继承更合适?例如,咖啡屋报价软件。基于面向对像设计,可以有一个公共的接口IDrink,之后继承至这个接口,会有Milk,Coffee,Wine等。可以分别实现GetPrice()。但随着业务的扩展,比如咖啡里加糖,冰镇等,需要有不同的报价。这时GetPrice()的实现则慢慢复杂化。出现了许多if else(switch case)分支。每当业务扩展,或发生改变,相应的GetPrice()就在不停变化。显然不符合开闭原则。于是我们想到继续继承Coffee,比如SugarCoffee,IceCoffee等。这样确实符合了开闭原则,新功能不会影响到现有代码了。但是如果还有加糖冰镇呢?组合爆炸了吧!于是出现了装饰者模式,其设计类图如上图。最核心的部分就是一个装饰者可以用于装饰另一个装饰者,应对无穷组合,即充分利用了现有代码,又不需要改变他们。这样我们只需要从IDrink派生出IDrinkDecorator,并继承此接口,实现不同的装饰,比如SugarDecorator, IceDecorator等。如果要计算加糖冰镇咖啡的价格,只须要形如这样的代码:IceDecorator(SugarDecorator(Coffee().GetPrice()。总之,遵守开闭原则,在软件开发过程中十分重要。它直接关系到一个软件产品,是否处于BUG收敛,功能完善的过程进而影响产品的质量和生命期。3. 替换原则 (the Liskov Substitution Principle LSP)子类应当可以替换父类并出现在父类能够出现的任何地方。这个原则是Liskov于1987年提出的设计原则。它同样可以从Bertrand Meyer 的DBC (Design by Contract) 的概念推出。同样,几乎所有的设计模式都遵守了这条原则。Strategy, proxy,command等。还是绘画几何图形的例子:void DrawShape(const Shape& s)if (typeid(s) = typeid(Square)DrawSquare(static_cast(s);else if (typeid(s) = typeid(Circle)DrawCircle(static_cast(s);利用LSP和多态技术就可以避免这些类型转换,并使函数代码稳定。还有一些情况下,对LSP的遵守就不向上面例子那样简单。有这样的一个设计,Square is a Rectangle。因为方形要求长宽相等,于是重载Rectangle的SetWidth和SetHeight函数,在设置长或宽之后,同时改变宽或长的值,保证方形的几何性质。看起来似乎一切都很好。但是:void g(Rectangle& r)r.SetWidth(5);r.SetHeight(4);assert(r.GetWidth() * r.GetHeight() = 20);用户在使用基类指针时,他们只知道基类接口的precondition和postcondition,因此,子类对这些接口都实现都需要和基类所预期的结果一致。Rectangle的setwidth,的postcondition是 width = w ,height = ori.height。这显然和square的width=w height = width不同。因此方形不能直接继承于矩行。虽然事实上Square is a Rectangle。4. 依赖原则 (the Dependency Inversion Principle DIP)怎样的设计是一个不好的设计?显然,不满足需求的设计一定是不好的,但满足需求的设计就一定好吗?当一个程序在满足所有现有需求时,只要出现一个或以上下述情况,同样可以认为是一个不好的设计。a.增加新需求会有巨大代价,难以扩展。b.增加新需求,很容易带来原有功能的破坏。c.不能提取成独立模块以复用。一个好的设计可以让程序员生活得轻松一些!看如下Copy类:Copy依赖于Read Keyboard和Write Printer,后两者有很强复用性,因为它们职责单一,且没有依赖。但Copy几乎没有被复用的可能。假如我们现在想要将Keyborad的内容复制到显示器上,Copy则完全无法使用。因为它依赖于Writeprinter,且只能在Printer上输出。如何解决?这样的设计,让Copy包含Reader和Writer两个接口,而Keyborad Reader和Printer Writer继承至他们。则可以让copy可以简单的被复用。因为Copy现在只依赖于Reader和Writer,而不是一个具体的Keyboard Reader或是Printer Writer。更高的抽象度意味着更好的复用性和代码稳定性。在结构化设计中,我们可以看到底层的模块是对高层抽象模块的实现(高层抽象模块通过调用底层模块),比如一开始那个无法复用的Copy。这说明,抽象的模块要依赖具体实现相关的模块,底层模块的具体实现发生变动时将会严重影响高层抽象的模块,显然这是结构化方法的一个硬伤。面向对象方法的依赖关系刚好相反,具体实现类依赖于抽象类和接口。根据DIP,在进行业务设计时,与特定业务有关的依赖关系应该尽量依赖接口和抽象类,而不是依赖于具体类。具体类只负责相关业务的实现,修改具体类不影响与特定业务有关的依赖关系。假设,有个台灯类Lamp ,和走道的路灯类RoadLight,我们想实现它们的触摸开关类,可以用来操控灯。非常简单:Class Controller on();off();Lamp* m_Lamp;RoadLight* m_RoadLight; ;这样一来,只需要在on,off接口里调用相应指针的on,off就可以实现功能。这完全是不符合DIP。开关类,直接依赖于具体的台灯和路灯。当开关控制走道路灯时须要路灯通知开关当前路灯的状态,路灯暗时,开关需要发亮,提示其位置,方便使用。路灯亮时,开关提示需要关闭。那么路灯类需要直接操作开关类,好吧,我们继续之前的思路,把开关类的指针放到路类类里,并相应调用其接口。功能完成?编译,发现挂了吧。这是传说中的循环依赖。诚然,C+语言特性,前置申明可以一定程度上解决这类问题。但我们应该在设计层面根除它。提供抽象开关类接口IController和抽象灯的接口ILight,所有的开关和灯都将继承至它们,并对接口进行操作。完美解开循环依赖。同时,根据可替换原则,要开关支持新型的灯,完全不需要改变开关类代码,只需要将ILight指针指向新型的灯就大功告成。5. 接口分离原则(the Interface Segregation Principle ISP)这个原则的本质相当简单:接口最小化。换句话说,也可以认为是接口要纯净,稳定。假设,我们有TimerClient类,提供了计时回调接口OnTick()。有Door类,提供Open,Close接口。现在我们需要实现一个可以在打开时间超时就报警的TimedDoor。如何实现?上图是一种解决方案。让door直接继承自timerclient,然后timeddoor去实现这个接口。显然这样的设计遗害万年。以后任何door的子类都要去实现tick这个接口,就算它们并不关心。同样,任何一个子类需要一个DOOR没有的接口时就直接加上去,这会影响到所有继承自DOOR的类型,除非在DOOR上提供默认实现,但这样明显违反了LSP原则。子类替换时,随时可能发生不等价的情况。因为每个子类中都有可能出现自己用不到且不必关心的接口。由于我们只需要一个实现了OnTick接口的Door而已,这时很自然的想到了adapter。设计如下,增加再多的功能,也不会污染原来纯净的Door的接口。同样,我们可以采用多重继承来解决:让TimerManager只见OnTick接口,Door的使用者,知道这是个door。接口分离原则会大大提高代码的安全性,和灵活性。一个抽象接口,应该是针对其使用者做得最小化设计。例如,在一款游戏中,玩家可以释放技能,可以使用物品,可以接受任务,可以在玩家的控制下移动,等等。任务系统的实现者,任何情况下都不会去关心一个玩家是不是可以释放技能,它只关心任务相关的东西。同样,战斗系统不会关心玩家是不是能接受任务。所以玩家这个类,暴露给任务系统的接口应该和暴露给战斗系统的接口分离。可以考虑如下实现:Player:public questHolder, public skiller,public control等。任务系统内拿到的就是questholder,战斗系统内使用的是skiller。 假如策划提了个需求,希望玩家可以控制某个NPC去杀个怪。 你打开现有的NPC.cpp类,发现根本没有这方面考虑,如何解决?重构?重构意味着,重新测试,从开发到QA成倍的工作量。而且重构发生的越晚,代价越大,有时根本无法承受。如果系统中原来就是接口分离,那么这个工作是个轻量级的工作。ControllableNPC:public NPC,public control。并针对这个NPC做一些实现,这对现有的系统没有任何伤害。同时,新实现的NPC类也可以无伤和任何原有的模块整合。只要原来可以控制玩家的模块,现在可以安全的控制这个NPC。以上五个原则是面向对象中常常用到的原则。二、模块划分原则: 1. Reuse/Release Equivalance(REP)模块划分粒度就是模块被复用的粒度。这个原则帮助我们确定应该划分出哪些模块。2. The Common Reuse Principle (CRP)当一个模块中的某个类被复用时,模块中所有的类应同时被复用。这个原则可以帮助我们确定哪些类应被划分在同一个模块中。因为当一个模块被复用时,使用者会直接依赖于整个模块。我们并不希望存在无意义的依赖。当有使用者只需要依赖于该模块的一部份类时,这个模块就需要重新划分。3. The Common Closure Principle (CCP)当一个改变影响了某模块中的一个类时,这个模块内的类都应同时受到影响。但模块外的所有类不应受到影响。因为当我们不得不对程序做一些修改时,我们更希望一个修改只发生在一个模块中,而不要有全局性的影响。这样我们只需要重新发布这个模块。也就是说,那些可能会一起更改的类应放在同一个模块中。OCP原则告诉我们,类应该对改变关闭,这个原则强化了OCP。也就是说一个模块中的所有类应该是同时对同一类改变是关闭的。当需求变化。一定需要改变时,那么它会将改变局限在最小的范围里。三、 模块之间的耦合性原则:1. The Acyclic Dependencies Principle (ADP)模块间的依赖关系应该是无环单向依赖。依赖关系应该是有向无环图。多个程序员共同维护同一份代码,给我们带来了极大不便。所以我们需要将工程模块化,同时每个模块由一个专职程序员维护。上图是一个依赖良好的模块关系图。首先,如果MyDialogs模块更新了,只有MyTasks和MyApplication需要考虑是否有必要更新,其他模块也完全不受影响。同时,如果MyDialogs需要写单元测试也十分方便,它只依赖于Windows,只要引入Windows,就可以编写测试用例。如果是这样的依赖,情况就变得复杂多
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 草原文化活动方案
- 焊接员工考试题及答案
- 公司写作考试题及答案
- 高考试题及答案化学
- 幼儿园教学教案设计:安全使用筷子
- 合作合同履行与守秘保证承诺书(9篇)
- 我和妈妈的一次出游作文11篇范文
- 税务申报表格快速填写指引
- (正式版)DB15∕T 3652-2024 《沙化土地综合治理技术规程》
- 生命常常如此绿色10篇
- 2025兴业银行福建总行国际业务部交易银行部招聘若干人备考考试题库附答案解析
- 1.3 几和第几(课件)数学苏教版一年级上册(新教材)
- 1.3加与减①(课件)数学沪教版二年级上册(新教材)
- 2025至2030中国HPV相关疾病行业项目调研及市场前景预测评估报告
- 无领导小组讨论的经典面试题目及答案解析
- 许昌襄城县特招医学院校毕业生招聘笔试真题2024
- 永辉超市快消培训
- (2025秋新版)苏教版三年级数学上册全册教案
- 2025北京京剧院招聘10人考试备考试题及答案解析
- 2025至2030中国催收外包服务行业销售模式及未来营销策略分析报告
- 2025-2030矿山工程机械租赁市场商业模式与风险防控报告
评论
0/150
提交评论