【问题标题】:Fix a large class using inheritance使用继承修复大型类
【发布时间】:2020-09-02 01:09:55
【问题描述】:

在我看来,我的班级太大太复杂,我想减少它。考虑到我创建 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++ inheritance design-patterns


    【解决方案1】:

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

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

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

    如果基本类型是一个实际实体,例如Vehicle,并且您有多个车辆,您可以解决此问题。但是,您的 InitClass 专门用于拆分您的代码,因此它实际上不会生成 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 在构造函数调用后完全初始化

    【讨论】:

      猜你喜欢
      • 2022-10-07
      • 2015-02-16
      • 2020-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-17
      • 1970-01-01
      • 2011-01-27
      相关资源
      最近更新 更多