I’ve written an article on the Factory pattern in C++17, and an occasional article on the Singleton pattern in C++17. It looks like it’s going to be a lot of work, which is a lot of trouble for lazy people. I don’t know if I intend to write a full personal understanding of GoF and the new implementation, so take your time and see what happens.

Review the Builder pattern and address the problem of how the builder template class should be implemented when doing a class library.

Prologue

In fact, as far as I am concerned, the real use of Builder pattern is in the Java development experience. The same is true for streaming interfaces.

The Builder mode is used to construct an object step by step.

FROM: HERE

Although most of the time we only care about how to manipulate the object after it is new, sometimes in some scenarios we only care about how to new the object. That’s where Builder comes in.

Builder Pattern

The theory of

The Builder pattern is one of the Creational Patterns. We already covered the creation pattern in talking about Factory in C++17, so we won’t cover it in this article.

The purpose of the Builder pattern is to allow you to build complex objects step by step. It allows you to use the same (similar) creation diamante to produce different types and forms of objects.

An important hallmark of the Builder pattern, though not specified but often by convention, is that it ends with a.build() call. Such as:

auto shape = Builder().choose(Shape.Rect)   // choose a factory
  .setColor(COLOR.RED)
  .setBorderWidth(1).setFill(COLOR.GRAY)
  .build(a); canva.place(shape, Position.Default);
Copy the code

The Builder model does not have to be a streaming interface.

Instead, many times we need to negotiate a choice with the interaction object and set the decision to the Builder Builder. The final product instance is not built using Builder.build () until all negotiations are complete.

As imagined in the example code, we can also combine Builder and Factory patterns (and Proxy patterns and others), with a fundamental Builder calling the Concreted FactoryBuilder to build multiple products. Because this tends to take a lot of code to look good, it is no longer expanded.

C + + implementation

Note that the following examples are long.

The basic

Here is a standard, basic Builder pattern example. This case shows the typical implementation method of Builder pattern through the step-by-step construction of the four components of email.

 namespace hicc::dp::builder::basic {
 ​
   class email_builder;
   class email {
     public:
     ~email() {}
     friend class email_builder; // the builder can access email's privates
     static email_builder builder(a);
 ​
     std::string to_string(a) const {
       std::stringstream ss;
       ss << " from: " << _from
         << "\n to: " << _to
         << "\nsubject: " << _subject
         << "\n body: " << _body;
       return ss.str(a); }explicit email(std::string const &from, std::string const &to, std::string const &subject, std::string const &body)
       : _from(from)
         , _to(to)
         , _subject(subject)
         , _body(body) {}
     email(email &&o) {
       _from = o._from, _to = o._to, _subject = o._subject, _body = o._body;
     }
     email clone(email &&o) {
       email n{o._from, o._to, o._subject, o._body};
       return n;
     }
 ​
     private:
     email() = default; // restrict construction to builder
     std::string _from{}, _to{}, _subject{}, _body{};
   };
 ​
   class email_builder {
     public:
     email_builder &from(const std::string &from) {
       _email->_from = from;
       return *this;
     }
 ​
     email_builder &to(const std::string &to) {
       _email->_to = to;
       return *this;
     }
 ​
     email_builder &subject(const std::string &subject) {
       _email->_subject = subject;
       return *this;
     }
 ​
     email_builder &body(const std::string &body) {
       _email->_body = body;
       return *this;
     }
 ​
     operator std::unique_ptr<email> &&() {
       return std::move(_email); // notice the move
     }
 ​
     auto build(a) {
       return std::move(_email); // not a best solution since concise is our primary intent
     }
 ​
     email_builder()
       : _email(std::make_unique<email>(""."".""."")) {}
 ​
     private:
     std::unique_ptr<email> _email;
   };
 ​
   inline email_builder email::builder(a) { return email_builder(a); }inline std::ostream &operator<<(std::ostream &os, const email &email) {
     os << email.to_string(a);returnos; }}// namespace hicc::dp::builder::basic
 ​
 void test_builder_basic(a) {
   using namespace hicc::dp::builder::basic;
   // @formatter:off
   auto mail = email::builder().from("[email protected]").to("[email protected]").subject("About Design Patterns").body("There is a plan to write a book about cxx17 design patterns. It's good?").build(a); std::cout << *mail.get() < <'\n';
   // @formatter:on
 }
