设计原则是基本的工具,应用这些规则可以使你的代码更加灵活、更容易维护,更容易扩展。
OO(面向对象)的设计目标
※可扩展性Extensibility:有了新的需求,新的性能可以容易添加到系统中,不影响现有的性能,也不会带来新的缺陷。
※可修改性Flexibility:系统一部分的代码要修改时不会破坏系统的现有结构,也不会影响到其它的部分。
※可替换性Pluggability:可以将系统中的某些代码替换为相同接口的其它类,不会影响到系统。
基本原则:
- 封装变化Encapsulate what varies.
- 面向接口编程而非实现 Code to an interface rather than to an implementation.
- 优先使用组合而非继承 Favor Composition Over Inheritancele responsibility principle 单一职责
1.SRP:the single resp(单一职责原则(SRP), 高内聚)
系统中的每一个对象都应该只有一个单独的职责,而所有对象所关注的就是自身职责的完成。
Every object in your system should have a single responsibility ,and all the object s services should be focused on carrying out that single responsibility .
- 每一个职责都是一个设计的变因,需求变化的时候,需求变化反映为类职责的变化。当你系统里面的对象都只有一个变化的原因的时候,你就已经很好的遵循了SRP原则。
- 如果一个类承担的职责过多,就等于把这些职责耦合在了一起。一个职责的变化就可能削弱或者抑制这个类其它职责的能力。这种设计会导致脆弱的设计。当变化发生的时候,设计会遭到意想不到的破坏。
- SRP 让这个系统更容易管理维护,因为不是所有的问题都搅在一起。
- 内聚Cohesion 其实是SRP原则的另外一个名字.你写了高内聚的软件其实就是说你很好的应用了SRP原则。
- 怎么判断一个职责是不是一个对象的呢?你试着让这个对象自己来完成这个职责,比如:“书自己阅读内容”,阅读的职责显然不是书自己的。
- 仅当变化发生时,变化的轴线才具有实际的意义,如果没有征兆,那么应用SRP或者任何其它的原则都是不明智的。epeat yourself Principl
2.DRY:dont repeaty
通过抽取公共部分放置在一个地方避免代码重复.
Avoid duplicate code by abstracting out things that are common and placing those thing in a single location .
- DRY 很简单,但却是确保我们代码容易维护和复用的关键。
- 你尽力避免重复代码候实际上在做一件什么事情呢?是在确保每一个需求和功能在你的系统中只实现一次,否则就存在浪费!系统用例不存在交集,所以我们的代码更不应该重复,从这个角度看DRY可就不只是在说代码了。
- DRY 关注的是系统内的信息和行为都放在一个单一的,明显的位置。就像你可以猜到正则表达式在.net中的位置一样,因为合理所以可以猜到。
- DRY 原则:如何对系统职能进行良好的分割!职责清晰的界限一定程度上保证了代码的单一性。-Close Principle开闭原则
3.OCP:open-close 开放-封闭原则(OC),扩展开放(对于系统),修改关闭(对于成型模块)
类应该对修改关闭,对扩展打开;
Classes should be open for extension ,and closed for modification .
- 扩展开放:某模块的功能是可扩展的,则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。
- 修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求是修改 关闭的。
- OCP 关注的是灵活性,改动是通过增加代码进行的,而不是改动现有的代码;
- OCP的应用限定在可能会发生的变化上,通过创建抽象来隔离以后发生的同类变化
- OCP原则传递出来这样一个思想:一旦你写出来了可以工作的代码,就要努力保证这段代码一直可以工作。这可以说是一个底线。稍微提高一点要求,一旦我们的代码质量到了一个水平,我们要尽最大努力保证代码质量不回退。这样的要求使我们面对一个问题的时候不会使用凑活的方法来解决,或者说是放任自流的方式来解决一个问题;比如代码添加了无数对特定数据的处理,特化的代码越来越多,代码意图开始含混不清,开始退化。
- OCP 背后的机制:封装和抽象;封闭是建立在抽象基础上的,使用抽象获得显示的封闭;继承是OCP最简单的例子。除了子类化和方法重载我们还有一些更优雅的方法来实现比如组合;
怎样在不改变源代码(关闭修改)的情况下更改它的行为呢?答案就是抽象,OCP背后的机制就是抽象和多态
- 没有一个可以适应所有情况的贴切的模型!一定会有变化,不可能完全封闭.对程序中的每一个部分都肆意的抽象不是一个好主意,正确的做法是开发人员仅仅对频繁变化的部分做出抽象。拒绝不成熟的抽象和抽象本身一样重要。
- OCP是OOD很多说法的核心,如果这个原则有效应用,我们就可以获更强的可维护性 可重用 灵活性 健壮性 LSP是OCP成为可能的主要原则之一kov substitution principle
4.LSP:the liskov subs( 里氏代换原则)
子类必须能够替换基类。
Subtypes must be substitutable for their base types.
基本地说,也就是“子类型必须能够替换掉它们的父类型”。一个软件实体如果运用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。在程序中,把父类都替换成它的子类,程序的行为没有变化.(正方形不是长方形),最有难度的约束设计.
- LSP关注的是怎样良好的使用继承.
- 必须要清楚是使用一个Method还是要扩展它,但是绝对不是改变它。
- LSP清晰的指出,OOD的IS-A关系是就行为方式而言,行为方式是可以进行合理假设的,是客户程序所依赖的。
- LSP让我们得出一个重要的结论:一个模型如果孤立的看,并不具有真正意义的有效性。模型的有效性只能通过它的客户程序来表现。必须根据设计的使用者做出的合理假设来审视它。而假设是难以预测的,直到设计臭味出现的时候才处理它们。
- 对于LSP的违反也潜在的违反了OCP原则
Liskov 替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,
才能保证继承复用是可靠地。实现的方法是面向接口编程:将公 共部分抽象为基类接口或抽象类,
通过ExtractAbstractClass,在子类中通过覆写父类的方法实现新的方式支持同样的职责。Liskov替 换原则能够
保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
5.DIP:依赖倒置原则
高层模块不应该依赖于底层模块 二者都应该依赖于抽象
抽象不应该依赖于细节 细节应该依赖于抽象
依赖抽象,不要依赖具体。
- 什么是高层模块?高层模块包含了应用程序中重要的策略选择和业务模型。这些高层模块使其所在的应用程序区别于其它。
- 如果高层模块依赖于底层模块,那么在不同的上下文中重用高层模块就会变得十分困难。然而,如果高层模块独立于底层模块,那么高层模块就可以非常容易的被重用。该原则就是框架设计的核心原则。
- 这里的倒置不仅仅是依赖关系的倒置也是接口所有权的倒置。应用了DIP我们会发现往往是客户拥有抽象的接口,而服务者从这些抽象接口派生。
- 这就是著名的Hollywood原则:"Don't call us we'll call you."底层模块实现了在高层模块声明并被高层模块调用的接口。
- 通过倒置我们创建了更灵活 更持久更容易改变的结构
- DIP的简单的启发规则:依赖于抽象;这是一个简单的陈述,该规则建议不应该依赖于具体的类,也就是说程序汇总所有的依赖都应该种植于抽象类或者接口。
- 如果一个类很稳定,那么依赖于它不会造成伤害。然而我们自己的具体类大多是不稳定的,通过把他们隐藏在抽象接口后面可以隔离不稳定性。
- 依赖倒置可以应用于任何存在一个类向另一个类发送消息的地方
- 依赖倒置原则是实现许多面向对象技术多宣称的好处的基本底层机制,是面向对象的标志所在。原则
6.ISP:接口隔离原则
不应该强迫客户程序依赖它们不需要的使用的方法。
多个和客户相关的接口要好于一个通用接口
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,
但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
- 接口不是高内聚的,一个接口可以分成N组方法,那么这个接口就需要使用ISP处理一下。
- 接口的划分是由使用它的客户程序决定的,客户程序是分离的接口也应该是分离的。
- 一个接口中包含太多行为时候,导致它们的客户程序之间产生不正常的依赖关系,我们要做的就是分离接口,实现解耦。
- 应用了ISP之后,客户程序看到的是多个内聚的接口。
7. 迪米特法则(LoD).又叫最少知识原则(Least Knowledge Principle 简写LKP)
就是说,一个对象应当对其它对象有尽可能少的了解.如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其 中一个类须要调用另一个类的某一个要领的话,可以通过第三者转发这个调用。“。它强调我们在封装类的时候,在类的设计结构上,每一 个类都应当降低成员的访问权限。类与类之间的耦合度越低越好,因为一个类被修改,不会对有关联的其他类也执行 修改。(发布-订阅模 式),低耦合.
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果一个类需要调用领一个类的
某个方法话,可以通过第三者转发这个调用。迪米特法则首先强调的前提是在类的设计上,每一类都应当尽量
降低成员的访问权限。它的根本思想是强调类之间的松耦合。
8.合成/聚合复用原则:尽量使用合成/聚合,尽量不要使用继承。
合 成(Composition)和聚合(Aggregation)都是关联的特殊种类,聚合表示一种弱的拥有关系;
合成这是一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。
优先使用合成或聚合原则将有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承
层次会保持较小规 模,并且不太可能增长为不可控制的庞然大物
OO设计原则和目标的关系
1.可扩展性Extensibility :允许一个具有同样接口的新类替代旧类,是对抽象接口的复用。
客户端依赖于抽象接口,而不是一个具体实现类,使得这个具体类可以被别的具体类替换,
而不影响客户端。以下原则实现可扩展性。
※开/闭原则
※里氏替换原则
※依赖倒转原则
※合成/聚合复用原则
2.可修改性Flexibility:模块相对独立,通信尽可能少。这样当一个模块修改时,对别的模块的影响很小。
以下原则实现可修改性。
※开/闭原则
※迪米特法则
※接口隔离原则
3、可替换性Pluggability:当一部分不再满足需要时,可以将旧的部分拔出,新的部分插入。
以下原则实现可替换性。
※开/闭原则
※里氏代换原则
※依赖倒转原则
※合成/聚合复用原则
OO(面向对象)的设计过程
1. 分析式样,进行机能分类。
2. 根据机能进行类的抽象。
※
※
3. 设计抽象基类和接口类。
※
※
※
4.确定类间的耦合关系。
4.1 决定耦合的程度的依据何在呢?
※
※
※
※
4.2 怎样做到依赖倒转?
※
※
5.运用OO设计的5大原则来对设计进行进一步的优化
※
※
※
※
总体上说,在面向对象设计时,要充分考虑设计的5大原则,但不是强求的,一味的追求满足原则也可能会
导致设计出的系统在性能和资源上的消耗,可以根据具体的情况来具体的分析和设计。