This article is not for beginners, you should already be familiar with the Factory pattern, and you are familiar with common C++17 features.

Factory Pattern

Review the factory pattern and consider implementing a common factory template class to achieve low code on the business side.

FROM: Refactoring Guru

The theory of

The Factory pattern is one of the Creational Patterns.

Creation pattern

The so-called creation pattern mainly includes the following:

  • Abstract Factory Abstract factory pattern. A set of object creation factories with the same topic are individually encapsulated, and multiple sets of different object factories have a unified abstract creation interface, which is called an abstract factory.
  • Builder pattern. The purpose is to construct a complex object, and it is necessary to set up the various properties that it contains in order to make the construction process manageable. A chain invocation is typically used, and after the property is constructed, a starting gun (such as build()) directs the complex object to be eventually constructed as an instance.
  • The Factory method is the classical Factory model. Factory method pattern. There is usually a static create() to create an instance of the object.
  • Prototype Prototype mode. Create a new instance by copying an existing type, clone()
  • Singleton Singleton pattern. There is only one object instance globally.

The above is the classical division of GoF. Almost three decades later, however, there are more creative patterns:

  • Generator Pattern slightly different from Builder
  • Lazy initialization mode. The lazyinit keyword in Kotlin is one of its linguistic support.
  • Object pool mode. If object creation is time-consuming or resource-intensive, create a group of objects at a time, use them as needed, and then put them back into the pool.
  • And so on.

The factory pattern

In this paper, the Factory pattern refers to Factory Method, Factory, Abstract Factory and so on. Taken together, the Factory pattern refers to a programming paradigm in which a Product is created by means of a Factory. The goal is to let the consumer (the business-side code) care less about how the product was made (as simple as factory.create ()) and more about how the product is used.

From another point of view, the factory model has such characteristics: I know the factory can make cleaning products, but it doesn’t matter whether soap or soap. If I want something with a little smell, the factory will make soap for me. If I don’t ask for it, the factory may make soap for me. That is, the interface looks like that, but the factory will produce something that conforms to the interface convention, but not the instance of the class (usually determined by the creation parameter).

In programming practice, the factory pattern is always accompanied by a product root class, which is an interface class that typically contains a series of abstract methods as a business-side operation interface. For complex product families, several categories are derived from this interface class.

Factory Method

The most classical Factory mode is Factory Method, a Pattern first discussed by GoF.

Take Point for example,

namespace cmdr::dp::factory::classical { class classical_factory_method; class Transport { public: virtual ~Transport() {} virtual void deliver() = 0; }; class Trunk : public Transport { float x, y; public: explicit Trunk(double x_, double y_) { x = (float)x_, y = (float)y_; } explicit Trunk(float x_, float y_) { x = x_, y = y_; } ~Trunk() = default; friend class classical_factory_method; void deliver() override { printf("Trunk::deliver()\n"); } friend std::ostream &operator<<(std::ostream &os, const Trunk &o) { return os << "x: " << o.x << " y: " << o.y; }}; class Ship : public Transport { float x, y; public: explicit Ship(double x_, double y_) { x = (float)x_, y = (float)y_; } explicit Ship(float x_, float y_) { x = x_, y = y_; } ~Ship() = default; friend class classical_factory_method; void deliver() override { printf("Ship::deliver()\n"); } friend std::ostream &operator<<(std::ostream &os, const Ship &o) { return os << "x: " << o.x << " y: " << o.y; }}; class classical_factory_method { public: static Transport *create_ship(float r_, float theta_) { return new Ship{r_ * cos(theta_), r_ * sin(theta_)}; } static Transport *create_trunk(float x_, float y_) { return new Trunk{x_, y_}; } static std::unique_ptr<Ship> create_ship_2(float r_, float theta_) { return std::make_unique<Ship>(r_ * cos(theta_), r_ * sin(theta_)); } static std::unique_ptr<Trunk> create_trunk_2(float x_, float y_) { return std::make_unique<Trunk>(x_, y_); } template<typename T, typename... Args> static std::unique_ptr<T> create(Args &&... args) { return std::make_unique<T>(args...) ; }}; } // namespace cmdr::dp::factory::classical void test_factory_classical() { using namespace cmdr::dp::factory::classical; classical_factory_method f; Auto p1 = F.create_trunk (3.1f, 4.2f); std::cout << p1 << '\n'; p1->deliver(); Auto p2 = F. create_ship(3.1f, 4.2f); std::cout << p2 << '\n'; p2->deliver(); Auto p3 = F. create_SHIP_2 (3.1f, 4.2f); std::cout << p3.get() << '\n'; p3->deliver(); Auto P4 = F. crew <Ship>(3.1f, 4.2f); std::cout << p4.get() << '\n'; p4->deliver(); }Copy the code

