2022-09-14

c++: Designing builders for an hierarchy of classes [duplicate]

The question presented below approaches the problem of constructing objects that have several properties that must be initialized during their constructions. In this sense, the question is more related to builder design patterns than factories (or abstract factories).

In addition to this, the question also presents a scenario where these objects' classes can be inherited from at will, and these child classes can also be inherited from at will, and so on. Thus, we don't know how many classes will be derived from a parent class, and we also don't know how deep the inheritance chain can be.

All this leads to the problem of designing a builder pattern (perhaps a mix between a builder and a factory) that could be inherited from, thus reducing/avoiding code duplication. Below a concrete example, with a current solution that can certainly be improved, is presented. The bottom line of this post is: if and how the proposed solution could eventually be improved.

Suppose we have an abstract base class A from which a hierarchy of descendant classes could be derived:

class A {
 public:
  virtual ~A() = 0;
  int getX() const { return x_; }

 private:
  int x_;
};

A::~A(){};

In the code below I derive, as an example, a child class B and a grandchild class C from A:

class B : public A {
 public:
  const std::string& getY() const { return y_; }

 private:
  std::string y_;
};

class C : public B {
 public:
  const std::string& getZ() const { return z_; }

 private:
  std::string z_;
};

In the example above the classes were kept simple to avoid cluttering the question. However, in real life these classes will have each several different properties that should be set during their instantiation. Also, all properties will have default values and the user will be able to set just the desired properties (or eventually none of them) when creating the objects.

In this scenario, the use of a builder pattern to initialize the objects seems to be a common approach.There are several different builder designs available, and I would like the user to be able to instantiate the classes above in the following manner:

B_Builder b_builder;             // creates the builder object
b_builder.setX(10);              // sets a property inherited from class A
b_builder.setY("Test");          // sets a property defined in class B
B b_object = b_builder.build();  // the builder instantiate an object of class B

C_Builder c_builder;             // creates the builder object
c_builder.setX(11);              // sets a property inherited from class A
c_builder.setY("Test2");         // sets a property inherited from class B
c_builder.setZ("Test23");        // sets a property defined in class C
C c_object = c_builder.build();  // the builder instantiate an object of class C

One possible way to implement these builders would be to create the B_Builder and C_Builder both separately and from scratch. The problem with this approach is that, since the builders will have to initialize commonly inherited properties, there will be code duplication. The problem will be more noticeable as more properties are inherited or deeper is the inheritance chain.

Thus, I considered a builder that could be inherited from (perhaps a mix of a builder and a factory), which would avoid code duplication (I've already seen this approach in a Java post, but could not find it again to link it here). The solution I came up with uses the builder inheritance chain to temporarily store the object properties and to set the object properties once it is created.

Below you can check the current solution (for the code below to work, I had to add the builder classes as friends of the classes they actually build):

class A_Builder {
 public:
  virtual ~A_Builder() = 0;
  void setX(int x) { x_ = x; }
 
 protected:
   template<typename T>
   T& build_() const {
     T* t = new T;
     t->x_ = this->x_;
     return *t; 
   }

 private:
  int x_;

};

A_Builder::~A_Builder(){};

class B_Builder : public A_Builder {
 public:
  void setY(std::string y) { y_ = y; }

  B& build() const {
    return build_<B>();
  }

protected:
  template<typename T>
  T& build_() const {
    T& c = A_Builder::build_<T>();
    c.y_ = y_;
    return c;
  }
  
private:
  std::string y_;

};

class C_Builder : public B_Builder {
 public:
  void setZ(std::string z) { z_ = z; }

  C& build() const {
    return build_<C>();
  }

protected:
  template<typename T>
  T& build_() const {
    T& c = B_Builder::build_<T>();
    c.z_ = z_;
    return c;
  }

private:
  std::string z_;

};

The solution looks scalable, exposes the object building interface I was looking for and avoids code duplication. However, I didn't like the builder inheritance scheme in its current state because, every time a new builder needs to be created (i.e. inherited) the user will have also to (in addition to the inheritance) write a build_ method that is almost identical to the builder_ found in parent's builders.

I am not a c++ expert, but I am quite sure that a cleaner and more elegant inherited builder scheme could be devised (e.g. which would avoid the build_ method problem). It can even be that such a builder design already exists, but I am not aware and could not find it trough my search.

So, any help/advice regarding the improvement of this object building scheme is very welcome! Thank you in advance!



No comments:

Post a Comment