首页 > 解决方案 > 使用继承修复大型类

问题描述

在我看来,我的课太大太复杂了,我想减少它。我可以以这种方式使用继承吗,因为我创建 InitCar 类只是为了继承它并且不会显式使用此类的对象。

在重构之前。人员和许可证不是我自己的课程,我无法更改它们。

class Car
{
public:
    void Move();
    void SpeedUp();
    void SpeedDw();
    //More other

private:
    int speed = 0;
    std::string name;
    int id = 0;
    People owner;    // not my own class
    License license; // not my own class

    void InitCarFromConfig()
    {
        //Here I read the data from the file
    }

    void InitOwner()
    {
        //Here I init the People owner
    }

    void InitInspection()3
    {
        //Here I init the License license
    }


};

重构后

class InitCar
{
protected:
    std::string name;
    int id = 0;
    People owner;    // not my own class
    License license; // not my own class

    void InitCarFromConfig()
    {
        //Here I read the data from the file
    }

    void InitOwner()
    {
        //Here I init the People owner
    }

    void InitInspection()
    {
        //Here I init the License license
    }
};

class Car : InitCar
{
public:
    void Move()
    {
        InitOwner();
    }
    void SpeedUp();
    void SpeedDw();
    //More other
private:
    int speed = 0;
};

这种继承的使用是否可以接受,是否可能存在性能问题?

标签: c++inheritancedesign-patterns

解决方案


尽管确实可以使用继承来减少类的大小并减少其他类的代码重复(也可以继承InitCar类似的基本功能),但这实际上不是一个好主意。

在功能方面,它确实有效,它确实减少了代码重复和类大小。然而,这是一个糟糕的设计,因为它使用了错误的继承,并且打破了“干净代码”的概念。

当你创建一个类时,你创建了一个代表某物的实体。继承在这些实体之间创建关系。说Car继承InitCar就是说这Car是一种InitCar,这在逻辑上没有意义,因为InitCar它只是一个辅助类。

如果基本类型是一个实际的实体,例如Vehicle,并且您有多个车辆,则可以解决此问题。但是,您InitClass专门用于拆分您的代码,因此它实际上并没有生成 a Vehicle,重命名它不会修复设计。

组合优于继承

“干净代码”中的一个众所周知的概念,即最好将帮助类的功能作为变量保存在类中,而不是从基类继承。它既可以更灵活地切换实现,又不会滥用继承的目的:

class Car {
public:
    void Move();
    void SpeedUp();
    void SpeedDw();
    //More other

private:
    int speed = 0;
    std::string name;
    int id = 0;
    People owner; 
    License license;
    HelperClass helper; // new class for initing..

    void InitCarFromConfig()
    {
        //data = helper.InitCarFromConfig();
    }

    void InitOwner()
    {
        //owner = helper.InitOwnerForCar(param);
    }

    void InitInspection()
    {
        //data = helper.InitInspection(param);
    }
};

现在我们简单地将我们的调用委托给辅助类(它的名字只是一个存根,你应该有一个与它的作用相匹配的名字,或者可能是几个类)。所以我们确实节省了一些空间,我们没有滥用继承和类型,而且我们现在实际上在实现上具有灵活性,因为现在我们可以替换帮助器的实例并获得新的逻辑。

我们如何初始化助手?通常通过构造函数接收是最好的主意。但是如果你真的想的话,你可以在类中创建。

适合您的案例的最佳设计

但它是这里最好的设计吗?实际上没有。因为类中真正的问题是类中Init_方法存在。

在创建类时,重要的是当构造函数完成运行时,类被完全初始化。如果不是,我们会产生几个问题:

  • 冒着使用类的风险,并且某些属性/方法未完成使用,导致错误
  • 课程的复杂性增加了,使维护变得更加困难(这是您指出的问题)。
  • 限制类的用户在创建时的灵活性,从而限制使用类的设计选项。例如,创建测试将是一场噩梦。
  • 多线程使用的风险很大,并且更难处理

相反,我们可以做的是从构造函数接收我们需要操作的所有数据,并简单地存储它:

public:
    Car(std::string name, id, People owner, License license);

现在又来了一个问题:如果难以执行初始化怎么办。毕竟你有 3 种方法来初始化你的类,所以对用户来说可能并不容易。这就是工厂设计模式的出现。我们将创建一个名为CarFactory(或其他)的类,并使用它来创建我们的类。在其中,它将具有初始化类数据的所有逻辑:

class CarFactory {
public:
    Car* CreateCar(params_from_user) {
        // init data
        return new Car(data);
    }
};

我们用这个完成了什么:

  • 我们做得Car更小、更简单
  • 我们为用户提供了更多关于如何使用的选项Car
  • 我们保留了Init之前的选项,以帮助创建Car
  • 我们的代码更易于查看和维护,因为它在逻辑上是分开的,并且类很小
  • Car在构造函数调用后完全初始化

推荐阅读