- 所谓面向抽象编程,是指当设计某种重要的类时,不让该类面向具体的
类,而是面向抽象类,即所设计类中的重要数据是抽象类声明的对象,而
不是具体类声明的对象。
- 面向抽象编程的目的是为了应对用户需求的变化,将某个类中经常因需
求变化而需要改动的代码从该类中分离出去。面向抽象编程的核心是让类
中每种可能的变化对应地交给抽象类的一个子类去负责,从而让该类的设
计者不去关心具体实现,避免所设计的类依赖于具体的实现。面向抽象编
程使设计的类容易应对用户需求的变化。
举例理解:
注释:
Pillar 为 柱状体
bottom 为 底面图形类的对象
黑色的为抽象类
褐色的为抽象类的一个子类 圆形Circle
蓝色的为抽象类的另一个子类 矩形Rect
这两个子类中都有求面积方法 getArea ( ): double;
程序设计的目的是求柱状体的体积 求体积的方法在Pillar中:
double get Volume(){
return bottom.getArea()* height ;
}
从程序的目的中可以看到,这里是想求柱状体的体积,且求体积的方法都是 底面积 × 高 , 如果 需要计算体积的柱状体很少, 比如只需要计算一个圆柱体的体积,那么就需要创建一个圆形类Circle,在其内部声明一个 求面积的方法:getArea ( ): double ,然后在class Pillar 即柱状体类中,用对象组合的知识 组合一个 Circle类的对象 bottom, 然后在求体积的时候 用 bottom.getArea()* height来计算体积即可 ; 如果要求其他的柱状体的体积,那么就根据 底面的形状的不同创建不同的类, 然后定义求面积,然后再在class Pillar中组合新的对象名
例如 求完圆柱体的体积之后 还需要求一个长方体的面积 那么就需要在classPillar中组合一个 矩形类Rect的对象bottomRect
(注意: 为了区分,新组合的对象名一定不能与bottom重名,不然在计算底面积时用到的 bottom.gerArea() 程序就不知道是在求谁的底面积了 是Circle的?还是Rect的? 那它就会报错了 )
同时由于组合的对象名称改了 求面积的方法也改了 就得新加一个
double get VolumeRect(){
return bottomRect.getArea()* height ;
}
所以像这样的话,每需要求一个新的柱状体的体积,都需要填上一组代码 例如新增加的是底面图形 A类
那么需要新加的就是
A bottomA = new A;
...
double get VolumeA(){
return bottomA.getArea()* height ;
}
那么每新求一个柱状体的体积,就加类似这样的一组代码 需要求体积的柱状体一旦多了 代码的规模就会变得异常庞大 且难以维护
可见,这样的设计是失败的,是我们不愿意看到的
#### 那么成功的设计是什么样的呢?
在解决这个问题的时候,我们发现:
每新加一个柱状体 都需要去写一个新的 底面图形的类A 然后在A类中写一个求面积的方法getArea( ) class Pillar再去组合A类的对象 然后利用 getArea()* height 去求体积
但是每次组合一个新的A类的对象的时候都需要取其一个新的对象名 然后因为对象名改了 求体积的方法也得用这个新对象名再写一遍
*比如 起了一个新对象名 VolumeRect 那也得根据这个新对象名再写一个新的求体积的方法*
*double get VolumeRect(){*
*return bottomRect.getArea()* height ;*
*}*
那这个时候我们发现了 :
求体积的逻辑都是一样的 都是 创建一个底面图形类 组合他的对象 然后 对象.getArea()* height 去求体积
之所以会有这么多的麻烦,让代码冗长难以维护 就是 出在了 这个 每次都需要起一个新的对象名身上!!!
那我们就会想,有没有一种方法,能够就使用一个对象名,然后在求各种柱状体的体积的时候,这个对象能够根据不同的情况执行不同的getArea( ) 求面积方法呢 这样的话不就容易多了!
这样的话 就只需要 组合一次对象 写一次求体积的方法就行了!!!
诶,这种 想要程序在运行时能够根据实际对象的类型动态地选择执行相应的方法 恰恰就是上转型对象呀!
我们只需要写一个让所有的新加的 底面图形的类 都作为一个 类X 的子类 然后用这个 类X 创建一个对象bottom作为其所有子类的上转型对象
然后将这个对象组合进class pillar中 每次想求一个新柱状体的体积时 就把子类对象的引用传给这个上转型对象bottom 就达到我们的目的了啊!
那么我们再来分析一下 类X中都有什么
首先最重要的就是gerArea( );方法 能够根据不同的子类对象传进来的引用执行不同的getArea( )方法 那么看来就不能在类X中写出getArea的具体实现了
getArea( )方法需要留给子类去重写,也就是说类X的getArea( )方法中不能有方法体,即在类X中需要写一个抽象方法 abstract getArea ( );
那么类X就是一个抽象类;
所以直接创建一个抽象类X(名字可以根据不同的情况任取,这里可以将类名叫作图形类 abstract class Geometry) ,在里面写一个抽象方法 getArea( ); 让 每次新加的 底面图形类A 都去作为这个抽象类的子类,在子类中重写getArea( )方法
然后直接用抽象类去声明一个对象bottom,作为其各个子类的的上转型对象,然后将其组合到class Pillar 中,每次新创建一个子类A的时候,只需要将A类对象的引用传给bottom就好了
例如
bottom = new Ciecle( );
这样每次使用时就不需要再去 在class Pillar中创建具体的 底面图形的 对象了
只需要将每次新加的类A的对象的引用传给bottom即可
在用户提出求新的柱状体的需求时 也方便维护了
类图:
像这样的设计就叫作面向抽象编程
而 面向接口编程 在机制上也非常类似于 这种面向抽象编程
只不过
这里的面向抽象编程涉及的是父类的上转型对象与其子类对象
而面向接口编程中涉及的是 接口对象 与 实现类对象
而 接口回调的机制 也类似于 抽象类的上转型对象调用其子类对象的机制
那同样的面向接口编程当然也会体现出开闭原则的思想
具体的区别和阐述会在下一篇文章中给出
有过c语言基础的读者 相信能更好地理解 因为这种通过传递对象引用来间接调用方法的机制 其实就相当于是C语言中的指针回调
(本文图片来源于 《Java2实用教程(第六版 )》微课视频 )