Classically, the factory method pattern suggests using special factory methods instead of direct calls to object constructors (that is, using the new operator). Don’t worry, the object will still be created with the new operator, just called in the factory method instead. The objects returned by factory methods are often referred to as “products.”

But in modern C++, explicit new delete is rejected (and even naked Pointers are rejected), so the above statement also needs to be tweaked slightly. It might look something like this:

 static std::unique_ptr<Ship> create_ship_2(float r_, float theta_) {
   return std::make_unique<Ship>(r_ * cos(theta_), r_ * sin(theta_));
 }
 static std::unique_ptr<Trunk> create_trunk_2(float x_, float y_) {
   return std::make_unique<Trunk>(x_, y_);
 }
Copy the code

Fine tuning is also required for use, but it can be almost unchanged (due to the encapsulation capabilities of the smart pointer) :

Auto p3 = F. create_SHIP_2 (3.1, 4.2); std::cout << *p3.get() << '\n'; p3->deliver();Copy the code

The case.

commented

The Factory Method pattern is characterized by centralizing the code that creates a subclass object into a separate Factory class. There is usually a special Method corresponding to each subclass, hence the name Factory Method.

The advantages of factory method mode are obvious, but the disadvantages are also many.

The advantage is that it has a centralized creation point, is easy to maintain, and makes it easy or even unnecessary to adjust the business code if there are design adjustments or requirements changes. The code that calls the factory method (hereafter referred to as business code) does not need to know the difference between the actual object returned by different subclasses. Business code treats all products as abstract points. The business code knows that all Point objects provide AT methods, but doesn’t care how they are implemented.

Its disadvantage is that it is relatively stiff, and new products will bring worse consequences. In general, there are several duplicate create() methods associated with the new product, making library version iteration a minor problem or preventing users from adding their own product implementation classes.

To improve the

In Modern C++, with the help of template arguments (C++17 or later) and perfect forwarding, it is possible to cut back on the creation method and mash it up into a single function:

class classical_factory_method { public: template<typename T, typename... Args> static std::unique_ptr<T> create(Args &&... args) { return std::make_unique<T>(args...) ; }};Copy the code

Use it like this:

Auto P4 = f. crew <Ship>(3.1, 4.2); std::cout << *p4.get() << '\n'; p4->deliver();Copy the code

This makes coding very concise and is also friendly for adding a new product, requiring almost no changes to Factroy.

We’ll also provide a more refined Factory template class later that addresses these issues.

Inner

Tweaking the implementation of the Factory Method pattern to scatter the Method for creating new instances among each product class can form the Inner Factory Method. It is generally possible to think of it as just a variation of the Factory Method.

namespace hicc::dp::factory::inner { class Transport { public: virtual ~Transport() {} virtual void deliver() = 0; }; class Trunk : public Transport { float x, y; public: explicit Trunk(float x_, float y_) { x = x_, y = y_; } ~Trunk() = default; void deliver() override { printf("Trunk::deliver()\n"); } friend std::ostream &operator<<(std::ostream &os, const Trunk &o) { return os << "x: " << o.x << " y: " << o.y; } static std::unique_ptr<Trunk> create(float x_, float y_) { return std::make_unique<Trunk>(x_, y_); }}; class Ship : public Transport { float x, y; public: explicit Ship(float x_, float y_) { x = x_, y = y_; } ~Ship() = default; void deliver() override { printf("Ship::deliver()\n"); } friend std::ostream &operator<<(std::ostream &os, const Ship &o) { return os << "x: " << o.x << " y: " << o.y; } static std::unique_ptr<Ship> create(float r_, float theta_) { return std::make_unique<Ship>(r_ * cos(theta_), r_ * sin(theta_)); }}; } // namespace hicc::dp::factory::innerCopy the code

Sometimes, the absence of a centralized Factory class may be appropriate. This is why we emphasize the inner Factory Method pattern.

To improve the

To take advantage of the advantages of a centralized Factory class (centralized entry can facilitate code maintenance and business-level maintenance — such as buried points), even the Inner FM Pattern can provide a helper class/function for specific calls. Consider Modern C++ ‘s SFINAE feature.

