面向对象设计原则

设计模式,一个老生常谈的话题了,在这一点上,花了无数的精力,无数的时间去学习,总结。直到有一天,在敏捷里,一个理念给我带来了灵感,这就是TDD, 测试驱动开发。

在我正式学习计算机语言时,第一门语言就是Java,是一门面向对象语言,于此同时进行的还有C。在那个不理解的时代,它们2个的区别并没有感觉很大。

项目越做越多,越来越大,越发的理解面向对象OO的精髓。

OO解决什么问题

提前要知道这个世界上有很多编程模型,面向过程编程 OPP面向切面编程 AOP面向对象编程 OOP,等等。即然有多种,就要知道为什么OOP那么实用,有什么区别。

基于OO的思想又衍生了很多的设计模式,像GoF的23种设计模式,这些又是什么关联。

变化

如果给定一个需求,做出一个能运行的东西出来,无论是用C还是用Java,区别并不大,这也是大学时的感觉,没有体会到OO的优点。但一个真正的项目从来都不是固定状态的,

不变的东西用什么模式都行,if else写一地都没关系,因为他不会变,写好就没问题了,你也不用管代码好不好懂。

但现实却反的,不变的东西只存在于大学的作业本上。现实的任何东西都有变的可能,互联网从我读大学到现在已经完全变掉了,更何况那些商业模式和背后的计算机系统。如何应会变化,是对传统语言的挑战。

OO是如何应对变化

OO将程序按现实世界进行抽像,也就是对象,对象有自己的属性,表示其状态,好比汽车有轮子、窗户、喇叭一般;对象也有自己的行为,好比汽车能8,能跑。这些属性、行为构成对象,于现实世界吻合,容易理解。同时在计算机层面,将这些属性、行为(方法)归到一个对象里,通过设置公开隐私的权限供其它对象进行调用,这种方式被称作封装。

封装从技术上保护了对象的隐私和提供对象间的交互,为模块化提供了可能,降低大型软件的复杂度。

和现实世界一样,对象千奇百怪,同为车,却有SUV, CRV, MVP等不同车型,在OO里,这种现实被另一种技术延承着:继承。
通过继承,可能得到父类身上的所有信息,然后在子类中还可以添加自己的特征(属性、行为)。

看完OO语言的这3大特性,再回头看变化,所谓的变化都是有一个基本的,如果全变的意义和不变是相同的。因为全变就意味着全部重写。所以这里讨论的变是指在一定格局下发生的变化。如:

iPhone App中有一个联系人的页面,每个人有不同的角色:公共账号、管理员、普通用户、VIP用户。不同的用户在其具体显示页面里出现界面会有所不同。

OOP中如何解决这样的问题呢,抽像,将这些角色View中相同的地方抽像出来,然后建立多个对象(类),对应着不同的角色。然后在每个角色上添加那些不同的地方。这样,当新的角色加入时,可以方便的通过添加新的对象来解决变化。

抽象层次

在刚才的小示例上提到了抽象,想必也听过面像抽象编程面向接口编程这样的话来形容OOP,也就是说抽象至关重要,它决定着赢取变化的关健。

在上例中,如果抽象的类中所含属性方法过多,意味着你迎接变化的能力就越小,但对于现有的角色,它们可被继承的东西越多,所做的重复就越少;反之亦然。

那如何抽象呢,明确的说,这不是一个技术问题,这是一个行业的经验问题。为什么好的程序员很贵,一方面他善用OOP的抽象,另一方面,他有着行业里的抽象经验。

优点

  1. 易维护
    采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。
  2. 质量高
    在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
  3. 效率高
    在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
  4. 易扩展
    由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。

OO五大原则

1 单一职责原则 (The Single Responsiblity Principle,简称SRP)

就一个类而言,应该仅有一个引起它变化的原因。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。测试驱动的开发实践常常会在设计出现臭味之前就迫使我们分离职责。

2 开放-封闭原则 (The Open-Close Principle,简称OCP)

软件实体(类、模块、函数)应该是可扩展的,但是不可修改的。也就是说:对于扩展是开放的,对于更改是封闭的。怎样可能在不改动模块源代码的情况下去更改它的行为呢?怎样才能在无需对模块进行改动的情况下就改变它的功能呢?关键是抽象!因此在进行面向对象设计时要尽量考虑接口封装机制、抽象机制和多态技术。该原则同样适合于非面向对象设计的方法,是软件工程设计方法的重要原则之一。

3 Liskov 替换原则(The Liskov Substitution Principle,简称LSP)

子类应当可以替换父类并出现在父类能够出现的任何地方。这个原则是Liskov于1987年提出的设计原则。它同样可以从Bertrand Meyer 的DBC (Design by Contract〔基于契约设计〕) 的概念推出。

4 依赖倒置原则(The Dependency Inversion Pricinple,简称DIP)

1、高层模块不应该依赖于低层模块。二者都应该依赖于抽象。2、抽象不应该依赖于细节。细节应该依赖于抽象。在进行业务设计时,与特定业务有关的依赖关系应该尽量依赖接口和抽象类,而不是依赖于具体类。具体类只负责相关业务的实现,修改具体类不影响与特定业务有关的依赖关系。在结构化设计中,我们可以看到底层的模块是对高层抽象模块的实现(高层抽象模块通过调用底层模块),这说明,抽象的模块要依赖具体实现相关的模块,底层模块的具体实现发生变动时将会严重影响高层抽象的模块,显然这是结构化方法的一个”硬伤”。面向对象方法的依赖关系刚好相反,具体实现类依赖于抽象类和接口。

5 接口隔离原则 (The Interface Segregation Principle,简称ISP)

采用多个与特定客户类有关的接口比采用一个通用的涵盖多个业务方法的接口要好。
ISP原则是另外一个支持诸如COM等组件化的使能技术。缺少ISP,组件、类的可用性和移植性将大打折扣。这个原则的本质相当简单。如果你拥有一个针对多个客户的类,为每一个客户创建特定业务接口,然后使该客户类继承多个特定业务接口将比直接加载客户所需所有方法有效。

以上五个原则是面向对象中常常用到的原则。此外,除上述五原则外,还有一些常用的经验诸如类结构层次以三到四层为宜、类的职责明确化(一个类对应一个具体职责)等可供我们在进行面向对象设计参考。但就上面的几个原则看来,我们看到这些类在几何分布上呈现树型拓扑的关系,这是一种良好、开放式的线性关系、具有较低的设计复杂度。一般说来,在软件设计中我们应当尽量避免出现带有闭包、循环的设计关系,它们反映的是较大的耦合度和设计复杂化。