Copy the code

The test code section also shows a typical streaming invocation style.

The sample code provides a rigid means of coding the structure by obtaining the builder through Model Class :: Builder () and, in the last step, obtaining the final Model Class instance object with Builder.build (). Sometimes a rigid approach is the best option. Indeed, we will see later that a design pattern can be implemented in a variety of ways. However, keeping the code structure similar will help users to obtain the interface usage method without additional documentation when visiting the interface API, especially when visiting the available interface through the namespace level.

So the code speaks for itself, which is the right way to avoid comments.

Bonus tip

In keeping with the Modern C++ style, the sample code uses unique_ptr to help manage the sample. Why not use shared_ptr? Because shared_Ptr is relatively heavy and requires an additional set of reference counting mechanisms to manage, use Unique_Ptr directly and only consider using Shared_Ptr when necessary (such as when you need to host it in multiple containers).

What if I use the fixed paradigm above, but I need shared_ptr, can I convert unique_ptr to shared_PTR semantics?

This, however, is not a problem, as the move semantics allow direct transfer from U to S:

 std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
 std::shared_ptr<std::string> shared = std::move(unique);
Copy the code

Even:

 std::shared_ptr<std::string> shared = std::make_unique<std::string>("test");
Copy the code

So you can decide whether to make an explicit return type declaration when building () :

 auto obj = builder.build(a);/ / get unique_ptr < T >
 std::shared_ptr<T> o = builder.build(a);// Implies a move operation
Copy the code

The embedded

The previous example used two separate classes to make the class structure and dependencies clearer, but it might be a bit dirty because there would be an extra builder class for a product in the namespace. A namespace named Models should not have helpers, utilities or anything other than Model. Therefore, especially in Metaprogramming, there is a preference to embed the Builder class directly in the Product class:

 namespace hicc::dp::builder::embed {
 ​
   class email {
     public:
     class builder_impl {
       public:
       builder_impl &from(const std::string &from) {
         _email._from = from;
         return *this;
       }
       // ...
       auto build(a) {
         return _email;
       }
 ​
       private:
       std::unique_ptr<email> _email;
     };
 ​
     static builder_impl builder(a){
       return builder_impl{}; 
     }
 ​
     public:
     / /...
 ​
     private:
     email() = default; // restrict construction to builder
     std::string _from, _to, _subject, _body;
   };
 ​
 } // namespace hicc::dp::builder::embed
 ​
 void test_builder_embed(a) {
   using namespace hicc::dp::builder::embed;
   // @formatter:off
   auto mail = email::builder().from("[email protected]").to("[email protected]").subject("About Design Patterns").body("There is a plan to write a book about cxx17 design patterns. It's good?").build(a); std::cout << mail <<'\n';
   // @formatter:on
 }
Copy the code

There is little need for users to revise.

The added benefit is that there is no need for additional declarations for forward references or friend class declarations, which can save a lot of brainpower.

complex

However, the Builder pattern does not have to have a build() method to do the trick, nor does it have to have a streaming interface. The following case also often appears in the corresponding tutor, but we have changed it.

Firstly, the product category is given:

 namespace hicc::dp::builder::complex {
 ​
   namespace basis {
     class wheel {
       public:
       int size;
     };
 ​
     class engine {
       public:
       int horsepower;
     };
 ​
     class body {
       public:
       std::string shape;
     };
 ​
     class car {
       public:
       wheel *wheels[4];
       engine *engine;
       body *body;
 ​
       void specifications(a) {
         std::cout << "body:" << body->shape << std::endl;
         std::cout << "engine horsepower:" << engine->horsepower << std::endl;
         std::cout << "tire size:" << wheels[0]->size << "'"<< std::endl; }}; }// namespace basis
 } // namespace hicc::dp::builder::complex
Copy the code

It has nothing to say.

