Этот подход может вдохновить на что-то лучшее, поэтому я думаю, что им следует поделиться.
Сначала создайте класс для членов, которые вы хотите предоставить, используя шаблон построителя, назовем его классами членов и неизменным классом.для построения класса построителя.
Класс членов будет использоваться для:
Класс построителя будет наследоваться от него.
Класс построителя принимает его в своем конструкторе для предоставлениявсе константные значения для константных членов.
Теперь мы хотим создать плавный интерфейс для установки переменных-членов в классе members.
Появляется конфликт: Чтобы сделать члены класса построителя постояннымикласс членов также должен иметь их const.
Но плавная конструкция требует способа предоставления аргументов по одному за раз и в идеале способа управления порядком, в котором можно приводить аргументы.
Пример:
У нас есть класс, представляющий запущенный процесс, для его построения нам нужно знать:
1. (Команда) Какую команду выполнить
2. (Режим) Будет ли требоваться только чтение из стандартного вывода (режим чтения) или оно будет использоваться в интерактивном режиме, требуя возможности записи в егостандартный ввод (режим записи).
3. (цель) Куда будет перенаправлен стандартный вывод?cout, файл или канал?
Для простоты все аргументы будут представлены строками.
Ограничение допустимых методов после каждого предоставленного аргумента отлично подходит для автозаполнения, но для этого необходимо определитьобласть действия с допустимыми методами и в какую область она будет переходить - для каждого этапа построения.
Возможно, лучше было бы использовать пространство имен, зависящее от типа, но я хотел бы повторно использовать класс members, если это возможно.
Каждый интерфейс аргумента представлен классом с методом (ами) для предоставления аргумента конструктора.Метод вернет объект, имеющий следующий интерфейс в качестве типа для предоставления следующего аргумента конструктора или готового объекта компоновщика.
Я повторно использую один и тот же объект для всех этапов строительства, но интерфейс изменяется при статическом приведении.
Мы начнем с создания последнего интерфейса, который клиент будет использовать до того, как будет создан класс построителя, в данном случае (3) целевой аргумент.Позволяет назвать, если после этого:
struct Target : protected members_class
{
builder_class havingTarget( const string& _target )
{
this->target = target;
return builder_class ( *(this) ) ;
}
};
Класс построителя может быть создан путем предоставления ему объекта members_class, который мы наследуем от members_class, поэтому мы можем вернуть созданный класс построителя, указав указатель this.
Перед целевым интерфейсом у нас есть интерфейс для установки аргумента режима:
struct Mode : protected Target
{
Target& inMode( const string& mode )
{
this->mode = mode;
return static_cast<Target&>(*this);
}
};
Режим наследуется от цели, для переключения на целевой интерфейс после предоставления аргумента режима мы приводим указатель this кцелевой интерфейс.
Последний интерфейс Command:
struct Command : protected Mode
{
Mode& withCommand( const string& command )
{
this->command = command;
return static_cast<Mode&>(*this);
}
};
Наследование из режима и возврат указателя this, приведенного к типу режима после получения аргумента команды.
Но мывозник конфликт, класс members используется классом builder для включения членов, и мы хотим, чтобы они были постоянными.Но шаблон построителя использует класс-член таким образом, что каждый аргумент предоставляется по одному за раз.
struct members_class
{
string target;
string mode;
string command;
};
Сначала разрешите включить способ предоставления аргумента шаблона, который будет определять, будут ли члены постоянными илиnot:
template <typename T>
using noop = T;
template< template <typename> class constner = noop >
struct members_dyn_const
По умолчанию аргумент является операцией no, но если указан std :: remove_const_t, члены не будут const, поскольку они объявлены следующим образом:
constner<const string> target;
constner<const string> mode;
constner<const string> command;
два псевдонима для двух способов создания класса:
using members = members_dyn_const<>;
using members_mutable = members_dyn_const<std::remove_const_t>;
Теперь мы хотим включить конструирование класса членов const с изменяемым классом члена:
template< template <typename> class C>
members_dyn_const( members_dyn_const<C> m) : target(m.target), mode(m.mode), command(m.command){}
Но нам также нужнычтобы определить значения по умолчанию для членов, когда он создается как изменяемый класс:
members_dyn_const () : target(""), mode(""), command(""){}
Теперь мы определяем класс построителя, наследуемый от класса членов const, но принимая класс изменяемых членов для построения const:
class base_process : protected members
{
public:
base_process( members_mutable _members ) : members( _members ) {}
Теперь мы можем сконструировать класс построителя с помощью:
process_builder.withCommand( "ls" ).inMode( "read" ).havingTarget( "cout" );
, а неизменяемый класс создается с использованием константных членов.
Я не видел такого подхода, описанного где-либо еще, поэтому я хотелподелитесь им, так как это может послужить источником вдохновения для лучшего способа, но я не могу его порекомендовать, и я действительно не тестировал и не полировал код, кроме подтверждения концепции.
#include <string>
#include <iostream>
using namespace std;
namespace process
{
namespace details
{
template <typename T>
using noop = T;
template< template <typename> class constner = noop >
struct members_dyn_const
{
friend class members_dyn_const< noop >;
template< template <typename> class C>
members_dyn_const( members_dyn_const<C> m) : target(m.target), mode(m.mode), command(m.command){}
members_dyn_const () : target(""), mode(""), command(""){}
protected:
constner<const string> target;
constner<const string> mode;
constner<const string> command;
};
using members = members_dyn_const<>;
using members_mutable = members_dyn_const<std::remove_const_t>;
namespace builder
{
class base_process : protected members
{
public:
base_process( members_mutable _members ) : members( _members ) {}
void test() { /*command = "X";*/ cout << "Executing command: " << command << " in mode " << mode << " having target " << target << endl; }
};
namespace arguments
{
struct Target : protected members_mutable
{
base_process havingTarget( const string& _target )
{
this->target = target;
return base_process( *(this) ) ;
}
};
struct Mode : protected Target
{
auto& inMode( const string& mode )
{
this->mode = mode;
return static_cast<Target&>(*this);
}
};
struct Command : protected Mode
{
Mode& withCommand( const string& command )
{
this->command = command;
return static_cast<Mode&>(*this);
}
};
}
}
}
using details::builder::base_process;
using details::builder::arguments::Command;
Command process_builder = Command();
}
using namespace process;
int main()
try
{
process_builder.withCommand( "ls" ).inMode( "read" ).havingTarget( "cout" ).test();
return 0;
}
catch( exception& e )
{
cout << "ERROR:" << e.what() << endl;
return -1;
}
https://onlinegdb.com/BySX9luim