可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have two options. Either make a class that accepts a lot arguments in its constructors, or create a lot of setter methods and an init method. I'm not sure which is preferred option, should some arguments be accepted in constructors, while others could be manually set via setter? Or am I over-thinking this?
This is a relevant question, also by me: Conflicts between member names and constructor argument names.
回答1:
If after you create an object you have to call set
or init
to actually use it... well, that's just an awful design.
If the object is usable without some of the members initialized the way you want them to be, you can set them later on.
The golden rule here is - if you create an object, you should be able to use it without doing any other sort of initialization.
Expanding on the answer:
Say you have a shape with 10 sides, 10 corners, a color and a name, that can be connected to a different shape. The constructor should look like:
MyShape(Point c1, Point c2,...., Point c10, Color c, Name n)
As you can see, I've omitted the connected shape because it can sensibly be set to NULL
if the current object is not connected. However, in the absence of any of the other parameters, the object isn't valid, so they should be set in the constructor.
A possible overload (alternitively a default argument) can be:
MyShape(Point c1, Point c2,...., Point c10, Color c, Name n,
MyShape* connectedShape /*=NULL*/)
回答2:
You should provide the constructor arguments for all the members which are necessary to preserve the class invariant. In other words, object should be in valid and consistent state from the moment it is created until it is destroyed. Everything else is calling for troubles.
That being said, concessions are sometimes made, e.g. in cases of hierarchies where virtual methods are required to be called in order to provide type specific initialization. Oftentimes, this can be avoided by usage of template classes/methods (i.e. static polymorphism)
If there are class members which don't affect the class invariant, they can be set later on via setters or other methods.
回答3:
the builder pattern will help here also try to coalesce the parameters to have them make sense during the setting up of the builder
回答4:
It rather depends on what you're doing. Usually it's best to set things in the constructor as these help shape how the object is used later in its lifecycle. There can also be implications of changing a value once the object has been created (e.g. a calculation factor, or file name) which might mean you have to provide functionality to reset the object - very messy.
There are sometimes arguments for providing an initialization function which is called after the constructor (when calling a pure virtual would make it difficult to initialize direct from the constructor) but you then have to keep a record of object state which adds more complexity.
If the object is a straight stateless data container then accessors and mutators might be OK but they add a lot of maintenance overhead and are rarely all used anyway.
I'd tend to stick with setting your values in the constructor and then adding accessors as and when to allow you read only access to arguments that you might need.
回答5:
That depends on your architecture and tools:
If you plan to develop/prototype a large OO-hierarchy, i'd be reluctant with passing lots of information via constructors if you don't have a good IDE/editor. In this case you might end up with a lot of work each refactoring step, which might result in errors, not catched by the compiler.
If you plan to use a well integrated set of objects (e.g. through strong use of design patterns) which do not span one large hierarchy, but rather have strong iteraction, passing more data via constructors is a good thing, since changing one objects constructor does not break all the child constructors.
回答6:
If the setting is required and cannot be given a default value, make it required in the constructor. That way you know it will actually be set.
If the setting is not required and can be given a default value, make a setter for it. This makes the constructor a lot simpler.
e.g. If you have a class that sends an email, the "To" field might be required in the constructor, but everything else can be set in a setter method.
回答7:
My experience points me to having arguments in the constructor rather than getters and setters. If you have a lot of parameters, it suggests optional ones can be defaulted while the required/mandatory ones are the constructor parameters.
回答8:
As a rule of thumb, having lots of constructor parameters is a sign of a class that does too much, so try splitting it into smaller classes first.
Then try grouping some of the parameters into smaller classes or structs having their own, simpler, constructor each.
If you have sensible default values, you can use a constructor that provides parameters only for values that absolutely MUST be given when constructing a new object, and then add setters, or use static functions that copy a "starter" object, changing part of it in the process. That way, you always have consistent objects (invariants OK), and shorter constructor or function calls.
回答9:
I agree with ratchet freak's suggestion of the builder pattern except that there is a trade-off in that the typical builder pattern offers no compile-time checks to insure all arguments have been included, and you can end up with an incompletely/incorrectly built object.
This was a problem enough for me that I make a compile-time checking version that might do the job for you if you can forgive the extra machinery. (There are certainly optimizations to be had as well)
#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;
}