【问题标题】:C++ Builder pattern with Fluent interface具有 Fluent 接口的 C++ Builder 模式
【发布时间】:2018-10-20 10:47:35
【问题描述】:

我正在尝试使用流畅的接口实现构建器模式,以在 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();

【问题讨论】:

    标签: c++ c++11 c++builder builder fluent


    【解决方案1】:

    这是在 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();
    }
    

    这是一个示例,它只允许在右值引用上更改值(由构建器返回):

    #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
    }
    

    【讨论】:

    • 谢谢沃利!!这是否确保不变性?我相信,对象可以在创建后修改,因为 setDerivedClassVariable() 和 setBaseClassVariable() 在 DerivedClass 和 BaseClass 中都是公共的。
    • 是的,可以在创建后更改。你希望防止这种情况发生吗?
    • 是的,我希望对象是不可变的。构建器模式的一个优点是不变性,我想保留它。
    • 再次感谢沃利。这行得通!但是不能为派生类创建构建器类吗?有没有办法在 C++ 中像我们在 Java 中那样做?
    • @Abee 不变性仅在 Java 等仅供参考的语言中很重要。在 C++ 中,您可以(并且通常会)按值传递事物,所以同样的方式并不重要。
    【解决方案2】:

    这种方法可能会激发一些更好的东西,所以我认为应该分享它。

    首先使用构建器模式为您要提供的成员创建一个类,我们将其称为成员类和不可变类来构建构建器类。

    成员类将用于:

    构建器类将从它继承。

    builder 类在其构造函数中接受它,为 const 成员提供所有 const 值。

    现在我们要创建一个流畅的接口来设置成员类的成员变量。

    出现冲突:要使构建器类成员为 const,成员类也需要使它们为 const。

    但是流畅的构造需要一种每次给出一个参数的方法,理想情况下是一种控制可以给出参数的顺序的方法。

    例子:

    我们有一个代表正在运行的进程的类,要构造它,我们需要知道:

    1.(命令)执行什么命令

    2.(模式)将只需要从标准输出读取(读取模式)还是交互使用它需要写入其标准输入的能力(写入模式)。

    3.(目标)标准输出应该重定向到哪里? cout,文件还是管道?

    为简单起见,所有参数都将由字符串表示。

    在每个提供的参数之后限制有效方法对于自动完成非常有用,但它需要我们使用有效方法定义范围 以及它将过渡到什么范围 - 对于构建的每个阶段。

    也许依赖类型的命名空间会更好,但我想尽可能重用成员类。

    每个参数接口都由一个类表示,该类具有用于提供构造函数参数的方法。 该方法将返回一个对象,该对象具有下一个接口作为其类型,用于提供下一个构造函数参数或完成的构建器对象。

    我在所有构造阶段都重用同一个对象,但接口通过静态转换改变。

    我们首先创建客户端在构建构建器类之前将使用的最后一个接口,在本例中为 (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);
        }
    };  
    

    Mode 继承自 target,为了在提供 mode 参数后切换到目标接口,我们将 this 指针强制转换为目标接口。

    最后一个命令界面:

    struct  Command : protected Mode
    {
        Mode& withCommand( const string& command )
        {
            this->command = command;
            return static_cast<Mode&>(*this);
        }
    };
    

    从模式继承并返回一个在获取命令参数后转换为模式类型的 this 指针。

    但是我们有一个冲突,builder 类使用 members 类来继承成员,我们希望它们是 const。 但是构建器模式使用成员类的方式是每次提供一个参数。

    struct members_class
    {
        string target;
        string mode;
        string command;
    };
    

    首先让我们启用一种提供模板参数的方法,该参数将决定成员是否为 const:

            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" );
    

    一个不可变的类是用 const 成员创建的。

    我还没有在其他任何地方看到过这种方法的描述,所以我想分享它,因为它可能会为更好的方法提供灵感,但我不能真正推荐它,而且除了概念证明之外,我还没有真正测试或完善代码。

    #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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-28
      • 2018-02-06
      • 1970-01-01
      • 2010-12-23
      • 1970-01-01
      • 2010-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多