In the metaprogramming world, since all product objects always implement create (and the clone method), we can easily construct instances of it using SFINAE techniques (as well as C++17’s parameter package expansion, template arguments, and folded expressions) :

template<typename T, typename... Args> inline std::unique_ptr<T> create_transport(Args &&... args) { return T::create(args...) ; }Copy the code

And no serious coding skills are even required (although there can be some pain in learning tips, after the code is written it seems to be reviewers free, everything is plain, intuitive, most people can understand reviewers and very reviewers friendly).

It will look like this when used:

 void test_factory_inner() {
     using namespace hicc::dp::factory::inner;
     
     auto p1 = create_transport<Trunk>(3.1, 4.2);
     std::cout << *p1.get() << '\n';
     p1->deliver();
 ​
     auto p2 = create_transport<Ship>(3.1, 4.2);
     std::cout << *p2.get() << '\n';
     p2->deliver();
 }
Copy the code

Of course, it could go like this:

Auto p3 = Ship::create(3.1, 4.2); std::cout << *p3.get() << '\n'; p3->deliver();Copy the code

It looks ok.

commented

A comprehensive look at several forms of Factory Method includes not only Inner mode but also orthodox Factory Method mode.

What they have in common is that they draw out an obvious creator as a method. It doesn’t matter where the method is placed, but the purpose of doing so is to remove the tight coupling between the creator and the specific product, so we can adopt various means to achieve better separation. In addition, because we tend to place product creation code in one location, such as a Factory Class, the code is easier to maintain, which is the goal of the single responsibility principle. Turning to business code, we can find that after necessary improvement, the new product type can hardly affect the change of business code, so Factory Method also has advantages in this aspect.

As for the drawbacks, after some of the Modern C++ improvements mentioned above, there are basically no drawbacks, or the fact that the subclasses can be so numerous and separate that the class inheritance system can sometimes swell to the point of being difficult to manage.

Abstract Factory

Due to this naming, we may often confuse it with C++ Abstract class, Virtual Method and Polymorphic object.

But in fact there is no necessary causal relationship between the two.

Good examples are hard to find, so let’s take Refactoring Guru’s example as a brief illustration, and what are some of the Refactoring Guru design patterns we recommend you read based on this article?

Let’s say you’re developing a furniture store simulator. Your code includes classes that represent:

  1. A range of related products, e.gThe Chair ChairSofa SofaCoffee table CoffeeTable
  2. A variety of products. For example, you can useModern ModernVictorianArtDecoEqual style generationThe chairThe sofaandThe coffee table

Therefore, the characteristic of Abstract Factory is that the Factory can uniformly create all products in a certain style, rather than just create products. This ability to control multiple dimensions is unique to the Abstract Factory.

In other words, consider the creation of an abstract factory that introduces multiple styles (Metro, Fluent, Apple, Material Design, etc.) and multiple themes (red, white, dark, etc.) on UI controls.

In addition to multidimensional dimensions, to further promote this understanding, in fact, chairs and sofas are two completely different products, but they both have the common character of “furniture”, so the “woodworking factory”, an abstract factory, is organized together. Therefore, we should note that another feature of abstract factory is that it can create multiple series of products, chair series, table series, etc.

This allows the abstract factory to create a range of Shapes: Point, Line, Arc, Ellipse, Circle, Rect, Rounded Rect, etc., and control their fill colors, border lines, and more.

commented

There is no sample code because the sample code will be obviously large. However, you can easily and thoroughly understand what AF is and how it differs from FM.

In fact, there is some inheritance between abstract factories and the factory method pattern. Abstract factory can be obtained by further extending the factory method. So the line between the two is not so clear. In most architectural designs, it starts with a simple Inner and then reconstructs into a concrete Factory class to form the FM mode. Then it introduces more objects and continues to reconstruct objects for grouping and classification, to extract commonalities and introduce themes, and then it begins to evolve into the abstract factory mode.

Small nodules

Unlike generator/ Builder mode, which is more concerned with building a complex object step by step, factory mode always returns a new instance of an object immediately.

The business code faces the Factory Class, plus the various interface abstraction classes in the production architecture. That’s what it’s called, I don’t need to know what the product is, I just need to know what the ingredients are and how to do it in order to produce the product that meets the requirements.

The factory template class

We certainly don’t stop there.

