为什么要用多态

多态是OOP(面向对象编程)的一项重要技术。

任何技术都不是凭空想出的,他都是为了解决一定实际问题的。多态其实是程序设计的一种方法学,他不仅能让程序设计更加易实现,也更容易维护和扩展。程序的初学者大多很难在一开始就理解这些技术难点。

下面看一个应用场景,一个软件要实现下面功能:用户在界面上添加各种几何图形(圆,三角形和正方形),用户可以以任意顺序、任意数量(当然内存不要溢出即可)添加上述几何图形,当用户点击“计算面积”按钮时,计算并显示所有添加图形的面积数值。

这里要注意的是虽然都是二维图形,但是圆,三角形和正方形计算面积的方法迥异

我们实现下面三个类(以c#为例)

public class Circle
{

double r;
//...
//...

 public double ComputeArea()
{
    return 2*Math.PI*r;
}


}



public class Triangle
{

double a,b,angle;
//...
//...

 public double ComputeArea()
{
    return a*b*Math.Sin(angle);
}


}




public class Square
{

double a;
//...
//...

 public double ComputeArea()
{
    return a*a;
}


}

为了能在当用户需要的时候(用户点击了“计算面积”按钮)计算面积,你可以在用户选择创建对应几何图形时计算出相应的面积,用户每创建一个图形,你就预先计算出它的面积,并且把所有计算的结果存放到一个List<double>(或者c++的Vector中)——你不能存储到一个数组中,因为用户创建的几何图形数量是随机的,可能只创建1个也可能创建十万个,而数组并不是应对这种场合的好工具。

嗯,看起来是一种不错的方案。

再添加一个功能需求:当用户点击“清空”按钮的时候,清空所有图形数据。

对于上述方案来讲,当用户点击“清空”的时候,你只要清空你的List列表即可——虽然前期的计算和存储都是白费(比如用户创建了100个图形,在创建第101个时,因为某种原因需要清空所有已创建的图形,那么按照上面的方法你的List不仅白费了,而且含浪费了100次的计算资源)。假如这个软件不仅需要计算面积,还需要计算周长、中心点、参数方程、描绘方法等等需要处理才能得到的信息,难道这些信息你都要提前偷偷的计算出?万一用户点击了清空按钮呢——你用了很多资源结果却是徒劳的,而且后期处理也会更复杂,比如清理更多的列表List;显然这不是好的解决方法。

如果我们“真正”的在用户需要的时候才进行面积计算,而不是像上面偷偷的提前计算。该怎么办呢:

当用户点击创建的时候,我们只创建其基本信息,比如半径,边长,夹角等基本信息。当用户点击计算面积按钮的时候,我们才进行相应计算。

这时候就出现一个问题:我们怎么存储用户已创建的图形呢,假如同样用List来存储,那么这个List的类型是什么?Circle类型?Triangle类型?还是Sqaure类型?显然哪一种都不合适——你不能往List<Circle>中添加Triangle类型——当然你仍然可以创建三个List:List<Circle>,List<Triangle>,List<Square>;当你这样创建的时候,你还需要其他的变量来记录用户创建的顺序——这时你在给自己“养虎遗患”——假如几何图形增加到10种呢,难道你创建10个不同的List???更多图形种类扩展呢?

正确的做法,创建一个基类GeometeryGraph,剩下的各个图形类继承于它,像这样:

public class GeometeryGraph
{
    
}




public class Circle:GeometeryGraph
{

double r;
//...
//...

 public double ComputeArea()
{
    return 2*Math.PI*r;
}


}



public class Triangle:GeometeryGraph
{

double a,b,angle;
//...
//...

 public double ComputeArea()
{
    return a*b*Math.Sin(angle);
}


}




public class Square:GeometeryGraph
{

double a;
//...
//...

 public double ComputeArea()
{
    return a*a;
}


}

这样你只需要创建一个List<GeometryGraph>  graphSet即可,因为任何图形类都可以往里面放,其实这是多态的一个好处:基类可以存放子类(协变)。当用户创建圆时,你后台的代码只需要这样即可:graphSet.Add(new Circle());

是不是简洁不少。

但是问题又来了——用户点击计算面积按钮要计算面积时,我怎么知道List<GeometryGraph>  graphSet 中取出来的元素到底是Circle还是Triangle还是其他?从目前的类设计来看,好像的确不能解决这个问题,但是很快你就会有一个策略:给每个图形类添加一个Type字段(可以是枚举型)。如下:

public class GeometeryGraph
{
    int type;
    public GeometeryGraph(int t)
    {
        type=t;
    }
     public virtual double ComputeArea()
        {
            return -1.0;
        }
}




public class Circle:GeometeryGraph
{

double r;
//...
//...
int type=1;
public Circle():base(1)
{
}
 public override double ComputeArea()
{
    return 2*Math.PI*r;
}


}



public class Triangle:GeometeryGraph
{

double a,b,angle;
int type=2;
//...
//...
public Triangle():base(2)
{
}
 public override double ComputeArea()
{
    return a*b*Math.Sin(angle);
}


}




public class Square:GeometeryGraph
{

double a;
//...
//...
int type=3;
public Square():base(3)
{
}
 public override double ComputeArea()
{
    return a*a;
}


}

这样每从List<GeometryGraph>  graphSet中取出一个元素,我们就检查他的type是几,然后根据这个type值写一个switch语句来进行相应的子类转换并进一步计算面积。好像没什么坏处。

没有什么坏处吗.....?

1.你要写很多switch语句,每个不同的计算请求(求周长,中心点等)你都要有对应的switch语句

2.每添加一个图形子类,你就要动好多代码:每处switch都要动——此为难维护且不易扩展(扩展了一个子类,却修改添加了很多地方的代码,破坏了历史测试的完备性)。对于一些实际软件,这是耦合(关联性)强的表现,有可能牵一发而动全身。我们希望的结果是:你只要添加一个子类代码,程序中其他的代码都尽可能少动或者不动。假如这里添加了一个梯形子类,那么除了编写梯形子类本身的代码外,程序中其他地方的代码都不要动,即便是那些又臭又长的switch语句。显然目前我们设计的类无法做到这一点。

这里提示一下:OOP程序设计有许多方法学的问题,不管是本着易维护的目的还是易扩展的目的,始终离不开一个基本原则:那就是类内高内聚,类间低耦合

是时候看看多态是怎么展现真正技术来解决这个问题的时候了:

语法上就不提了:基类函数virtual化,继承的子类对应的重写该方法。如下

public class GeometeryGraph
{
     public virtual double ComputeArea()
        {
            return -1.0;
        }
}




public class Circle:GeometeryGraph
{

double r;
//...
//...
int type=0;
 public override double ComputeArea()
{
    return 2*Math.PI*r;
}


}



public class Triangle:GeometeryGraph
{

double a,b,angle;
int type=1;
//...
//...

 public override double ComputeArea()
{
    return a*b*Math.Sin(angle);
}


}




public class Square:GeometeryGraph
{

double a;
//...
//...
int type=2;
 public override double ComputeArea()
{
    return a*a;
}


}

这样每当你从List<GeometryGraph>  graphSet 中取出一个元素时,如GeometryGraph  graphtmp=graphSet[i],你只需要简单的写下面的代码即可计算出实际对应子类图形的面积

double area=graphtmp.ComputeArea();

你不需要知道此时graphtmp具体是Circle类还是Triangle还是Square,运行时会自动动态联编,它自动知道这个graphtmp具体是哪个子类并调用它的ComputeArea()方法。有什么好处,一个最直接的好处是你可以再添加100个图形类,而在计算面积时仍然还是这行代码——计算面积按钮的回调处理过程代码不需要做任何修改!其实任何其他的地方的代码都不需要修改!计算周长的代码不需要修改!测试人员只针对你新增加的子类的重载函数进行测试即可。

往更远了说,继承和多态是整个现代软件设计方法学的基础,是各类设计模式的基础,任何设计模式和架构本质仍然是OOP多态和继承的高级运用

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值