面向对象是相对于面向过程而言的。面向过程语言是一种基于功能分析的、以算法为中心的程序设计方法;而面向对象是一种基于结构分析的、以数据为中心的程序设计思想。早在面向过程语言时代,有一句话说:程序=算法+数据结构。而现在在面向对象语言时代,这句话变为:程序= 对象+消息。对象:万物皆对象; 消息:指对象之间的相互通信。在面向对象语言中有一个有很重要东西,叫做类。从面向过程的角度看,类就是一个特殊的数据结构,它就好像是我们C语言中的结构体;从面向对象的角度看,类就是具有相同属性和方法的对象的集合。
面向对象有三大特性:封装、继承、多态。
封装
所谓封装,就是指隐藏对象的实现细节,给外界提供公共的方法来访问。
这一点,我个人认为和面向过程语言有本质的区别。在C语言中,我们必须在乎每一个实现细节,去关注每一个过程; 而自从从在面向对象语言中提出了封装这个概念后,我们就可以不必要去关心每一个对象的实现细节, 我们只要关注我们所要实现的功能就行,然后根据给我们提供好的接口,我们去面向接口编程就行了。
面向对象的封装思想,我认为应用的最好、最成功的地方,就是在微软的.NET技术上.微软把很多经常用到的功能都封装在一个控件里,作为我们用户不必去在意到底这个控件是用什么实现的,它内部到底是怎么样的?我们只需要关心我们需要实现的功能就行, 然后根据控件给我们提供的属性和方法去操作这些控件,实现我们想要的功能就行了。
继承
面向对象的继承和生物学的继承很相似。子类可以继承父类的公共属性和方法,子类永远没法继承到父类的私有属性和方法。这一点还区别于生物学的继承,生物学中子类可以同时继承父亲和母亲。但是在 java|C#|C++ 等面向对象语言中,是不允许多重继承的,但可以多层继承。
为了弥补不能多重继承这点,在java和c#语言中都提出了接口这一概念。接口就是一种规范。它同样不会有实现细节,而只是给那些要实现这个接口的类一个规范和约束,约束那些实现这些接口的类,要实现我提供的功能,就必须实现我的所有方法, 要不你就声明为抽象类。
多态
多态,就是同一个实现接口,对不同的实例而执行不同的操作。这一点,可以理解为遗传学的变异。 同一个物种的后代由于基因突变或自然环境等影响,而造成不同的个体差异。而我们这里的多态也一样,同属一个基类的不同派生类也可以有自己不同于其他类的属性和方法。
除了这封装、继承、多态这三点基本特征外,面向对象还有一个很重要的概念,叫抽象。抽象就是把提取事物的本质东西,而忽视非本质的东西。对应于抽象这一概念,java和c#中都有一个类叫做抽象类。抽象类中可以给出方法的实现细节,同接口一样如果你要实现我这个抽象类就必须实现我的所有方法,要不你就声明你为抽象类。如果不允许抽象类中有方法的实现细节,这就变成了接口。
总之,面向对象就是万物皆对象,把客观事物当成一个对象来处理的程序设计思想。是一种区别于POP、SOA、面向组件等其他程序设计思想,是一种基于结构分析的,以数据为中心的程序设计思想。
OOP几大设计模式遵循的一般原则:
1.开-闭原则(Open-Closed Principle, OCP)
一个软件实体应当对扩展开发,对修改关闭.说的是,再设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展.换言之,应当可以在不必修改源代码的情况下改变这个模块的行为,在保持系统一定稳定性的基础上,对系统进行扩展。这是面向对象设计(OOD)的基石,也是最重要的原则。
2.里氏代换原则(Liskov Substitution Principle,常缩写为.LSP)
由Barbar Liskov(芭芭拉.里氏)提出,是继承复用的基石。
严格表达:如果每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换称o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型.
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别.只有衍生类可以替换基类,软件单位的功能才能不受影响,基类才能真正被复用,而衍生类也能够在基类的基础上增加新功能。
反过来的代换不成立
<墨子.小取>中说:”白马,马也; 乘白马,乘马也.骊马(黑马),马也;乘骊马,乘马也.”
该类西方著名的例程为:正方形是否是长方形的子类(答案是”否”)。类似的还有椭圆和圆的关系。
应当尽量从抽象类继承,而不从具体类继承,一般而言,如果有两个具体类A,B有继承关系,那么一个最简单的修改方案是建立一个抽象类C,然后让类A和B成为抽象类C的子类.即如果有一个由继承关系形成的登记结构的话,那么在等级结构的树形图上面所有的树叶节点都应当是具体类;而所有的树枝节点都应当是抽象类或者接口.
“基于契约设计(Design By Constract),简称DBC”这项技术对LISKOV代换原则提供了支持.该项技术Bertrand Meyer伯特兰做过详细的介绍: 使用DBC,类的编写者显式地规定针对该类的契约.客户代码的编写者可以通过该契约获悉可以依赖的行为方式.契约是通过每个方法声明的前置条件(preconditions)和后置条件(postconditions)来指定的.要使一个方法得以执行,前置条件必须为真.执行完毕后,该方法要保证后置条件为真.就是说,在重新声明派生类中的例程(routine)时,只能使用相等或者更弱的前置条件来替换原始的前置条件,只能使用相等或者更强的后置条件来替换原始的后置条件.
3.依赖倒置原则(Dependence Inversion Principle)
要求客户端依赖于抽象耦合.
表述: 抽象不应当依赖于细节,细节应当依赖于抽象.(Program to an interface, not an implementaction)
表述二: 针对接口编程的意思是说,应当使用接口和抽象类进行变量的类型声明,参量的类型声明,方法的返还类型声明,以及数据类型的转换等.不要针对实现编程的意思就是说,不应当使用具体类进行变量的类型声明,参量类型声明,方法的返还类型声明,以及数据类型的转换等.要保证做到这一点,一个具体的类应等只实现接口和抽象类中声明过的方法,而不应当给出多余的方法.只要一个被引用的对象存在抽象类型,就应当在任何引用此对象的地方使用抽象类型,包括参量的类型声明,方法返还类型的声明,属性变量的类型声明等.
接口与抽象的区别就在于抽象类可以提供某些方法的部分实现,而接口则不可以,这也大概是抽象类唯一的优点.如果向一个抽象类加入一个新的具体方法,那么所有的子类型一下子就都得到得到了这个新的具体方法,而接口做不到这一点.如果向一个接口加入了一个新的方法的话,所有实现这个接口的类就全部不能通过编译了,因为它们都没有实现这个新声明的方法.这显然是接口的一个缺点.
一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现处在抽象类所定义出的继承的登记结构中,而由于一般语言都限制一个类只能从最多一个超类继承,因此将抽象作为类型定义工具的效能大打折扣.
反过来,看接口,就会发现任何一个实现了一个接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个接口.从代码重构的角度上讲,将一个单独的具体类重构成一个接口的实现是很容易的,只需要声明一个接口,并将重要的方法添加到接口声明中,然后在具体类定义语句中加上保留字以继承于该接口就行了.而作为一个已有的具体类添加一个抽象类作为抽象类型不那么容易,因为这个具体类有可能已经有一个超类.这样一来,这个新定义的抽象类只好继续向上移动,变成这个超类的超类,如此循环,最后这个新的抽象类必定处于整个类型等级结构的最上端,从而使登记结构中的所有成员都会受到影响.
接口是定义混合类型的理想工具,所为混合类型,就是在一个类的主类型之外的次要类型.一个混合类型表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为.
联合使用接口和抽象类:由于抽象类具有提供缺省实现的优点,而接口具有其他所有优点,所以联合使用两者就是一个很好的选择. 首先,声明类型的工作仍然接口承担的,但是同时给出的还有一个抽象类,为这个接口给出一个缺省实现.其他同属于这个抽象类型的具体类可以选择实现这个接口,也可以选择继承自这个抽象类.如果一个具体类直接实现这个接口的话,它就必须自行实现所有的接口;相反,如果它继承自抽象类的话,它可以省去一些不必要的的方法,因为它可以从抽象类中自动得到这些方法的缺省实现;如果需要向接口加入一个新的方法的话,那么只要同时向这个抽象类加入这个方法的一个具体实现就可以了,因为所有继承自这个抽象类的子类都会从这个抽象类得到这个具体方法.这其实就是缺省适配器模式(Defaule Adapter).
什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节的改变而改变的真理. 它是系统内部的隐喻.
4.接口隔离原则
(Interface Segregation Principle, ISP)
一个类对另外一个类的依赖是建立在最小的接口上。
使用多个专门的接口比使用单一的总接口要好.根据客户需要的不同,而为不同的客户端提供不同的服务是一种应当得到鼓励的做法.就像”看人下菜碟”一样,要看客人是谁,再提供不同档次的饭菜.
胖接口会导致他们的客户程序之间产生不正常的并且有害的耦合关系.当一个客户程序要求该胖接口进行一个改动时,会影响到所有其他的客户程序.因此客户程序应该仅仅依赖他们实际需要调用的方法.
5.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过这些向对象的委派达到复用已有功能的目的.这个设计原则有另一个简短的表述:要尽量使用合成/聚合,尽量不要使用继承.
6.迪米特法则(Law of Demeter LoD)又叫做最少知识原则(Least Knowledge Principle,LKP)
就是说,一个对象应当对其他对象有尽可能少的了了解.
迪米特法则最初是用来作为面向对象的系统设计风格的一种法则,与1987年秋天由Ian Holland在美国东北大学为一个叫做迪米特(Demeter)的项目设计提出的,因此叫做迪米特法则[LIEB89][LIEB86].这条法则实际上是很多著名系统,比如火星登陆软件系统,木星的欧罗巴卫星轨道飞船的软件系统的指导设计原则.
没有任何一个其他的OO设计原则象迪米特法则这样有如此之多的表述方式,如下几种:
只与你直接的朋友们通信(Only talk to your immediate friends)
不要跟”陌生人”说话(Don’t talk to strangers)
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些本单位密切相关的软件单位.
就是说,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
7.单一职责原则(Simple responsibility pinciple SRP)
就一个类而言,应该仅有一个引起它变化的原因,如果你能想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责.应该把多于的指责分离出去,分别再创建一些类来完成每一个职责.
常说的OO五大原则就是指其中的 :
1、单一职责原则;
2、开放闭合原则;
3、里氏替换原则;
4、依赖倒置原则;
5、接口隔离原则。