6

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?

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
nahpr
  • 619
  • 7
  • 15
  • 12
    My general approach is to avoid inheritance as much as possible, and try to keep each class focussed on a single responsibility. Could you construct `ClassA` separately, and then initialise `ClassB` with a reference to that, rather than tightly coupling them through inheritance? – Mike Seymour Sep 04 '12 at 17:13
  • 2
    Mike Seymour is right. Prefer a has-a relationship (composition) to the more tightly coupled is-a relationship (inheritance). Perhaps if you explain what you want to do, we can tell you how better to accomplish it? – metal Sep 04 '12 at 17:28
  • 2
    I do not see any major problem with inheritance. It should be used everywhere where it is needed. It should be not abused as everything else. – Kirill Kobelev Sep 04 '12 at 17:42
  • 1
    @KirillKobelev: I entirely agree; it should be avoided unless it's needed. – Mike Seymour Sep 04 '12 at 20:48
  • What constitutes 'too many arguments'? When using IOC the container takes care of them and this only happens when you **abuse inheritance** or have a 'God Object' that does everything and depends on the universe. – Yoztastic Sep 05 '12 at 17:59

3 Answers3

6

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).

Jan Wrobel
  • 6,969
  • 3
  • 37
  • 53
  • This may be a bit nicer than setters, but you're effectively delaying the check for sufficient information is present to construct an object to runtime. I don't consider that an improvement over a plain constructor taking a few arguments. – Frerich Raabe Sep 06 '12 at 07:43
  • If you want compile time enforcement, you can still sometimes benefit from the builder. This is the case when a class constructor take many optional parameters with default values. Then, you can use builder's setters to set these optional parameters and have build() method take all required params. This would give you compile time enforcement that all required parameters are passed but will be more readable and less error prone than a call to a constructor that takes many parameters some of them required some of them optional with default values. – Jan Wrobel Sep 06 '12 at 08:16
  • 1
    I like this approach. The code for a client constructing the object is much cleaner. Although its true that you lose the compile time check, the build() function still allows for a validation check in your code. – Bryan Johnson Jan 16 '13 at 17:25
1

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.

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
  • In most cases, it would make sense to make `PropsA` a full-fledged class by pushing related methods into it as well. Otherwise you just end up with a data structure which is not very object-oriented. – casablanca Sep 05 '12 at 06:04
  • 1
    I put the requirements of the app, speed of development and clarity of the code FAR FAR ahead of the fancy stuffs of being "object oriented". – Kirill Kobelev Sep 05 '12 at 06:13
  • 1
    OOP is not "fancy", but it's a choice that you take or leave. I'm fine with someone just doing it the procedural way, using structs and functions, but if you're planning on using classes, it pays to put effort into doing it right, otherwise you end up in a mess where half your code is object-oriented and the rest is procedural -- I don't think that leads to much clarity. – casablanca Sep 06 '12 at 02:45
  • The OOP itself is not fancy. It is a set of extremely useful methods. Only it is necessary to think what elements of this set are useful in each case first, and only after thinking use them. If something does not have reasonable methods and/or live for a short time - it should be a struct. And it is right for it to be a struct. This is not making the code less object oriented. OOP just for OOP - this is useless fancy stuffs. – Kirill Kobelev Sep 06 '12 at 05:21
  • I totally agree that structs have their place, but that's why I said "in most cases, ..." -- in most cases, when a constructor takes "too many" arguments, it's a sign that the class is doing too much and could be refactored into smaller classes. – casablanca Sep 06 '12 at 05:27
  • It can be that I was too hard. Sorry for that. "In most cases" sounded to me like "always with rare exceptions". "it's a sign that the class is doing too much" - yes, this can be a sign. Only before refactoring it is still necessary to make evaluation if this piece of code is the worst in the whole project or not. Most likely that other place needs more urgent attention. – Kirill Kobelev Sep 06 '12 at 05:34
  • No worries. Indeed I am somewhat of an OO purist, and when someone tags a question with "oop" or "design-patterns", I am naturally inclined to discuss the proper OO way rather than the quickest solution. :) – casablanca Sep 06 '12 at 05:48
  • this method looks like "Parameter object pattern" and good option. similar discussion: http://stackoverflow.com/questions/5551640/c-design-pattern-for-passing-a-large-number-of-parameters – sardok Sep 06 '12 at 08:11
1

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.
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207