我有两个选择。要么创建一个在其构造函数中接受大量参数的类,要么创建许多setter方法和init方法。我不确定哪个是首选选项,是否应该在构造函数中接受一些参数,而其他参数可以通过setter手动设置?还是我过度思考这个?
这是一个相关问题,我也是:Conflicts between member names and constructor argument names。
答案 0 :(得分:26)
如果在创建对象后,您必须调用set
或init
来实际使用它......好吧,这只是一个糟糕的设计。
如果对象可用而没有按照您希望的方式初始化某些成员,您可以稍后设置它们。
这里的黄金法则是 - 如果您创建一个对象,您应该能够在不进行任何其他初始化的情况下使用它。
假设您的形状有10个边,10个角,颜色和名称,可以连接到不同的形状。构造函数应该如下所示:
MyShape(Point c1, Point c2,...., Point c10, Color c, Name n)
如您所见,我省略了连接的形状,因为如果当前对象未连接,它可以合理地设置为NULL
。但是,在没有任何其他参数的情况下,该对象无效,因此应在构造函数中设置它们。
可能的重载(或者是默认参数)可以是:
MyShape(Point c1, Point c2,...., Point c10, Color c, Name n,
MyShape* connectedShape /*=NULL*/)
答案 1 :(得分:4)
您应该为保留class invariant所需的所有成员提供构造函数参数。换句话说,对象从创建到销毁之前应处于有效且一致的状态。其他一切都在呼唤麻烦。
话虽如此,有时会作出让步,例如:在层次结构的情况下,需要调用虚方法以提供特定于类型的初始化。通常,这可以通过使用模板类/方法(即static polymorphism)
来避免如果有类成员不影响类不变量,可以稍后通过setter或其他方法设置它们。
答案 2 :(得分:2)
the builder pattern这里也会帮助我们尝试合并参数,让它们在构建构建器时有意义
答案 3 :(得分:0)
这取决于你在做什么。通常最好在构造函数中设置内容,因为这些有助于塑造对象在其生命周期的后期使用的方式。一旦创建了对象(例如计算因子或文件名),也可能意味着你必须提供重置对象的功能 - 这非常麻烦。
有时会提供一个初始化函数的参数,该函数在构造函数之后调用(当调用纯虚函数会使得很难直接从构造函数初始化时),但是你必须保留对象状态的记录,这会增加复杂性
如果对象是一个直接的无状态数据容器,那么访问器和增变器可能没问题,但它们会增加很多维护开销,并且很少都会被使用。
我倾向于坚持在构造函数中设置您的值,然后添加访问器以及何时允许您只读取您可能需要的参数。
答案 4 :(得分:0)
这取决于您的架构和工具:
如果你计划开发/原型一个大的OO层次结构,如果你没有一个好的IDE /编辑器,我不愿意通过构造函数传递大量信息。在这种情况下,每个重构步骤可能会导致很多工作,这可能会导致错误,而不会被编译器捕获。
如果您计划使用一个集成良好的 set 对象(例如,通过强烈使用设计模式),这些对象不跨越一个大型层次结构,而是具有强大的迭代次数,那么通过构造函数传递更多数据是一件好事,因为更改一个对象构造函数不会破坏所有子构造函数。
答案 5 :(得分:0)
如果需要设置且无法给出默认值,请在构造函数中使其成为必需项。这样你知道它实际上会被设置。
如果不需要该设置并且可以给出默认值,请为其设置一个setter。这使得构造函数更加简单。
e.g。如果您有一个发送电子邮件的类,构造函数中可能需要“收件人”字段,但其他所有内容都可以在setter方法中设置。
答案 6 :(得分:0)
我的经验指出我在构造函数中有参数而不是getter和setter。如果你有很多参数,它建议可选的那些参数可以默认,而必需/强制参数是构造函数参数。
答案 7 :(得分:0)
根据经验,拥有大量构造函数参数是一个做太多的类的标志,所以首先尝试将它拆分为更小的类。
然后尝试将一些参数分组为较小的类或结构,每个类或结构都有自己的,更简单的构造函数。
如果你有合理的默认值,你可以使用一个构造函数,它只为在构造一个新对象时必须给出的值提供参数,然后添加setter,或者使用复制“starter”对象的静态函数,改变在此过程中的一部分。这样,你总是有一致的对象(不变量OK),以及更短的构造函数或函数调用。
答案 8 :(得分:0)
我同意ratchet freak的suggestion构建器模式,除了需要权衡之外,典型的构建器模式不提供编译时检查以确保包含所有参数,你最终可能会得到一个不完整/不正确的对象。
这对我来说是个问题,如果你可以原谅额外的机器,我会编写一个可以为你完成工作的编译时检查版本。 (当然也有优化)
#include <boost/shared_ptr.hpp>
class Thing
{
public:
Thing( int arg0, int arg1 )
{
std::cout << "Building Thing with \n";
std::cout << " arg0: " << arg0 << "\n";
std::cout << " arg1: " << arg1 << "\n";
}
template <typename CompleteArgsT>
static
Thing BuildThing( CompleteArgsT completeArgs )
{
return Thing( completeArgs.getArg0(),
completeArgs.getArg1() );
}
public:
class TheArgs
{
public:
int arg0;
int arg1;
};
class EmptyArgs
{
public:
EmptyArgs() : theArgs( new TheArgs ) {};
boost::shared_ptr<TheArgs> theArgs;
};
template <typename PartialArgsClassT>
class ArgsData : public PartialArgsClassT
{
public:
typedef ArgsData<PartialArgsClassT> OwnType;
ArgsData() {}
ArgsData( const PartialArgsClassT & parent ) : PartialArgsClassT( parent ) {}
class HasArg0 : public OwnType
{
public:
HasArg0( const OwnType & parent ) : OwnType( parent ) {}
int getArg0() { return EmptyArgs::theArgs->arg0; }
};
class HasArg1 : public OwnType
{
public:
HasArg1( const OwnType & parent ) : OwnType( parent ) {}
int getArg1() { return EmptyArgs::theArgs->arg1; }
};
ArgsData<HasArg0> arg0( int arg0 )
{
ArgsData<HasArg0> data( *this );
data.theArgs->arg0 = arg0;
return data;
}
ArgsData<HasArg1> arg1( int arg1 )
{
ArgsData<HasArg1> data( *this );
data.theArgs->arg1 = arg1;
return data;
}
};
typedef ArgsData<EmptyArgs> Args;
};
int main()
{
Thing thing = Thing::BuildThing( Thing::Args().arg0( 2 ).arg1( 5 ) );
return 0;
}