How it works...

The factory pattern provides an object that allocates resources with a means to change the types that the object allocates. To better understand how this pattern works and why it is so useful, let's look at the following example:

class know_it_all
{
public:
auto ask_question(const char *question)
{
(void) question;
return answer("The answer is: 42");
}
};

We start, as shown in the preceding code, with a class called know_it_all that provides an answer when asked a question. In this particular case, no matter what question is asked, it always returns the same answer. The answer is defined as the following:

class answer
{
std::string m_answer;

public:
answer(std::string str) :
m_answer{std::move(str)}
{ }
};

As shown in the preceding, the answer is a simple class that is constructed given a string and stores the string internally. It is important to note in this case that the user of this API cannot actually extract the string that the answer class stores, meaning the use of these APIs is as follows:

int main(void)
{
know_it_all universe;
auto ___ = universe.ask_question("What is the meaning of life?");
}

As shown in the preceding, we can ask a question, and a result is provided, but we are not sure of what result was actually provided. This type of problem exists all of the time in object-oriented programming, and testing this sort of logic is one of the many reasons why entire books have been written on the subject of object mocking. A mock is a fake version of an object designed specifically to validate the output of a test (unlike a fake, which is nothing more than an object that provides test input). In the preceding example, however, a mock still needs a way to be created so that the output of a function can be verified. Enter the factory pattern.

Let's modify the answer class as follows:

class answer
{
std::string m_answer;

public:
answer(std::string str) :
m_answer{std::move(str)}
{ }

static inline auto make_answer(std::string str)
{ return answer(str); }
};

As shown in the preceding code, we have added a static function that allows the answer class to create instances of itself. We have not changed the fact that the answer class doesn't provide the ability to extract the content it holds within, just how the answer class is created. We can then modify the know_it_all class as follows:

template<factory_t factory = answer::make_answer>
class know_it_all
{
public:
auto ask_question(const char *question)
{
(void) question;
return factory("The answer is: 42");
}
};

As shown in the preceding code, the only difference here is that the know_it_all class takes a template parameter for factory_t and uses it to create the answer class instead of creating the answer class directly. factory_t is defined as follows:

using factory_t = answer(*)(std::string str);

This defaults to the static make_answer() function that we added to the answer class. In its most simple form, the preceding example demonstrates the factory pattern. Instead of creating an object directly, we delegate the creation of an object to another object. The preceding implementation doesn't change anything about how the two classes are used, as follows:

int main(void)
{
know_it_all universe;
auto ___ = universe.ask_question("What is the meaning of life?");
}

As shown in the preceding, the main() logic remains unchanged, but this new approach ensures that the know_it_all class focuses on answering questions without worrying about how to create the answer class itself, leaving that task to a different object. The real power behind this subtle change is we can now provide the know_it_all class with a different factory, resulting in a different answer class being returned. To demonstrate this, let's create a new answer class as follows:

class expected_answer : public answer
{
public:
expected_answer(std::string str) :
answer{str}
{
if (str != "The answer is: 42") {
std::cerr << "wrong answer: " << str << ' ';
exit(1);
}

std::cout << "correct answer: " << str << ' ';
}

static inline answer make_answer(std::string str)
{ return expected_answer(str); }
};

As shown in the preceding, we have created a new answer class that sub-classes the original answer class. This new class checks the value it is given during construction and outputs success or failure based on the string it is provided. We can then use this new answer class as follows:

int main(void)
{
know_it_all<expected_answer::make_answer> universe;
auto ___ = universe.ask_question("What is the meaning of life?");
}

The following is the resulting output:

Using the preceding approach, we are not able to ask different questions to see whether the know_it_all class provides the right answers without having to modify the original answer class. For example, suppose the know_it_all class was implemented this way:

template<factory_t factory = answer::make_answer>
class know_it_all
{
public:
auto ask_question(const char *question)
{
(void) question;
return factory("Not sure");
}
};

We tested this version of the know_it_all class, as follows:

int main(void)
{
know_it_all<expected_answer::make_answer> universe;
auto ___ = universe.ask_question("What is the meaning of life?");
}

The result would be the following:

It should be noted that there are several ways to implement the factory pattern. The preceding approach uses a template argument to change how the know_it_all class creates answers, but we could also use a runtime approach as well, as in this example:

class know_it_all
{
std::function<answer(std::string str)> m_factory;

public:
know_it_all(answer(*f)(std::string str) = answer::make_answer) :
m_factory{f}
{ }

auto ask_question(const char *question)
{
(void) question;
return m_factory("The answer is: 42");
}
};

As shown in the preceding, we start with a custom know_it_all constructor that stores a pointer to a factory function, which again, defaults to our answer class, but provides the ability to change the factory if we choose, which is shown as follows:

int main(void)
{
know_it_all universe(expected_answer::make_answer);
auto ___ = universe.ask_question("What is the meaning of life?");
}

If we wanted, we could also add a setter to this class to change this function pointer at runtime.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset