18

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.

Community
  • 1
  • 1
corazza
  • 31,222
  • 37
  • 115
  • 186
  • 7
    Do you have reasonable defaults for some of the values? – chris Oct 10 '12 at 15:10
  • 6
    *" Either make a class that accepts a lot arguments in its constructors, or create a lot of setter methods and an init method."* -- False dilemna. Generally, the first option is better. But if possible, the third option, redesign such that your constructor has fewer arguments *and* there is no need for setters or an initialize function, would be preferred. – Benjamin Lindley Oct 10 '12 at 15:13
  • 1
    @Bane: more and more I find myself provided no setter at all, at least for objects with just a few fields. If you want a different value for a given field, then just create a new object with those values. It's not really manageable after 3 or 4 fields though... but that just keeps my objects smaller :D – Matthieu M. Oct 10 '12 at 15:28
  • You should redisign your class, the princpal of one class / one job usually help you do that. Of course you can encapsulate those arguments in an wrapper as well. – lollancf37 Oct 23 '12 at 13:20

9 Answers9

28

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*/)
Community
  • 1
  • 1
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Well, the objects could function, but most of the arguments would be default (0). – corazza Oct 10 '12 at 15:13
  • 1
    @Bane If those defaults make sense, then why not. On the other hand if those defaults are in most cases overwritten by a following `set` call, it's probably rubbish. Even more so, if the values aren't gonna be changed ever except at init time, then there isn't any need for a setter anyway. In this case a costructor with many parameters definitely makes the most sense, possibly with the help of default arguments to simplify its usage. – Christian Rau Oct 10 '12 at 15:18
  • 3
    @Bane: remember you can have several constructors as well, if you have an object which can be set from a few different sets of parameters. – Matthieu M. Oct 10 '12 at 15:26
  • 1
    Objects that start incomplete cause lots of bugs later, especially if the missing values are only used sometimes. This is a basic OO principle and applies equally to Java and C#. – Michael Shopsin Oct 10 '12 at 16:20
  • 1
    @ChristianRau: I agree, but I'd add the small caveat that in some circumstances, multiple explicit constructors can be better than default arguments. For example, it doesn't make sense for the client to specify `argA` if they're not also specifying `argB`, then `int argA = 0, int argB = 0` allows a confusing/misleading set of arguments. – ruakh Oct 10 '12 at 19:42
4

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.

Zdeslav Vojkovic
  • 14,391
  • 32
  • 45
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

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
  • Builder pattern is a good choice--It's like having named parameters AND immutable classes. – Bill K Oct 10 '12 at 19:41
  • The only problem is that the typical builder pattern cannot guarantee at compile time that all arguments have been included – Catskul Jul 23 '13 at 20:17
  • @Catskul you can then either create sensible defaults or throw exceptions, or have parameters that belong together be provided in the same method from the builder – ratchet freak Jul 23 '13 at 21:10
  • I came up with a solution that has compile-time checks, but it may be overly complicated depending on the situation: http://stackoverflow.com/a/17820333/106797 – Catskul Jul 23 '13 at 21:23
1

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.

1

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;
}
Community
  • 1
  • 1
Catskul
  • 17,916
  • 15
  • 84
  • 113
0

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.

Component 10
  • 10,247
  • 7
  • 47
  • 64
0

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.

count0
  • 2,537
  • 2
  • 22
  • 30
  • 1
    I don't understand your argument against constructors. Do you mean it's not well supported by IDEs ? – Matthieu M. Oct 10 '12 at 15:26
  • I'm referring to constructors derived from the base class. Changing the base-class (f.e. deciding not to pass an object) forces you to change all derived constructors. This only happens in larger hierarchies of course. – count0 Oct 10 '12 at 15:31
  • 1
    Actually, I would say it is in favor of constructors here. Because it's very easy to forget to add a call to a setter somewhere (and thus have to trigger a runtime error) whereas the compiler checks that you did not forget to modify any derived constructor (and construction site) **automatically**! – Matthieu M. Oct 10 '12 at 15:36
  • @Matthieu: True, the compiler catches this in common cases. I'm referring less to the design and more to the workflow in case of prototyping/developing the hierarchy. If one decides to be more expressive and f.e. changes one parameter to const, it can easily flush a lot of time only to fix the hierarchy as opposed to having one setter fixed. That said i fully agree with Lucians and Zdeslav's answers. – count0 Oct 10 '12 at 15:52
0

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.

Magmatic
  • 1,754
  • 3
  • 19
  • 34
0

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.

bobestm
  • 1,334
  • 1
  • 9
  • 8