Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

The C# language also supports a similar construct although with a di erent terminology and syntax: events play the role of signals, and delegates are the slots. Additionally, a delegate can be a local variable, much like a function pointer, while a slot in Qt must be a class member declared as such.

Section 112.3: Factory Pattern

Factory pattern decouples object creation and allows creation by name using a common interface:

class Animal{ public:

virtual std::shared_ptr<Animal> clone() const = 0; virtual std::string getname() const = 0;

};

class Bear: public Animal{ public:

virtual std::shared_ptr<Animal> clone() const override

{

return std::make_shared<Bear>(*this);

}

virtual std::string getname() const override

{

return "bear";

}

};

class Cat: public Animal{ public:

virtual std::shared_ptr<Animal> clone() const override

{

return std::make_shared<Cat>(*this);

}

virtual std::string getname() const override

{

return "cat";

}

};

class AnimalFactory{ public:

static std::shared_ptr<Animal> getAnimal( const std::string& name )

{

if ( name == "bear" )

return std::make_shared<Bear>(); if ( name == "cat" )

return std::shared_ptr<Cat>();

return nullptr;

}

};

Section 112.4: Builder Pattern with Fluent API

The Builder Pattern decouples the creation of the object from the object itself. The main idea behind is that an object does not have to be responsible for its own creation. The correct and valid assembly of a complex object may be a complicated task in itself, so this task can be delegated to another class.

GoalKicker.com – C++ Notes for Professionals

568

Inspired by the Email Builder in C#, I've decided to make a C++ version here. An Email object is not necessarily a very complex object, but it can demonstrate the pattern.

#include <iostream> #include <sstream> #include <string>

using namespace std;

// Forward declaring the builder class EmailBuilder;

class Email

{

public:

friend class EmailBuilder; // the builder can access Email's privates

static EmailBuilder make();

string to_string() const { stringstream stream;

stream << "from: " << m_from

<<"\nto: " << m_to

<<"\nsubject: " << m_subject

<<"\nbody: " << m_body; return stream.str();

}

private:

Email() = default; // restrict construction to builder

string m_from; string m_to; string m_subject; string m_body;

};

class EmailBuilder

{

public:

EmailBuilder& from(const string &from) { m_email.m_from = from;

return *this;

}

EmailBuilder& to(const string &to) { m_email.m_to = to;

return *this;

}

EmailBuilder& subject(const string &subject) { m_email.m_subject = subject;

return *this;

}

EmailBuilder& body(const string &body) { m_email.m_body = body;

return *this;

}

operator Email&&() {

GoalKicker.com – C++ Notes for Professionals

569

return std::move(m_email); // notice the move

}

private:

Email m_email;

};

EmailBuilder Email::make()

{

return EmailBuilder();

}

// Bonus example!

std::ostream& operator <<(std::ostream& stream, const Email& email)

{

stream << email.to_string(); return stream;

}

int main()

{

Email mail = Email::make().from("me@mail.com")

.to("you@mail.com")

.subject("C++ builders")

.body("I like this API, don't you?");

cout << mail << endl;

}

For older versions of C++, one may just ignore the std::move operation and remove the && from the conversion operator (although this will create a temporary copy).

The builder finishes its work when it releases the built email by the operator Email&&(). In this example, the builder is a temporary object and returns the email before being destroyed. You could also use an explicit operation like Email EmailBuilder::build() {...} instead of the conversion operator.

Pass the builder around

A great feature the Builder Pattern provides is the ability to use several actors to build an object together. This is done by passing the builder to the other actors that will each one give some more information to the built object. This is specially powerful when you are building some sort of query, adding filters and other specifications.

void add_addresses(EmailBuilder& builder)

{

builder.from("me@mail.com")

.to("you@mail.com");

}

void compose_mail(EmailBuilder& builder)

{

builder.subject("I know the subject")

.body("And the body. Someone else knows the addresses.");

}

int main()

{

EmailBuilder builder; add_addresses(builder); compose_mail(builder);

GoalKicker.com – C++ Notes for Professionals

570

Email mail = builder; cout << mail << endl;

}

Design variant : Mutable object

You can change the design of this pattern to fit your needs. I'll give one variant.

In the given example the Email object is immutable, i.e., it's properties can't be modified because there is no access to them. This was a desired feature. If you need to modify the object after its creation you have to provide some setters to it. Since those setters would be duplicated in the builder, you may consider to do it all in one class (no builder class needed anymore). Nevertheless, I would consider the need to make the built object mutable in the first place.

GoalKicker.com – C++ Notes for Professionals

571