But its Builder would be a bit more complicated, as it was decided that there would be two prefabricated Builders (Jeep and Nissan) that would make cars of different specifications. So we need a Builder class for the abstract class, and a director to build the boilerplate class. In fact, you don’t have to separate the boilerplate class, but you can take full advantage of polymorphism:

 namespace hicc::dp::builder::complex {
 ​
   class builder {
     public:
     virtual basis::wheel *get_wheel(a) = 0;
     virtual basis::engine *get_engine(a) = 0;
     virtual basis::body *get_body(a) = 0;
   };
 ​
   class director {
     public:
     void set_builder(builder *b) { _builder = b; }
 ​
     basis::car *get_car(a) {
       basis::car *car = new basis::car(a); car->body = _builder->get_body(a); car->engine = _builder->get_engine(a); car->wheels[0] = _builder->get_wheel(a); car->wheels[1] = _builder->get_wheel(a); car->wheels[2] = _builder->get_wheel(a); car->wheels[3] = _builder->get_wheel(a);return car;
     }
 ​
     private:
     builder *_builder;
   };
 ​
 } // namespace hicc::dp::builder::complex
Copy the code

The template class determines the standard template for building cars.

If you do take the code logic that implements get_car() directly in the abstract Builder class and make it virtual (which is not required), then it actually references the Template Method Pattern.

The Template Method Pattern defines the framework of an algorithm in a superclass, allowing subclasses to override specific steps of the algorithm without modifying the structure.

Next, implement two builder classes:

 namespace hicc::dp::builder::complex {
 ​
   class jeep_builder : public builder {
     public:
     basis::wheel *get_wheel(a) {
       basis::wheel *wheel = new basis::wheel(a); wheel->size =22;
       return wheel;
     }
 ​
     basis::engine *get_engine(a) {
       basis::engine *engine = new basis::engine(a); engine->horsepower =400;
       return engine;
     }
 ​
     basis::body *get_body(a) {
       basis::body *body = new basis::body(a); body->shape ="SUV";
       returnbody; }};class nissan_builder : public builder {
     public:
     basis::wheel *get_wheel(a) {
       basis::wheel *wheel = new basis::wheel(a); wheel->size =16;
       return wheel;
     }
 ​
     basis::engine *get_engine(a) {
       basis::engine *engine = new basis::engine(a); engine->horsepower =85;
       return engine;
     }
 ​
     basis::body *get_body(a) {
       basis::body *body = new basis::body(a); body->shape ="hatchback";
       returnbody; }}; }// namespace hicc::dp::builder::complex
Copy the code

And, its test code:

 void test_builder_complex(a) {
   using namespace hicc::dp::builder::complex;
 ​
   basis::car *car; // Final product
 ​
   /* A director who controls the process */
   director d;
 ​
   /* Concrete builders */
   jeep_builder jb;
   nissan_builder nb;
 ​
   /* Build a Jeep */
   std::cout << "Jeep" << std::endl;
   d.set_builder(&jb); // using JeepBuilder instance
   car = d.get_car(a); car->specifications(a); std::cout << std::endl;/* Build a Nissan */
   std::cout << "Nissan" << std::endl;
   d.set_builder(&nb); // using NissanBuilder instance
   car = d.get_car(a); car->specifications(a); }Copy the code

Note that A Car is composed of many parts, each of which can have complex build steps.

To optimize the

Of course, this example is just that. In the real world, the implementation of this example could pull jeep_Builder and nissan_Builder out of a common base class:

 class managed_builder : public builder {
   public:
   basis::wheel *get_wheel(a) {
     basis::wheel *wheel = new basis::wheel(a); wheel->size = wheel_size;return wheel;
   }
 ​
   basis::engine *get_engine(a) {
     basis::engine *engine = new basis::engine(a); engine->horsepower = engine_horsepower;return engine;
   }
 ​
   basis::body *get_body(a) {
     basis::body *body = new basis::body(a); body->shape = body_shape;return body;
   }
 ​
   managed_builder(int ws, int hp, const char *s = "SUV")
     : wheel_size(ws), engine_horsepower(hp), body_shape(s) {}
   int wheel_size;
   int engine_horsepower;
   std::string_view body_shape;
 };
Copy the code

Not only is it good for eliminating duplicate code fragments, but it’s also better for future extensions, in case you want a BMW.

Generalize further