If we were just making Modern C++ improvements to a few old Patterns, we’d be done with this article. But I don’t want to be satisfied with that, because there are already plenty of Posts like this. So people are not satisfied.

Now, instead of recalling some characteristics, advantages and disadvantages of factory pattern, we can clear our mind and make a new design.

We want to make a generic Factory Pattern Factory template class to eliminate the need to write create Method, which is often boring and repetitive, and we don’t always want to write a Factory class skeleton. Instead, you want to have a set of Products classes followed by an automatic factory.

implementation

First, a series of products classes can actually be used as arguments to a template;

Second, from a type name, at compile time we already have a tool that can get the class name (hicc::debug::type_name

()).

This way, we’ll be able to construct a tuple and stack it with the class name and Creator.

We use tuple because we want to exploit the parameter package expansion syntax of C++17 STD ::tuple.

Without the help of STD ::tuple, we need a fragment like this:

template<typename product_base, typename... products> struct factory { template<typename T> struct clz_name_t { std::string_view id = debug::type_name<T>(); T data; }; auto named_products = {clz_name_t<products>... }; };Copy the code

But this leads to a number of coding problems.

Or maybe you think using STD ::map< STD ::string, STD ::function

> is a good idea?
()>

Well, if you’re really interested, you can try rewriting accordingly.

With such a tuple in hand, we can search for the object’s instance constructor based on the passed class name identifier string, and then complete the object’s instantiation.

So we can have an implementation like this:

namespace hicc::util::factory { template<typename product_base, typename... products> class factory final { public: CLAZZ_NON_COPYABLE(factory); template<typename T> struct clz_name_t { std::string_view id = debug::type_name<T>(); T data; }; using named_products = std::tuple<clz_name_t<products>... >; template<typename... Args> static std::unique_ptr<product_base> create_unique_ptr(const std::string_view &id, Args &&... args) { std::unique_ptr<product_base> result{}; std::apply([](auto &&... it) { ((static_check<decltype(it.data)>()), ...) ; }, named_products{}); std::apply([&](auto &&... it) { ((it.id == id ? result = std::make_unique<decltype(it.data)>(args...) : result), ...) ; }, named_products{}); return result; } template<typename... Args> static product_base *create(const std::string_view &id, Args &&... args) { return create_unique_ptr(id, args...) .release(); } private: template<typename product> static void static_check() { static_assert(std::is_base_of<product_base, product>::value, "all products must inherit from product_base"); }}; // class factory } // namespace hicc::util::factoryCopy the code

The original motivation for this class comes from the C++ Template to implement the Factory pattern-code Review Stack Exchange, but it eliminates the original portability problem and improves the constructor argument problem. So now it’s a real working version, and in cmDR-cxx it will also be available out of the box (CMDR ::util::factory<… >).

In order to create code that takes full advantage of the new features of C++17, we tried several approaches. For now, however, using a compile-time solidified instance of T, packaged with a tuple, is the simplest. Welcome to try and discuss more here.

The result is the implementation code shown above, which has the weakness of having to traverse the named_products{} array, which is often an unwieldy trick, but the code looks nice. In addition, each product will be constructed in advance of an internal instance T data, because there is no other effective means to extract decltype(it. Data), so this means is forced, its disadvantages is a waste of memory, reduce the start speed, but the use of runtime is no side effects.

All in all, assuming you don’t have more than 500 product categories, this waste is probably not unacceptable.

use

Using it is also a little different from the usual factory pattern in that you need to specify the product interface class and all product classes when you instantiate the Factory template class.

