zoukankan      html  css  js  c++  java
  • 【M33】将非尾端类设计为抽象类

    1、考虑下面的需求,软件处理动物,Cat与Dog需要特殊处理,因此,设计Cat和Dog继承Animal。Animal有copy赋值(不是虚方法),Cat和Dog也有copy赋值。考虑下面的情况:
    Cat cat1;
    Cat cat2;
    Animal *a1 = &cat1;
    Animal *a2 = &cat2;
    *a1 = *a2;
    思考*a1 = *a2会有什么问题? copy赋值不是虚方法,根据表面类型,调用Animal的copy赋值,这就导致所谓的部分赋值,cat2的Animal成分赋值给cat1的Animal成分,二者的Cat成分保持不变。

    2、怎么解决上面的问题?
    将Animal的copy赋值声明为virtual方法,如下:
    virtual Animal& operator=(const Animal &rhs);
    Cat和Dog重写:
    virtual Cat& operator=(const Animal &rhs);
    virtual Dog& operator=(const Animal &rhs);
    这里使用了C++语言后期的一个特性,即协变,返回的引用更加具体。但是,对于形参表,重写必须保证保持一致。将copy赋值声明为virtual,解决了部分赋值的问题。但是,引入了一个新的问题。如下:
    Cat cat;
    Dog dog;
    Animal* a1 = &cat;
    Animal* a2 = &dog;
    *a1 = *a2;
    这是异型赋值,左边是Cat,右边是Dog。C++是强类型语言,一般情况下,异型赋值不合法,不会造成问题。但是,这种情况下导致异型赋值合法。对于指针解引用的情况,我们期望同型赋值是合法的,异型赋值是非法的。容易想到的办法是,在重写的copy赋值中,使用dynamic_cast进行同型判断。比如Cat的copy赋值,首先判断rhs是不是Cat,如果是,就赋值,如果不是,抛出异常。
    我们知道,使用dynamic_cast效率低,考虑下面的情况,cat1 = cat2; 即使cat1与cat2的表面类型就是Cat,也会调用Cat& operator=(const Animal &rhs),进行一次dynamic_cast的运算,这不是我们所期望的。解决办法是:增加一个过载方法,编译器编译时,根据表面类型确定方法的调用。如下:Cat& operator=(const Cat &rhs)。同时对于重写的方法,可以调用前面的方法,如下:
    Cat& operator=(const Animal &rhs)
    {
    return operator=(dynamic_cast<Cat&>(rhs));
    }

    3、运行期的类型检查,dynamic_cast的使用应该尽量避免。因为,首先效率低,其次,有些编译器还不支持dynamic_cast,不具有移植性。有没有更好的办法?
    导致问题的原因是,对于指针解引用的赋值,父类的copy赋值不是虚方法,导致部分赋值。
    因此,解决办法是,提取一个抽象类AbstractAnimal,将copy赋值声明为protected,子类可以调用,表面类型是抽象类的指针解引用赋值,不能调用。增加一个Animal类,继承AbstractAnimal。
    对于抽象类,内部至少要有一个纯虚方法,很自然地将析构方法声明为纯虚方法。对于纯虚方法,需要注意:
    a、纯虚方法意味着当前类为抽象类,不能实例化。
    b、纯虚方法要求子类必须重写。
    c、特别注意,纯虚方法一般不提供实现,但是允许提供实现,子类也可以调用。如果析构方法为纯虚方法,必须要提供实现。因为子类调用自身的析构方法后,必定会去调用父类的析构方法。

    4、考虑,具体基类没有字段,是不是就不需要上述的抽象类了?这有两个问题,首先现在没有字段,以后可能会有字段,其次如果一个类没有字段,一开始就应该是一个抽象类。

    5、结论,对于继承体系中的非尾端类,应该设计为抽象类,如果使用外界的程序库,需要做一下变通。

  • 相关阅读:
    Linux下nginx 的常用命令
    Mybatis generator 自动生成代码(2)
    Android Retrofit2 网路编程
    Android webView输出自定义网页
    Android Studio OkHttpClient使用
    Android Studio SVN使用
    Android Toolbar的使用 顶部标题栏+后退键
    Android DrawLayout + ListView 的使用(一)
    RabbitMQ配置与安装
    Struts2拦截器
  • 原文地址:https://www.cnblogs.com/nzbbody/p/3640315.html
Copyright © 2011-2022 走看看