zoukankan      html  css  js  c++  java
  • 六、继承与面向对象设计--条款35-37

    条款35:考虑virtual函数以外的其他选择

    在这个条款里面讨论virtual函数的替代方案。文中主要探讨了两种方式——NVI手法(Template Method模式)和Strategy模式。

    一、NVI手法

    NVI即Non-Virtual Interface。是Template Method设计模式中特定的一种,算法骨架来自于基类,具体的实现是在子类中实现。

    在某个流派中,他们建议virtual函数几乎总是private的,然后使用一个non-virtual的member函数来调用virtual函数。

    class GameCharacter
    {
    public:
        int healthValue() const
        {
            ... // 事前工作
            int retVal = doHealthValue();
            ... // 事后工作
        }
    private:
        virtual int doHealthValue()
        {
            ...
        }
    };
    

    作者把上述的那个non-virtual函数——healthValue称为virtual函数的外覆器

    NVI手法的一个优点在于在调用外覆器之前设定好适当的场景,调用之后清理场景。 个人认为,在这个过程中要注意异常的抛出,要处理好异常。否则,事前如果锁定互斥锁,接着执行virtual函数的时候抛出了异常,那么这个锁要怎么释放呢?

    二、古典的Strategy模式

    采用函数指针作为计算健康值的计算方法。不作为member函数。

    UML图类似如下:

    在这个例子的大致做法就是,给定一个non-member的计算健康值的函数,作为Character的构造函数参数,然后使用这个函数去计算健康值。

    因为这边涉及到设计模式的方法,所以没有很细致的记录下来,会在后面学习设计模式的博客中分析设计模式。现在,让我们来总结一下替代方案:

    • NVI手法。 以public non-virtual成员函数包裹较低访问性(private or protected)的virtual函数。
    • 将virtual函数替换为“函数指针成员变量”。这是Strategy模式的一种分解表现形式。
    • 以std::tr1::function替换virtual函数。 这也是Strategy模式的一种分解形式。
    • 将继承体系中的virtual函数替换为另一个继承体系内的virtual函数。 这是Strategy模式的传统实现手法。

    作者总结

    virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。

    将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。

    tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式兼容”的所有可调用物。

    条款36:绝不重新定义继承而来的non-virtual函数

    这一条款其实很简单地就能陈述其原因。先肯定地明确一点:non-virtual函数的不变性凌驾于特异性。 继承一个non-virtual函数,使用public继承就是想要继承一个接口和强制性实现,所以不变性凌驾于特异性。

    然后我们再看一眼适合讲述的例子:

    class B
    {
    public:
        void mf();
    };
    class D : pubic B
    {
    public:
        void mf();
    };
    

    D以public形式继承了B,且重写了一份non-virtual的mf函数。那么B中的non-virtual就会被覆盖。考虑以下执行:

    D x;
    B *pB = &x;
    D *pD = &x;
    pB->mf();   // 调用B::mf
    pD->mf();   // 调用D::mf
    

    现在,同一个x,调用的肯定会是不同的mf函数实现。这是因为non-virtual函数都是静态绑定的,不是动态绑定,他们会调用各自指针指向类的成员函数。

    所以,如果真要有一份不同的mf函数的实现,那么我们就应该考虑其声明为virtual函数。

    作者总结

    绝对不要重新定义继承而来的non-virtual函数。

    条款37:绝不重新定义继承而来的缺省参数值

    一、原因

    virtual函数是动态绑定(dynamically bound)的,缺省参数值却是静态绑定的(statically bound)。

    二、从代码中看到错误

    #include <iostream>
    
    using namespace std;
    
    class Shape
    {
    public:
    	enum ShapeColor
    	{
    		Red,
    		Green,
    		Blue
    	};
    	virtual void draw(ShapeColor color = Red) = 0;  // 默认参数是Red
    	inline void ShowColor(ShapeColor color)
    	{
    		if (color == 0)
    		{
    			cout << "Red" << endl;
    		}
    		else if (color == 1)
    		{
    			cout << "Green" << endl;
    		}
    		else if (color == 2)
    		{
    			cout << "Blue" << endl;
    		}
    		else
    		{
    			cout << "invalid color" << endl;
    		}
    	}
    };
    class Rectangle : public Shape
    {
    public:
    	virtual void draw(ShapeColor color = Green) 	// 默认参数是Green
    	{
    		cout << "default parameter : Green, But the real parameter:";
    		ShowColor(color);
    	}
    };
    class Circle : public Shape
    {
    public:
    	virtual void draw(ShapeColor color = Blue) 	// 默认参数是Blue
    	{
    		cout << "default parameter : Blue , But the real parameter:";
    		ShowColor(color);
    	}
    };
    class Triangle : public Shape
    {
    public:
    	virtual void draw(ShapeColor color) 	// 默认参数是Blue
    	{
    		cout << "No default parameter" << endl;
    		ShowColor(color);
    	}
    };
    
    int main()
    {
    	Shape *pR = new Rectangle;
    	Shape *pC = new Circle;
    	Shape *pT = new Triangle;
    
    	pR->draw();
    	pC->draw();
    	pT->draw(Shape::Green);
    	return 0;
    }
    

    七十来行的代码,讲一下重点:

    (1) 抽象基类中有一个纯虚函数draw,缺省参数值为Red

    (2) Rectangle重写draw,默认参数值为Green。

    (3) Circle的重写draw,默认参数值为Blue。

    (4) Triangle重写draw,没有默认参数值。

    (5) 接下来我们用一个Shape类的指针,分别指向三个继承类。

    (6) 然后分别调用他们的draw函数。

    讲道理的话,pR调用的应该是自己的draw函数,默认参数为Green,同理,pC的默认参数为Blue。事实上的执行结果:

    三、缺省参数值是静态绑定的

    以上的例子已经完美说明了,virtual是动态绑定的,而缺省参数值却是静态绑定的。

    • 因为virtual是动态绑定的,所以指向derived classes的base指针会寻找到正确的virtual函数去执行。
    • 因为默认参数值是静态绑定的,所以使用的缺省参数值还是base类的缺省参数Red。

    四、为何支持这种看似错误的方式?

    如果编译器没有给我们这样的默认参数一个错误或者警告提示,为何不像我们想的那样,将缺省参数值也进行动态绑定呢?

    为了运行期效率。 如果编译器要执行某种方法将运行期的virtual的函数决定适当的缺省参数值,比现在这种“在编译器决定”的机制更加复杂且更加慢。

    作者总结

    绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定的。

  • 相关阅读:
    基础才是重中之重~B/S系统中的事件订阅
    将不确定变为确实~请自己搞清楚异常页面的设置方法(网上那些资料说的基本都有问题!)
    基础才是重中之重~延迟初始化
    批量替换sqlserver数据库TEXT字段类型的数据
    12554 A Special "Happy Birthday" Song!!!
    Linux socket 网络编程常用函数总结
    新浪微博Python SDK笔记——发微博(一)
    在Ubuntu上下载、编译和安装Android 4.2 最新内核源代码(Linux Kernel)
    20、SQL Server 数据修改之Update
    19、SQL Server 数据修改之Insert into
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/9699371.html
Copyright © 2011-2022 走看看