 namespace fct = hicc::util::factory;
 using shape_factory = fct::factory<tmp1::Point, tmp1::Point2D, tmp1::Point3D>;
Copy the code

There is a mandatory root class (product_base abstract class) for all of your product classes before the Factory Class can return you an instance of the new product through the polymorphism of the root class.

A practical example might look like this:

namespace tmp1 { struct Point { virtual ~Point() = default; // virtual Point *clone() const = 0; virtual const char *name() const = 0; }; struct Point2D : Point { Point2D() = default; virtual ~Point2D() = default; static std::unique_ptr<Point2D> create_unique() { return std::make_unique<Point2D>(); } static Point2D *create() { return new Point2D(); } // Point *clone() const override { return new Point2D(*this); } const char *name() const override { return hicc::debug::type_name<std::decay_t<decltype(*this)>>().data(); }}; struct Point3D : Point { // Point3D() = default; virtual ~Point3D() = default; static std::unique_ptr<Point3D> create_unique() { return std::make_unique<Point3D>(); } static Point3D *create() { return new Point3D(); } // Point *clone() const override { return new Point3D(*this); } const char *name() const override { return hicc::debug::type_name<std::decay_t<decltype(*this)>>().data(); }}; } // namespace tmp1 void test_factory() { namespace fct = hicc::util::factory; using shape_factory = fct::factory<tmp1::Point, tmp1::Point2D, tmp1::Point3D>; auto *ptr = shape_factory::create("tmp1::Point2D"); hicc_print("shape_factory: Point2D = %p, %s", ptr, ptr->name()); ptr = shape_factory::create("tmp1::Point3D"); hicc_print("shape_factory: Point3D = %p, %s", ptr, ptr->name()); std::unique_ptr<tmp1::Point> smt = std::make_unique<tmp1::Point3D>(); hicc_print("name = %s", smt->name()); // ok }Copy the code

advantages

We think this is an optimal solution to the factory model, because so much of the trivia has been removed or covered up that the amount of new code is now sufficiently low.

You may have noticed that the Create method of the Factory template class requires the business code to provide the class name as the creation identifier. This is by design. Because we need a run-time variable and not a compile-time expansion. Imagine a configuration software requirement where you can select eggplant or cucumber in a drop-down box and draw a symbol in the drawing area. All you need is a run-time variable identifier.

Even if you don’t want this future extensibility, it doesn’t affect your business code.

But you can write your own set of class templates expanded, or even specialized.

Background knowledge

For more information on perfect forwarding, look at the in situ constructor in C++ and perfect forwarding – writing our own variant wrapper class and in situ constructor (2) in C++, but you should also look at the cppreferences information.

Virtual destructor

Note that the important use of virtual destructors is to delete polymorphic instances safely through base class Pointers, which is a mandatory requirement. If you do not implement a virtual destructor, then delete base may not properly free a polymorphic object. Therefore, in most derived class systems a virtual destructor must be declared on the base class, after which the compiler will theoretically generate the corresponding destructor polymorphism for all derived classes.

However, I never challenge the compiler’s capabilities in these cases, but all derived classes write destructor code explicitly. In addition to avoiding potential portability issues, explicit virtual destructors help reduce the mental burden on code readers, as you should.

Once the base declares a virtual destructor, the destructor of a derived class does not have to take the virtual or override keywords; this is automatic.

See the example in the “Using” section above.

Smart Pointers and polymorphism

  • When polymorphic capabilities are needed, Pointers to base classes should be used, and reference references will not be able to perform polymorphic operations

     Point* ptr = new Point3D();
     ptr->name();   // ok
     (*ptr).name(); // mostly bad
    Copy the code
  • Smart pointer wrappers for base Pointers can also be polymorphic correctly:

     std::unique_ptr<Point> ptr = std::make_unique<Point3D>();
     smt->name(); // ok
    Copy the code

    Please pay attention to details.

  • From a derived smart pointer, you can move a bare pointer to a base smart pointer by a move operation or by calling release(). Otherwise, use the above construct and immediately degrade (using STD ::unique_ptr<T,… > move constructor, which actually implies move semantics.

The gossip needle and the intelligence needle

But that’s an excuse for the weak. If you can’t use a pointer and you’re having a conversation about unique versus shared, then you’re probably going to have a problem with it.

We all say we can, but we don’t. We choose what we need. That’s the performance of a master. But if the alternative is less brain-burning, it’s hard not to wonder if this is just sour grapes.

It is also a sign of their ability that people who have experienced pain and beatings before C98 often have a very good way of preventing the needle from going out of control. In fact, modern C++ has no protection against pointer problems. In a system-level programming language like C++/C, you have only one option when talking about Pointers.

It may be fair to say that Modern C++ offers a number of new tricks to help us hide pointer misuses in order to do the fill-in-the-blanks.

But don’t settle for cooking chicken.

so

So, we provide create(…) in our Factory template class. And create_unique_ptr (…). Two output interfaces, no matter which style you are a fan of, pointer, smart pointer, or neutral, can be used on demand without discomfort.

summary

Above, personal point of view, you just have a look.

For more complete source code (to eliminate potential warnings) see the hick-cxx source factory.cc.

HERE cannot determine the juejin editor problem, if the layout is missing, see the original text.

🔚