Возможные реализации для решения конкретных задач на С++
Factory method
Фабричный метод с созданием нового объекта
В рамках данной реализации появляется возможность избавиться от необходимости создания конкретного объекта в коде.
Идея заключается в создании иерархии классов, объекты которых будут отвечать за создание наших объектов, то есть базовый класс будет иметь метод create виртуальный, а последующие создатели будут реализовывать этот метод.
class Car
{
public:
virtual ~Car() = default;
virtual void drive() = 0;
};
class Sedan : public Car
{
public:
Sedan()
{
cout << "Sedan constructor called" << endl;
}
~Sedan() override
{
cout << "Sedan destructor called" << endl;
}
void drive() override
{
cout << "Driving sedan" << endl;
}
};
class User
{
public:
void use(const shared_ptr<CarCreator>& creator)
{
shared_ptr<Car> car = creator->create();
car->drive();
}
};
# include <iostream>
# include <memory>
using namespace std;
int main()
{
shared_ptr<CarCreator> creator = make_shared<ConcreteCarCreator<Sedan>>();
User{}.use(creator);
}
Фабричный метод без повторного создания объектов. Идиома NVI (Non-Virtual Interface).
Задан базовый абстрактный класс CarCreator с public методом getCar(), protected виртуальным методом createCar() и приватным полем car.
Метод getCar() создает Car, если он уже не создан и возвращает поле car.
Неабстрактный класс ConcreteCarCreator наследуется от CarCreator и определяет метод createCar(), для создания конкретного объекта.
Фабричный метод с использованием идиомы NVI применяется в случаях множественного использования тяжелого (тяжело создаваемого) объекта, при отсутствии необходимости в нескольких объектов данного типа.
class Car
{
public:
virtual ~Car() = default;
virtual void drive() = 0;
};
class Sedan : public Car
{
public:
Sedan()
{
cout << "Sedan constructor called" << endl;
}
~Sedan() override
{
cout << "Sedan destructor called" << endl;
}
void drive() override
{
cout << "Driving sedan" << endl;
}
};
# include <iostream>
# include <memory>
using namespace std;
int main()
{
shared_ptr<CarCreator> creator = make_shared<ConcreteCarCreator<Sedan>>();
unique_ptr<User> user = make_unique<User>();
user->use(creator);
return 0;
}
Фабричный метод с шаблонным CarCreator
В данном случае задан один единственный CarCreator, он является шаблонным, что позволяет избавиться от необходимости создания конкретных креаторов (Creator) для каждого типа объекта.
При данном подходе решение о создании объекта переносится на стадию компиляции и от этого выполнение происходит быстрее, но при добавлении новых типов объектов придется перекомпилировать всю кодовую базу, содержащую данный креатор.
class Car
{
public:
virtual ~Car() = default;
virtual void drive() = 0;
};
class Sedan : public Car
{
public:
Sedan()
{
cout << "Sedan constructor called" << endl;
}
~Sedan() override
{
cout << "Sedan destructor called" << endl;
}
void drive() override
{
cout << "Driving sedan" << endl;
}
};
class User
{
public:
template<NotAbstract TCar>
requires Derivative<TCar, Car>
void use(shared_ptr<CarCreator<TCar>> creator);
};
template<NotAbstract TCar>
requires Derivative<TCar, Car>
void User::use(shared_ptr<CarCreator<TCar>> creator)
{
shared_ptr<Car> car = creator->create();
car->drive();
}
# include <iostream>
# include <memory>
using namespace std;
int main()
{
using SedanCreator_t = CarCreator<Sedan>;
shared_ptr<SedanCreator_t> sedanCreator = make_shared<SedanCreator_t>();
unique_ptr<User> user = make_unique<User>();
user->use(sedanCreator);
}
Фабричный метод со статическим шаблонным методом create
class Car
{
public:
virtual ~Car() = default;
virtual void drive() = 0;
};
class Sedan : public Car
{
private:
int seats;
double weight;
public:
Sedan(int s, double w) : seats(s), weight(w)
{
cout << "Sedan constructor called" << endl;
}
~Sedan() override
{
cout << "Sedan destructor called" << endl;
}
void drive() override
{
cout << "Driving sedan" << endl;
}
};
# include <iostream>
# include <memory>
# include <concepts>
using namespace std;
int main()
{
unique_ptr<User> us = make_unique<User>();
us->use<Sedan>(1, 100.);
}
Фабричный метод с шаблонным базовым классом Creator
BaseCreator - абстрактный базовый класс создаваемых объектов продуктов (в нашем случае Car), от которого наследуется конкретный шаблонный Creator. Параметр шаблона Tbase - это абстрактный базовый класс иерархии классов продуктов.
Идея в создании базового абстрактного шаблонного класса BaseCreator, принимающего абстрактный класс, заключается в возможности не создавать для каждой иерархии продуктов свой базовый класс Creator.
class Car
{
public:
virtual ~Car() = default;
virtual void drive() = 0;
};
class Sedan : public Car
{
private:
int seats;
double weight;
public:
Sedan(int s, double w) : seats(s), weight(w)
{
cout << "Sedan constructor called" << endl;
}
~Sedan() override
{
cout << "Sedan destructor called" << endl;
}
void drive() override
{
cout << "Driving sedan" << endl;
}
};
class SUV : public Car
{
public:
SUV(int s, double w)
{
cout << "Calling the SUV constructor;" << endl;
}
~SUV() override
{
cout << "Calling the SUV destructor;" << endl;
}
void drive() override
{
cout << "Driving SUV;" << endl;
}
};
using BaseCarCreator_t = BaseCreator<Car, int, double>;
class User
{
public:
void use(const shared_ptr<BaseCarCreator_t>& creator)
{
shared_ptr<Car> car = creator->create(1, 100.);
car->drive();
}
};
# include <iostream>
# include <memory>
using namespace std;
using SedanCreator_t = Creator<Car, Sedan, int, double>;
int main()
{
shared_ptr<BaseCarCreator_t> creator = make_shared<SedanCreator_t>();
unique_ptr<User> user = make_unique<User>();
user->use(creator);
}
Фабричный метод и "Cтатический полиморфизм" (CRTP)
Статический полиморфизм (compile-time polymorphism) - это механизм, который позволяет вызывать различные функции или методы с одним и тем же именем, но с разными параметрами или типами данных. Это достигается с помощью перегрузки функций или методов. Компилятор статически выбирает соответствующую функцию или метод на основе типов параметров, указанных при вызове.
Задан базовый шаблонный класс Creator, который имеет метод create(). В методе происходит приведение объекта класса к типу, задаваемым параметром шаблона Tcrt, и вызывается метод по созданию объекта (create_impl). Любой креатор, который будет использован для создания базового Creator должен иметь данную функцию по созданию конкретного объекта.
Подход дает большую гибкость для расширения типов креаторов, не меняя написанного кода. Также содержит все плюсы и минусы шаблонных классов.
class Car
{
public:
virtual ~Car() = default;
virtual void drive() = 0;
};
class Sedan : public Car
{
public:
Sedan()
{
cout << "Sedan constructor called" << endl;
}
~Sedan() override
{
cout << "Sedan destructor called" << endl;
}
void drive() override
{
cout << "Driving sedan" << endl;
}
};
template <typename Tcrt>
class Creator
{
public:
auto create() const
{
return static_cast<const Tcrt*>(this)->create_impl();
}
};
template <Derivative<Car> TCar>
requires NotAbstract<TCar>
class CarCreator : public Creator<CarCreator<TCar>>
{
public:
unique_ptr<Car> create_impl() const
{
return make_unique<TCar>();
}
};
class User
{
public:
template<Derivative<Car> TCar>
void use(const Creator<CarCreator<TCar>>& creator) requires NotAbstract<TCar>
{
auto car = creator.create();
car->drive();
}
};
# include <iostream>
# include <memory>
using namespace std;
int main()
{
Creator<CarCreator<Sedan>> creator;
User{}.use(creator);
}
Фабричный метод с использованием идиомы «стирание типа» (Type erasure)
class User
{
public:
void use(shared_ptr<CarCreator>& creator)
{
shared_ptr<Car> ptr = creator->create();
ptr->drive();
}
};
# include <iostream>
# include <memory>
# include <concepts>
using namespace std;
int main()
{
shared_ptr<CarCreator> creator = make_shared<CarCreator>(Type2Type<Sedan>());
unique_ptr<User> user = make_unique<User>();
user->use(creator);
}
Использование паттерна «фабричный метод» для паттерна Command