为复杂对象初始化选择哪种设计



假设我有一个封装一个(或多个(成员的类,必须以某种方式初始化该成员,并且没有它就没有合理的方法来使用该类(所以我不想让它成为可选的(。那么最好像这样在其构造函数中运行初始化

class MyClass
{
    MyClass()
    {
        if(!obj.initialize()
            throw ...;
    }
private:
    MyObject obj;
}

或者你会建议以下设计:

class MyClass
{
    MyClass()
    {
    }
    bool initialize()
    {
        return obj.initialize();
    }
private:
    MyObject obj;
}

第一个看起来很有吸引力,因为我可以保证在构造函数运行后满足使用我的类的所有要求,并且我可以通过抛出异常来报告任何错误。

第二个看起来不错,因为它不会用直观上不属于那里的东西重载构造函数,特别是一旦初始化例程变得复杂,即创建小部件、打开数据库连接、初始化第三方库等。在我正在使用的许多遗留代码中,ctor 充斥着参数和初始化内容,可能在完成这种类型的臃肿对象构造之前运行数千行代码。尝试将它们重构为更干净的东西在某些时候真的很难,因为涉及的依赖项太多了。这就是我提出这个问题的动机。

可以看到的设计#2的最大缺点是我需要一个公共初始化例程,客户端必须记住调用该例程。但是由于这可能会被遗忘,我必须跟踪和检查所有公共成员的初始状态,并以某种方式处理错误(可能断言就可以了(。如果我选择设计#1,这也会使我的课堂上出现不存在的东西。

那么我最好的选择是什么?

"第一个看起来很有吸引力,因为我可以保证在构造函数运行后满足使用我的类的所有要求......">

必须如此,否则设计很糟糕。

当构造函数完成时,对象必须可用,没有任何未定义的行为,并且符合其接口规范。

这并不意味着需要为给定目的配置对象

我喜欢将初始化配置分开。

例如,查看std::fstream .您可以在不打开任何文件的情况下创建完全初始化的 fstream 对象

std::fstream fs; // initialized but not configured

它不会表现出未定义的行为,并将根据其接口规范运行。

因此,您可以使用其界面将其配置为给定目的 - 例如读取特定文件:

fs.open("myfile.txt", std::ios::in); // configured

默认构造函数应执行绝对最小操作,以使对象进入工作顺序,而不必将其配置为给定任务。

话虽如此,没有理由不使用其他构造函数来简化创建配置的对象:

std::fstream fs("myfile.txt", std::ios::in); // initialized & configured

将代码从 MyObject::initialize 移动到 MyObject 的构造函数(如有必要,也可以使用 throw(。现在,MyClass的隐式定义的默认构造函数将完成正确的工作。

最新更新