Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Too Many Constructor Args for Dependency Injection/Inheritance Design Pattern

So I decided to use the Factory Design Pattern along with Dependency Injection.

class ClassA
{        
    Object *a, *b, *c;
    public:
    ClassA(Object *a, Object *b, Object *c) :
    a(a), b(b), c(c) {}
};

class ClassB : public ClassA
{        
    Object *d, *e, *f;
    public:
    ClassB(Object *a, Object *b, Object *c, Object *d, Object *e, Object *f) :
    ClassA(a, b, c), d(d), e(e), f(f) {}
};

Now, the problem is that classB has too many arguments for the constructor. This is a single inheritance-layer example, but when the inheritance layers start getting deeper, and when each layer-class needs more objects to be constructed, the constructor in the top layer ends requiring too many arguments in order to be made!

I know I could use setters instead of the constructor, but is there any other way?

like image 819
nahpr Avatar asked Sep 04 '12 17:09

nahpr


Video Answer


3 Answers

Setter are not recommended for such things because they result in a partially constructed object which is very error prone. A common pattern for constructing an object that requires many parameters is the use of builder. The responsibility of ClassBBuilder is to create ClassB objects. You make ClassB constructor private and allow only builder to call it using friend relationship. Now, the builder can look somehow like this

ClassBBuilder {
  public:
    ClassBBuilder& setPhoneNumber(const string&);
    ClassBBuilder& setName(consg string&);
    ClassBBuilder& setSurname(const string&);
    ClassB* build(); 
} 

And you use the builder likes this:

ClassB* b = ClassBBuilder().setName('alice').setSurname('Smith').build();

build() method checks that all required parameters were set and it either returns properly constructed object or NULL. It is impossible to create partially constructed object. You still have a constructor with many arguments, but it is private and called only in a single place. Clients won't see it. Builder methods also nicely document what each parameter means (when you see ClassB('foo', 'bar') you need to check the constructor to figure out which parameter is a name and which is a surname).

like image 141
Jan Wrobel Avatar answered Oct 22 '22 14:10

Jan Wrobel


This is one of the C++ problems (if this can be called a problem). It does not have solution other than trying to keep the number of parameters of the ctor minimal.

One of the approaches is using the props struct like:

struct PropsA
{
    Object *a, *b, *c;
};

class ClassA
{
    ClassA(PropsA &props, ... other params);
};

This seems obvious but I did used this several times. In many cases it turns out that some group of params are related. In this case it makes sense to define a struct for them.

My worst nightmare of this sort was with the thin wrapper classes. Methods and data fields of the base can be accessed directly while all ctors has to be duplicated. When there are 10+ ctors, creating a wrapper starts to be a problem.

like image 39
Kirill Kobelev Avatar answered Oct 22 '22 15:10

Kirill Kobelev


I think what you're describing is not a problem in C++ - in fact, C++ reflects the dependencies expressed by your design fairly well:

  1. To construct an object of type ClassA, you need to have three Object instances (a, b and c).
  2. To construct an object of type ClassB, you also need to have three Object instances (d, e and f).
  3. Every object of type ClassB can be treated like an object of type ClassA.

This means that for constructing an object of type ClassB you need to provide three Object objects which are needed for the implementation of the ClassA interface, and then another three for the implementation of the ClassB interface.

I believe the actual issue here is your design. You could consider different approaches to resolve this:

  1. Don't let ClassB inherit ClassA. May or may not be an option depending on whether you need homogenous access to objects of either type (say, because you have a collection of ClassA* and this collection could also contain pointers to ClassB).
  2. Look for objects which always appear together. Like - maybe the first two objects passed to either constructor (a and b or d and e) represent some sort of pair. Maybe an object identifier or the like? In this case, it may be beneficial to introduce a dedicated abstract (read: type) for this.
like image 1
Frerich Raabe Avatar answered Oct 22 '22 14:10

Frerich Raabe