OOP编码风格:初始化构造函数上的所有内容吗?


14

我仍然认为自己是一个学徒程序员,所以我一直在寻找学习典型编程的“更好”方法。今天,我的同事认为我的编码风格做了一些不必要的工作,我想听听别人的意见。通常,当我使用OOP语言(通常是C ++或Python)设计类时,会将初始化分为两个不同的部分:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(或者,相当于python)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

您对这种方法有何看法?我应该避免拆分初始化过程吗?这个问题不仅限于C ++和Python,还感谢其他语言的回答。





您为什么通常这样做?习惯?您是否曾经给出过这样做的理由?
JeffO '16

@JeffO在使用MFC库制作GUI时,我养成了这种习惯。大多数与UI相关的类,例如CApp,CWindow,CDlg等,都具有您可以覆盖的OnInit()函数,该函数可响应其对应的消息。
Caladbolgll

Answers:


28

尽管有时会出现问题,但是初始化构造函数中的所有内容有很多好处:

  1. 如果发生错误,则应尽快发生并且最容易诊断。例如,如果null是无效的参数值,请在构造函数中测试并失败。
  2. 该对象始终处于有效状态。同事不会犯错,忘记打电话initMyClass1()是因为那里不存在“最便宜,最快和最可靠的组件是那些不存在的组件。”
  3. 如果有道理,可以使对象成为不可变的,这有很多优点。

2

考虑一下您要为用户提供的抽象。

为什么将可以一击完成的事情一分为二?

对于使用您的API的程序员来说,额外的初始化只是一件额外的事情,如果他们做得不好,还会提供更多出错的地方,但是对于这笔额外负担,他们有什么价值呢?

您想要提供简单,易用,难以出错的抽象抽象。编程足够困难,没有多余的事情要记住/跳跃。您希望您的API用户(即使只是您使用自己的API)也能成功


1

初始化除大数据区域以外的所有内容。静态分析工具将标记未在构造函数中初始化的字段。但是,最有效/最安全的方法是使所有成员变量都具有默认构造函数,并仅显式初始化仅需要非默认初始化的成员变量。


0

在某些情况下,对象具有大量初始化,可以分为两类:

  1. 不可更改或不需要重置的属性。

  2. 在完成工作后,可能需要根据某些条件恢复为原始值(或模板化值)的属性,这是一种软重置。例如连接池中的连接。

在这里,初始化的第二部分保存在单独的函数中,例如InitialiseObject(),可以在ctor中调用。

如果需要进行软复位,则以后可以调用相同的函数,而不必丢弃并重新创建对象。


0

正如其他人所说,在构造函数中初始化通常是一个好主意。

但是,在某些情况下有不适用的理由可能适用也可能不适用。

错误处理

在许多语言中,在构造函数中发出错误信号的唯一方法是引发异常。

如果初始化有引发错误的合理机会(例如,涉及IO或其参数可能是用户输入),则向您开放的唯一机制是引发异常。在某些情况下,这可能不是您想要的,将易于出错的代码分离到单独的初始化函数可能更有意义。

如果项目/组织标准是关闭异常,那么最常见的例子可能是C ++。

状态机

在建模具有显式状态转换的对象时就是这种情况。例如,可以打开和关闭的文件或套接字。

在这种情况下,对象构造(和删除)仅处理面向存储器的属性(文件名,端口等)是很常见的。然后将有一些功能可以专门管理状态转换,例如打开,关闭,它们是有效的初始化和拆卸功能。

如上所述,优点是在错误处理上,但也可能存在将构造与初始化分开的情况(例如,您构建文件向量并异步打开它们)。

就像其他人所说的那样,缺点是您现在将状态管理的负担加到了类的用户身上。如果您可以仅通过结构进行管理,那么可以说,使用RAII自动完成此任务。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.