欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Object Oriented Programming (3)

程序员文章站 2022-04-06 15:13:25
...

本文部分内容节选自Effective C++ by Scott Meyers 和 UML面向对象设计基础 by Meilir Page-Jones。

 

3 多态
3.1 概述
    简单来说,多态(polymorphism)是具有表现多种形态的能力的特征,这使得开发语言具有根据对象的类型以不同方式进行处理的能力。多态意味着许多类可以提供同样的属性或者方法,而且调用者在调用这些属性或方法之前,不必知道某个对象是什么类型。

3.2 虚拟函数
    在了解了继承的概念之后, 如果再深入一些,你会发现它分为两类,函数接口(function interface)的继承和函数实现(function implementations)的继承。虚拟函数(virtual function)意味着“接口必须被继承下去”;纯虚拟函数(pure virtual function)则意味着“只有接口必须被继承下去”;而“非虚拟函数”则意味着“接口和实现都必须被继承下去”。
    对于一般(非纯)虚拟函数和纯虚拟函数,子类都会继承函数的接口,但不同的是,一般的虚拟函数传统上都会提供一份实现代码,子类可以选择是否加以改写(override)。所以一般(非纯)虚拟函数的目的,是为了让子类继承该函数的接口和缺省行为。但是,允许一般虚拟函数同时指定“函数声明”和“缺省行为”,有时可能会造成危险,考虑一下XYZ航空公司所设计的继承体系,该公司只有两种飞机,A型和B型,两者都以相同的方式飞行,因此继承体系这样设计:

class Airpost{…}; // 机场
class Airplane
{
public:
    virtual void fly(const Airpost& destination);
    …
}

void Airplane::fly(const Airport& destination)
{
    default code for flying
}

class ModelA : public Airplane{…};
class ModelB : public Airplane{…};

    为了表示所有飞机都必须能飞,并考虑到“不同型号的飞机原则上需要不同的fly实现代码”,Airplane::fly被声明为virtual。然而为了避免在ModelA和ModelB中撰写相同的代码,缺省的飞行行为由Airplane::fly提供,用时被ModelA和ModelB继承。这个设计突出共同性质,避免代码重复,并提供在未来进行强化的能力,延缓长期维护所需付出--这些都是正式面向对象技术如此收到欢迎的原因。假设XYZ公司利润大增,决定购买一种新的C型飞机。C型飞机和A型以及B型都不相同,更准确的说,它的飞行方式不同。XYZ的开发人员在继承体系中为C型飞机增加了一个class,但由于他们急着让新飞机上线服务,竟忘记重新定义(define)其fly函数。这会造成灾难:因为这个软件试图以ModelA或者ModelB的飞行方式来飞ModelC。
    问题并不在于Airplane::fly提供的缺省行为,而是在于ModelC在未明白说“我要使用缺省行为“的情况下继承了该缺省行为,以下是一种解决方法:

class Airplane
{
public:
    virtual void fly(const Airpost& destination) = 0;
    …
protected:
    void defaultFly(const Airport& destnation);
}

class ModelA : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {defaultFly(destination);} // inline 调用
    …
};

class ModelB : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {defaultFly(destination);} // inline 调用
    …
};

class ModelC : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    …
};
void ModelC::fly(const Airport& destination)
{
    code for flying ModelC
}

    虽然开发人员还是可能因为剪贴代码来实现ModelC而招致麻烦,但是它的确比原先的设计值得信赖。Airplane::defaultFly不是一个虚拟函数,这一点很重要。因为没有任何一个子类应该重新定义此函数。
    也有人反对这种以不同的函数分别提供接口和缺省行为,就像上述的fly和defaultFly那样,因为这样会造成class内部命名空间(naming space)的污染。但是他们也同意,接口和缺省行为应该分开,那么这个表面上看起来的矛盾该如何解决?在C++中,可以为纯虚拟函数提供定义(defination),不过调用它的惟一途径是所谓的静态调用,例如:

class Shape
{
public:
    virtual void draw() const = 0;
    virtual void error(const string& msg);
    int objectId() const;
}
class Rectangle : public Shape {…};
class Ellipse : public Shape {…};

Shape *ps = new Rectange;
ps1->Shape::draw();

    回到XYZ公司,以下这个新的设计几乎和之前的一样,但是纯虚拟函数Airplane::fly的实现取代了成员函数Airplane::defaultFly。这利用了“纯虚拟函数必须在子类中重新声明(declare),但是纯虚拟函数也可以拥有自己的定义(defination)”这个事实。但是这种设计也有个问题是,丧失了让Airplane::fly的声明和定义有不同的保护级别的机会。

class Airplane
{
public:
    virtual void fly(const Airpost& destination) = 0;
    …
}

void Airplane::fly(const Airport& destination)
{
    //default code for flying
}

class ModelA : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    {Airplane::fly(destination);} 
    …
};

class ModelB : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    { Airplane:: (destination);} 
    …
};

class ModelC : public Airplane
{
public:
    virtual void fly(const Airport& destination)
    …
};
void ModelC::fly(const Airport& destination)
{
    //code for flying ModelC
}

    有了以上知识,当你声明你的成员函数(member function)的时候,就必须谨慎选择,不要把所有的成员函数都声明非虚拟函数,这使得子类没有足够的空间进行特殊化工作。非虚拟的析构函数(destructors)尤其会带来问题。同时也不要把所有的成员函数都声明成虚拟函数(请思考Java是怎么做的?),因为对于一个继承体系而言,有些行为是不会变化的,如果你想确保“不变性”,那么就使用非虚拟函数。

相关标签: OOP