Шаблон C ++ Builder с интерфейсом Fluent - PullRequest
0 голосов
/ 20 мая 2018

Я пытаюсь реализовать шаблон компоновщика с беглым интерфейсом для построения объектов в C ++.Я хочу, чтобы строитель следовал шаблону CRTP.В Java я бы сделал что-то похожее на приведенный ниже код.Как мне сделать то же самое в C ++?

Ниже приведен код Java, который имеет базовый класс и производный класс.Конструктор производного класса наследует конструктор базового класса ..

// Base class
public abstract class BaseClass {

    private final int base_class_variable;

    BaseClass(final Builder <?> builder) {
        this.base_class_variable = builder.base_class_variable;
    }

    public abstract static class Builder <B extends Builder> {

        int base_class_variable;

        public B setBaseClassVariable(final int variable) {
            this.base_class_variable = variable;
            return self();
        }

        protected abstract B self();
    }

}

// Derived class
public final class DerivedClass extends BaseClass {

    private final int derived_class_variable;

    private DerivedClass(final Builder builder) {
        super(builder);
        this.derived_class_variable = derived_class_variable;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder extends BaseClass.Builder <Builder> {

        private int derived_class_variable;

        public Builder setDerivedClassVariable(final int variable) {
            this.derived_class_variable = variable;
            return self();
        }

        public DerivedClass build() {
            return new DerivedClass(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }
}

// Creating an instance of DerivedClass
DerivedClass dInstance = DerivedClass.builder()
    .setBaseClassVariable(5)
    .setDerivedClassVariable(10)
    .build();

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Этот подход может вдохновить на что-то лучшее, поэтому я думаю, что им следует поделиться.

Сначала создайте класс для членов, которые вы хотите предоставить, используя шаблон построителя, назовем его классами членов и неизменным классом.для построения класса построителя.

Класс членов будет использоваться для:

Класс построителя будет наследоваться от него.

Класс построителя принимает его в своем конструкторе для предоставлениявсе константные значения для константных членов.

Теперь мы хотим создать плавный интерфейс для установки переменных-членов в классе 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

0 голосов
/ 20 мая 2018

Вот один из способов сделать это в C ++:

template <typename T>
class Builder {
public:
    static T builder() { return {}; }
    T & build() {return static_cast<T&>(*this); }
};

template <typename T>
class BaseClass : public Builder<T> {
    int base_class_variable;
public:
    T& setBaseClassVariable(int variable) { 
        base_class_variable = variable; 
        return static_cast<T&>(*this); 
    }
};

class DerivedClass : public BaseClass<DerivedClass> {
    int derived_class_variable;
public:
    DerivedClass& setDerivedClassVariable(int variable) { 
        derived_class_variable = variable; 
        return *this; 
    }
};

int main()
{
    // Creating an instance of DerivedClass
    DerivedClass dInstance = DerivedClass::builder()
        .setBaseClassVariable(5)
        .setDerivedClassVariable(10)
        .build();
}

Вот пример, который позволяет изменять значения только по ссылке rvalue (возвращаемой компоновщиком):

#include <utility>

template <typename T>
class Builder {
public:
    static T builder() { return {}; }
    T & build() {return static_cast<T&>(*this); }
};

template <typename T>
class BaseClass : public Builder<T> {
    int base_class_variable;
public:
    T&& setBaseClassVariable(int variable) && { 
        base_class_variable = variable; 
        return std::move(static_cast<T&>(*this)); 
    }
};

class DerivedClass : public BaseClass<DerivedClass> {
    int derived_class_variable;
public:
    DerivedClass&& setDerivedClassVariable(int variable) && { 
        derived_class_variable = variable; 
        return std::move(*this); 
    }
};

int main()
{
    // Creating an instance of DerivedClass
    DerivedClass dInstance = DerivedClass::builder()
        .setBaseClassVariable(5)
        .setDerivedClassVariable(10)
        .build();

    //dInstance.setBaseClassVariable(34); // will not compile
}

Вот третье решение, в котором используется класс Proto, возвращаемый builder().Закрытые функции-члены должны быть указаны с помощью операторов using, чтобы их можно было сделать доступными для общего пользования с помощью Proto.Наконец, функция build() возвращает DerivedClass, который не предоставляет функции-члены.

template<typename T>
class BaseClass;

class DerivedClass;

template <typename T>
class Proto : public T {
public:
    using BaseClass<T>::setBaseClassVariable;
    using T::setDerivedClassVariable;
};

template <typename T>
class Builder {
public:
    static Proto<T> builder() { return {}; }
    T& build() { return static_cast<T&>(*this); }
};

template <typename T>
class BaseClass : public Builder<T> {
    int base_class_variable;
    Proto<T>& setBaseClassVariable(int variable) {
        base_class_variable = variable;
        return static_cast<Proto<T>&>(*this);
    }
    friend class Proto<T>;
};

class DerivedClass : public BaseClass<DerivedClass> {
    int derived_class_variable;
    Proto<DerivedClass>& setDerivedClassVariable(int variable) {
        derived_class_variable = variable;
        return static_cast<Proto<DerivedClass>&>(*this);
    }
    friend class Proto<DerivedClass>;
};

int main()
{
    // Creating an instance of DerivedClass
    DerivedClass dInstance = DerivedClass::builder()
        .setBaseClassVariable(5)
        .setDerivedClassVariable(10)
        .build();

    //dInstance.setBaseClassVariable(34); // cannot access private member
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...