类在继承中的复用[转载]

类在继承中的复用
面向对象编程的一个重要特征就是类的继承,通过 类的继承,子类可以获得
父类定义的公有功能特性。但 是实现复用的方法却有多种,通常利用类的继承来实现
功能复用的方法有三种:
直接继承父类的方法实现复用;
调用父类的方法构成自己的方法实现复用;
通过抽象方法来实现逻辑关系的复用;
这三种方法的根本原理是一致的,也就是利用类对象存在公有特性的特征,第 1
2 种方法是抽象出公有的功能特性,而第 3 种要更高级一些,它是抽象出公
有的逻辑关系特性。我们在实现和利用复用的时候,不管采用那种方法,都应该
遵循一个原则,叫做类的封闭性原则,这个原则有两个方面的含义:
完备性 : 一个类要完成一个独立的业务过程,该类的内部应该定义了这个业务
的整个过程,尽量不要在这个类定义了一些过程,而在另一个类中又定义了另外
一些过程。
透明性 : 一个类要提供一项功能给其他的类复用,该项功能对其使用者是透
明的,不但在实现上是透明的,而且在使用上也应是透明的。
前2种方式都比较简单,这边针对的种方式给个不错的例子
通过抽象方法来实现逻辑关系的复用能够较好地遵循类的封闭性原则。对车辆这个类做分析,可以发现任何车辆要行驶,都是由驱动带动轮子进行滚动向前行驶的,因此驱动带动轮子向前行驶是所有车辆公有的逻辑关系,因此比较好的方式是类在继承中能够继承这种共有的逻辑关系,而不是简单的轮子滚动的功能。这样我们可以定义一个车辆的抽象类,在车辆抽象类中定义了车辆行驶的方法,但是不同类型的车辆的行驶是不一样的(2轮或4轮回N轮驱动==),所以我们可以定义了两个方法,一个是公有的轮子滚动的方法,它实现了所有车辆的轮子滚动功能,另一个是驱动的抽象方法,它在车辆抽象类并不实现,因为抽象的车辆并不知道是哪种驱动。而在行驶方法中,先调驱动方法,然后调用滚动方法,这样就实现了前面所说的车辆行驶的逻辑关系。
代码如下:
public class abstract Vechile
{
private void Roll
{
/ / 实现滚动功能
......
}
/ /
抽象的驱动方法
protected abstract void Drive();
/ / 车辆的行驶逻辑方法
public void Go()
{
// 驱动
Drive();
// 滚动
Roll();
}
}
这样,在它的子类当中,就直接继承了行驶的方法,
但是在子类中必须实现驱动的方法,下面是汽车子类的定义:
public class Car extends Vechile
{
//
实现父类的抽象的驱动方法
public void Drive()
{
// 具体特有的驱动功能代码
.......
}
}
这样在汽车子类中,只需要关心它自己驱动功能的实现,而车辆是如何行驶,轮子是如何滚动都在车辆父
类中定义好了的,子类继承了这种逻辑关系,在子类中在这种复用方法当中,车辆行驶的业务全过程是在
车辆类中进行了完整的定义,要使用父类的公有功能轮子滚动方法对于子类是完全透明,在子类当中根本不会
出现对滚动方法的调用,轮子滚动的方法已经是父类的私有方法了。
如何调用这些功能呢?车辆类是一个抽象类,
是不能实例化,实际当中也是这样,并不存在一台抽象的车。但是我们在行驶车辆时,其实关注的是它的行驶功能,
因此我们可通过抽象的车辆类来调用具体子类车辆实例
Vechile vechiletest = new Car();
{
/ / 调用车辆行驶功能
vechiletest.Go;
.....
}
在调用过程中我们可以发现,使用抽象的父类定义了类变量,创建的的确是子类的实例,后面都是通过父
类的类变量进行调用。这样做有一个好处,如果车辆类型要改变了,只要把创建的实例改一下,不会影响后面
的调用代码。
在使用者的眼中,只有车辆类,具体如何行驶是由背后的具体实例来完成,这一点对于使用者是
透明的。
顺便分析一下功能的执行过程,首先调用父类的行驶方法,在父类行驶方法的执行中,发现驱动方法
是抽象方法,就会寻找该抽象方法的实现,这样就调用了子类驱动的实现方法,之后,再调用父类的轮子滚动
方法。这样就完成了整个行驶过程的调用。对于调用者来说,只有车辆的行驶方法。就好比坐着车子行驶,不
必知道车子是如何驱动的,轮子是如何滚动。
Arty:
面向对象中,我们一般通过对象的继承和合成来实现复用.
合成关系分为聚合关系和组合关系.聚合是整体/部分关系的抽象(has-a关系).组合关系是一种包含关系("contains-a"关系)部分和整体的生命周期是一样的.一个组合而成的新对象完全拥有对其组成部分的支配权,包括它们的创建和销毁等.
通过合成实现的复用,新对象的存取组成对象的唯一方法是通过组成对象的接口;有利于封装,因为新对象看不到组成对象的内部细节;依赖较少,不像继承中,派生类得依赖基类;这种复用可以在运行期内动态进行,新对象可以动态地引用与组成对象类型相同地对象.不过,这种复用造成系统需要管理的对象较多的局面.
我们在开发时应该优先考虑合成复用,而不是继承复用.
Timmy:
.Net 中还有一个比较好用的关键字virtual:

virtual 关键字用于修改方法或属性的声明,在这种情况下,方法或属性被称作虚拟成员。虚拟成员的实现可由派生类中的重写成员更改。
调用虚方法时,将为重写成员检查该对象的运行时类型。将调用大部分派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。

示例
在该示例中,Dimensions 类包含 x 和 y 两个坐标和 Area() 虚方法。不同的形状类,如 Circle、Cylinder 和 Sphere 继承 Dimensions 类,并为每个图形计算表面积。每个派生类都有各自的 Area() 重写实现。根据与此方法关联的对象,通过调用正确的 Area() 实现,该程序为每个图形计算并显示正确的面积。
// cs_virtual_keyword.cs
// Virtual and override
using System;
class TestClass
{
public class Dimensions
{
public const double pi = Math.PI;
protected double x, y;
public Dimensions()
{
}
public Dimensions (double x, double y)
{
this.x = x;
this.y = y;
}
public virtual double Area()
{
return x*y;
}
}
public class Circle: Dimensions
{
public Circle(double r): base(r, 0)
{
}
public override double Area()
{
return pi * x * x;
}
}
class Sphere: Dimensions
{
public Sphere(double r): base(r, 0)
{
}
public override double Area()
{
return 4 * pi * x * x;
}
}
class Cylinder: Dimensions
{
public Cylinder(double r, double h): base(r, h)
{
}
public override double Area()
{
return 2*pi*x*x + 2*pi*x*y;
}
}
public static void Main()
{
double r = 3.0, h = 5.0;
Dimensions c = new Circle(r);
Dimensions s = new Sphere(r);
Dimensions l = new Cylinder(r, h);
// Display results:
Console.WriteLine("Area of Circle = {0:F2}", c.Area());
Console.WriteLine("Area of Sphere = {0:F2}", s.Area());
Console.WriteLine("Area of Cylinder = {0:F2}", l.Area());
}
}
输出
Area of Circle = 28.27
Area of Sphere = 113.10
Area of Cylinder = 150.80
这个应该也可以算作继承中的复用吧
Justin:
这里我还想谈谈关于构造函数的继承,子类的构造函数在默认的情况下会继承父类的无参构造函数,这一点一定要注意。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值