You can also use a template class:

 template<int wheel_size, int engine_horsepower, char const *const body_shape>
 class generic_builder : public builder {
   public:
   basis::wheel *get_wheel(a) {
     basis::wheel *wheel = new basis::wheel(a); wheel->size = wheel_size;return wheel;
   }
 ​
   basis::engine *get_engine(a) {
     basis::engine *engine = new basis::engine(a); engine->horsepower = engine_horsepower;return engine;
   }
 ​
   basis::body *get_body(a) {
     basis::body *body = new basis::body(a); body->shape = body_shape;returnbody; }};constexpr const char suv_str[] = {"SUV"};
 constexpr const char hatchback_str[] = {"hatchback"};
 ​
 class jeep_builder : public generic_builder<22.400, suv_str> {
   public:
   jeep_builder()
     : generic_builder<22.400, suv_str>() {}
 };
 ​
 class nissan_builder : public generic_builder<16.85, hatchback_str> {
   public:
   nissan_builder()
     : generic_builder<16.85, hatchback_str>() {}
 };
Copy the code

Here we use the constexpr const char suv_str[] trick, which allows us to try to pass literal strings directly in template arguments, so that the above code is completely tempetized.

If you’re already using C++20, STD ::basic_fixed_string gives you the ability to pass string literals directly:

 template<int wheel_size, int engine_horsepower, char const *const body_shape>
 class generic_builder : public builder {
   // ...
 };
 ​
 class jeep_builder : public generic_builder<22.400."SUV"> {
   public:};class nissan_builder : public generic_builder<16.85."hatchback"> {
   public:};Copy the code

If you are interested in the full source code, check out the related source dP-Builder.cc.

Builder Pattern in metaprogramming

We talked about generalizing a Builder in advance, but that was just a little bit of initial refactoring. The situation changes a little bit when Builder Pattern is needed in the template class system, especially when the common code for Builder is pulled up into a single base class, and CRTP technology is needed.

CRTP

CRTP is a C++ idiom that was born much earlier than C++11. In the Visual C++ era, ATL, WTL and, to a lesser extent, MFC used this technique on a large scale, as did ProfUIS.

Simply put, CRTP is designed to implement compile-time polymorphic binding by passing the derived class name to the base class’s template argument, so that the base class can use the static_cast

(*this*) syntax to obtain the ability to “polymorphic” the derived class:

 template <typename derived_t>
 class base{
   public:
   void do_sth(a){
     static_cast<derived_t> (*this*) - >show(a); }void show(a){hicc_debug("base::show");}
 };
 ​
 template <typename T>
 class derived: public base<derived> {
   public:
   T t{};
   void show(a){
     hicc_debug("t: %s", hicc::to_string(t).c_str()); }};Copy the code

Inheritable Builder Pattern

With an understanding of CRTP technology, here is just a schematic snippet:

 namespace hicc::dp::builder::meta {
     class builder_base {
     public:
         builder_base &set_a(a) {
             return (*this);
         }
 ​
         builder_base& on_set_b(a){
             return (*this); }};template<typename derived_t.typename T>
     class builder : public builder_base {
     public:
         derived_t &set_a(a) {
             return *static_cast<derived_t* > (this);
         }
         derived_t &set_b(a) {
             return *static_cast<derived_t* > (this);
         }
 ​
         std::unique_ptr<T> t{}; // the temporary object for builder constructing...
 ​
         // ... more
     };
 ​
     template<typename T>
     class jeep_builder : public builder<jeep_builder<T>, T> {
     public:
         jeep_builder &set_a(a) {
             return *this; }}; }// namespace hicc::dp::builder::meta
 ​
 void test_builder_meta(a) {
     using namespace hicc::dp::builder::meta;
     jeep_builder<int> b{};
     b.set_a(a); }Copy the code

In code, return *static_case

(this) guarantees that derived_t& reference is always returned, This ensures that the chain call to jeep_Builder ().set_a() from a derived class correctly calls the overloaded version of the derived class (which is also an overridden, erasing version), so that polymorphism can be correctly (simulated) without using the virtual function.
*>

Epilogue

A few features rely on syntax support above CXX17, but are not required.

🔚