引言
多态和封装、继承一起作为面向对象的三大特性,无论是Java还是其他面向对象的语言,相信很多人谈起多态可能都不会陌生,绝大多数都可以说出多态的很多知识,可往往在现实项目的开发过程中,这最基本的特性,由于种种原因,常常被人遗忘或者懒得使用,要知道我们编码并不是仅仅追求完成任务,假如说后期的维护升级还是要你来做的话你就会深刻感受到了,笔者最近对接了一位同事的项目深有感触,同时这也是深刻理解设计模式的必修课,所以总结下。
一、多态概述
多态和封装、继承一起作为面向对象的三大特性,无论是Java还是其他面向对象的语言,灵活使用好多态,对于提高我们的编码质量尤其重要,首先多态体现了动态绑定(dynamic binding),即在执行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法,这就提供了很大的灵活性,同时也利于消除类型之间的耦合度。
1、多态的定义
指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息即函数调用),简而言之就是同样的方法名在不同的对象中可以实现不同的功能
2、多态的条件
- 要有继承或者实现接口
- 重写,即子类重写或者覆盖父类的方法
- 子承父业,即定义父类的引用用子类来赋值初始化
3、多态的实现方式
接口实现,继承父类进行方法重写,同一个类中进行方法重载,在使用的时候使用不同的子类去初始化父类。
二、多态的应用
假如我们有以下的需求:求根据输入的值求长方形、圆形的面积,其中计算长方形面积时要求输入长和宽,计算圆形时只需要半径即可,接下来逐步分享下以多态的思想来解这个题。
首先,所谓面向对象即把一切看成对象,很明显长方形、圆各自可以抽象成为一个Java对象,然后输入值和计算面积可以抽象为方法,首先把他们共性的功能抽象为接口
public interface IShape {
void input();
float getArea();
}
此时我们已经定义了个形状接口,一般来说我们只要分别定义长方形类和圆形类并实现这个IShape接口即可,但是封装性还是不高,因为无论是求哪个形状的面积都得先输入再计算,这是一个流程,于是封装了一个流程类
public class ShapeProcess {
private IShape ishape;
public ShapeProcess(IShape shape){
this.ishape=shape;
}
public float computeArea(){
ishape.input();
return ishape.getArea();
}
}
如此就构建了一个小框架模型,其他业务也可以直接套用。
套用这个模型来解决我们前面的那个问题,只需要根据不同形状实现IShape接口,
public class Rectangle implements IShape {
private float width;
private float height;
public float getWidth() {
return width;
}
public void setWidth(float width) {
this.width = width;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
@Override
public void input() {
System.out.println("请一次输入长、宽:");
Scanner s=new Scanner(System.in);
setHeight(s.nextFloat());
setWidth(s.nextFloat());
s.close();
}
@Override
public float getArea() {
return width*height;
}
}
public class Circle implements IShape {
private float radius;
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
@Override
public void input() {
System.out.println("请输入半径:");
Scanner s=new Scanner(System.in);
setRadius(s.nextFloat());
s.close();
}
@Override
public float getArea() {
return (float) (Math.PI*radius*radius);
}
}
然后调用ShapeProcess 的computeArea计算面积即可。
public class TestDuotai {
public static void getRectangleArea(){
IShape shape=new Rectangle();
ShapeProcess process=new ShapeProcess(shape);
System.out.println("长方形面积"+process.computeArea());
}
public static void getCircleArea(){
IShape shape=new Circle();
ShapeProcess process=new ShapeProcess(shape);
process.computeArea();
System.out.println("圆形面积"+process.computeArea());
}
}
这样设计的好处在于代码的源头来自IShape接口,而ShapeProcess控制了流程,无论是计算什么形状的面积,我们要做的只是需要实现IShape接口定义具体的形状类即可,好好想下是不是很多地方都是可以套用的,但是扩展性还欠缺火候。
过了一段时间,客户突然要求要计算周长
一般想法重新定义IShape接口,增加一个获取周长的方法,这样做的后果就是前面的实现模块程序都需要修改重新编译,这明显扩展性不好,造成这样后果的根本原因是父类、子类定义的多态方法耦合度过高。,而柔性多态可以降低耦合,实现也很简单,重新设计下IShape2接口
public interface IShape2 {
/**
* 业务多态方法,根据tag 来执行不同的分支方法
* @param tag 业务标号
* @return
*/
public Object compute(int tag);
}
对应的Circle2
public class Circle2 implements IShape2 {
private float radius;
public Circle2(float radius) {
this.radius = radius;
}
@Override
public Object compute(int tag) {
Object result=null;
switch(tag){
case 0:
getArea();//计算面积
break;
case 1:
getPerimeter();//计算周长
break;
default:break;
}
return result;
}
//非多态方法
Object getArea(){
float area=(float) (Math.PI*radius*radius);
return new Float(area);
}
//非多态方法
Object getPerimeter(){
float area=(float) (Math.PI*radius*2);
return new Float(area);
}
}
简单计算圆的面积和周长
public static void getCircle2Area(){
IShape2 shape2=new Circle2(8.0F);
Float result=(Float) shape2.compute(0);
System.out.println("圆形面积:"+result);
}
public static void getCircle2Peimeter(){
IShape2 shape2=new Circle2(8.0F);
Float result=(Float) shape2.compute(1);
System.out.println("圆形周长:"+result);
}
所谓的柔性思想其实很简单,就是接口定义多态方法与各子类的普通具体方法之间的关系是间接的,而非直接的,这就消弱了父类与子类多态方法的强关联,简而言之,子类通过重写多态派发方法。
显而易见通过tag来完成相应的功能,但是所传递的tag似乎不受控制,更完善一点的话接口可以设计为
public interface IShape3 {
/**
* 业务多态方法,根据tag 来执行不同的分支方法
* @param tag 业务标号
* @return
*/
public Object compute(int tag);
public int getBizTag(String bizName);//通过业务名获取对应的tag编号
}
完善后的Circle3为
public class Circle3 implements IShape3 {
private static List<String> bizList=new ArrayList();
static{
bizList.add("getArea");
bizList.add("getPerimeter");
}
private float radius;
public Circle3(float radius) {
this.radius = radius;
}
@Override
public int getBizTag(String bizName) {
return bizList.indexOf(bizName);
}
@Override
public Object compute(int tag) {
Object result=null;
switch(tag){
case 0:
getArea();//计算面积
break;
case 1:
getPerimeter();//计算周长
break;
default:break;
}
return result;
}
//非多态方法
Object getArea(){
float area=(float) (Math.PI*radius*radius);
return new Float(area);
}
//非多态方法
Object getPerimeter(){
float area=(float) (Math.PI*radius*2);
return new Float(area);
}
}
完善后的简单应用
public static void getCircle3Area(){
IShape3 shape3=new Circle3(8.0F);
int tag=shape3.getBizTag("getArea");//如果需要计算周长则把计算周长的方法名穿过去就可以了
Float result=(Float) shape3.compute(tag);
System.out.println("圆形面积:"+result);
}
这样设计的好处就是当有新需求的时候我们不需要修改接口模块,只需要去在具体类里添加一个case即可,每种模式都不是完美的,程序开发不宜盲目迷信去套用模式,至于如何设计应该结合实际,这篇文章只是分享多态的思想。