一、面向对象介绍
1、面向过程
以“过程”为中心的编程思想,分析出解决问题所需的步骤,然后用函数把这些步骤一步步的实现,使用的时候一个个依次调
用就好。面向过程强调的是模块化。
2、面向对象
面向对象是相对于面向过程来说的,以“对象”为中心的编程思想,把问题看成一个个对象,通过对象的属性和行为,将问题
解决的。面向对象强调的是把事物对象化。
复杂来说面向对象是以“对象”为基本单元构建系统,对象是把数据和数据操作方法放在一起,作为一个相互依存的整体。把
同类的对象抽象出其共性,形成类。所以说类是对象的抽象,对象是类具体化的体现(类是大范围的,一个类可以有多个对
象),类中的大多数数据只能通过本类的方法实现,类包含说明和实现,说明部分被外界所看到,通过简单的外部接口与外
界进行联系;实现部分不被外界所看到,在内部通过不同方法的构建,实现不同的功能。对象与对象之间通过消息进行沟
通,程序流程由用户在使用中决定。
3、面向对象优点
解决越来越复杂的需求,提高了软件的重用性,灵活性和拓展性。
二、面向对象的设计原则
1、开闭原则(OCP)(对可变性的封装原则)
(1)简介
开闭原则是面向对象程序设计的第一原则,即一个软件实体应该对扩展开放,对修改关闭。当一个软件需要增加或者修改某
些功能时应该尽可能的只是在原来的实体中增加代码,而不是修改代码。
(2)优点
保证了系统具有一定的稳定性,同时也保证了系统的灵活性。使我们设计出来的系统是扩展性良好的系统。
(3)实例
这种设计在进行业务扩展时需要修改原代码。一种较好的设计方式是:将计价策略合并到Part的getPrice()方法中。
Part和ConcretePart的示例如下:
//Part类是所有计价策略类的父类
public class Part{
private double basePrice;
public void setPrice(doube price){basePrice = price;}
public double getPrice(){return basePrice;}
}
//子类实现计价策略
public class ConcretePart extends Part{
public double getPrice(){
//return (1.45 * basePrice); //计算额外的费用
return (0.90 * basePrice);
}
}
(4)如何在OO中引入OCP原则
把对实体的依赖改为对抽象的依赖即可。
(5)怎样做到开闭原则
1)多使用抽象类:
在设计类时,对于拥有共同功能的相似类进行抽象化处理,将公用的功能部分放到抽象类中,所有的操作都调用子类。这
样,在需要对系统进行功能扩展时,只需要依据抽象类实现新的子类即可。
2)多使用接口:
与抽象类不同,接口只定义子类应该实现的接口函数,而不实现公有的功能。在现在大多数的软件开发中,都会为实现类定
义接口,这样在扩展子类时实现该接口。如果要改换原有的实现,只需要改换一个实现类即可。
2、里氏替换原则(LSP)
(1)简介
对于基类(父类)是合法的操作,对于子类也一定合法。换言之,基类出现的地方,子类也可以出现。子类必须能够替换掉
它们的父类,并出现在父类能够出现的任何地方。经过替换,代码还能正常工作。
(2)优点
能够保证继承关系的正确性。
(3)实例
①正方形不是长方形的子类
//长方形类
class Rectangle{
double length;
double width;
public double getLength(){
return length;
}
public void setLength(double length){
this.length = length;
}
public double getWidth(){
return length;
}
public void setWidth(double width){
this.width = width;
}
}
//正方形类
class Square extends Rectangle{
//由于正方形长宽必须相等所以在方法setLength和setWidth中对长宽赋值相同
public void setWidth(double width){
super.setWidth(width);
super.setLength(width);
}
public void setLength(double length){
super.setWidth(length);
super.setLength(length);
}
}
//测试类
class TestRectangle{
//模拟长方形宽度逐渐增长的效果
public resize(Rectangle rec){
while(rec.getWidth()<=rec.getLength()){
rec.setWidth(rec.getWidth()+1);
}
}
}
在以上的代码实例中,我们运行时就会发现,把一个普通的长方形作为参数传入resize方法,就会看到长方形长度逐渐增
加,当宽大于长时代码就会停止,这种行为符合我们的预期。当我们传入一个正方形传入resize方法后,会看到正方形的宽
度和长度都在不断增加,代码会一直执行,知道系统产生溢出错误。
所以Rectangle类型不能被Square替代。他们之间的继承关系违反了里氏替换原则,即正方形不是长方形。
(4)总结
1)面向对象的设计关注的是对象的行为。它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。
2)类的继承关系就是一种"Is-A"关系,实际上是指行为上的“Is-A"关系,可以把它描述为“Act-As”。
3)继承关系要求子类要具有基类全部的行为。
4)所有派生类的行为功能必须和使用者对其基类的期望保持一致,如果派生类达不到这一点,那么必然违反里氏替换原则。
3、依赖倒置原则(DIP)
(1)简介
1)高层模块不应该依赖于底层模块,二者都应该依赖于抽象
2)抽象不应该依赖于细节,细节应该依赖与抽象,要针对接口编程,不要针对实现编程。
注:
低层模块是指实现了一些基本的或初级的操作的低层次的类。
高层次模块是指封装了某些复杂逻辑的类,并且其依赖于低层次的类。
依赖是指在程序设计中,如果一个模块A使用/调用了另一个模块B,我们称模块A依赖模块B。
高层模块为什么不能依赖底层模块?
高层模块包含了一个应用程序中的重要的策略选择和业务模型,正是这些高层模块才使得其所有的应用程序区别于其他,如
果高层依赖与低层,那么对低层模块的改动就会直接影响到高层模块,从而迫使它们依次做出改动。
(2)具体原则
1)任何变量都不能拥有一个具体类的指针或者引用。
2)任何类都不应该从具体类派生。
3)任何方法都不应该覆写基类中已经实现的方法。
也就是说应当使用接口和抽象类进行变量类型声明、参数声明、方法返还类型说明,以及数据类型的转换等,而不要用具体
类进行变量的类型声明、参数类型声明、方法返还类型说明,以及数据类型的转换等。
要保证做到这一点,一个具体的类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法。
(3)类结构设计方式
上层类(High Level Classes)->抽象接口层(Abstraction Layer)->低层类(Low Level Classes)
(4)实例
这种设计的缺点:耦合太紧密,Light发生变化将影响ToggleSwitch
解决办法一:将Light变成Abstract,然后具体子类继承自Light。
优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与TubeLight继承自Light,可以根据开闭原则进行扩展。
缺点:如果用ToggleSwitch控制一台电视就很困难了。
解决办法:
(5)总结
依赖倒置原则要求用户尽量依赖于抽象而不是实现。
传统的过程式的程序设计方法倾向于使得高层模块依赖低层模块,DIP原则把这个依赖关系倒转过来。一般而言,高抽象层次包含的是应用系统的商务逻辑和宏观的控制过程,而低层次模块包含了一些具体的算法。
低层次代码经常会变动,高层次则相对稳定一些。为了更有效的保持系统稳定性,应该使得低层次的模块依赖于高层次的米快,从复用的角度来看,只有实现依赖倒置原则,才会避免当低层模块发生改变的时候不会倒置高层次模块的修改。
4、接口隔离原则(ISP)
(1)描述
尽量使用多个小而专用的接口而不是单一的接口
(2)实例
实例中Agent接口中的方法过多。 修改如下
5、合成聚合复用原则(CARP)
(1)描述
优先使用合成和聚合,而不是继承来达到复用的目的。终态类不可继承,要复用终态类的代码,唯一的办法就是合成或者聚
合。因为一个非终态类的内部成员向子类公开,因此,继承关系是一种“白箱”复用。而白箱复用是一种破坏封装原则的复用
办法。
6、迪米特法则(LoD)(最少知识法则)
(1)描述
指一个对象应当对其它对象尽可能少的了解,并尽可能少的与其他对象发生联系。与模块化程序设计中的模块低耦合高内聚
是同样的道理。
7、单职责原则(SRP)
(1)描述
一个类应该有且仅有一个职责。职责的定义:所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职
责,那么就会有多个不同的原因引起该类变化,其实就是耦合了多个互不相关的职责,就会降低这个类的内聚性。