问题
I am trying to implement builder pattern with fluent interface for building the objects in C++. I want the builder to follow CRTP pattern. In Java, I would do something similar to the below code. How do I do the same in C++?
The below is some java code that has a base class and a derived class. The derived class's builder inherits the base class's builder..
// 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();
回答1:
Here is one way to do it in 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();
}
Here is an example that will only allow the values to be changed on an rvalue reference (as returned by the builder):
#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
}
Here is a third solution that uses a Proto
class which is returned by the builder()
. The private member functions have to be specified with using
statements to that they can be made available for public use by Proto
. Finally the build()
function returns the DerivedClass
which does not expose the member functions.
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
}
回答2:
This approach might inspire something better, so I think it should be shared.
First create a class for the members you want to supply using the builder pattern, lets call it the members class and the immutable class to construct the builder class.
The members class will used for:
The builder class will inherit from it.
The builder class accepts it in its constructor, for supplying all the const values for the const members.
Now we want to create an fluent interface for setting the member variables on the members class.
A conflict appears: To make the builder class members const the members class also needs to have them const.
But fluent construction requires a way of giving the arguments one at a time and ideally a way to control what order it is possible to give the arguments in.
Example:
We have a class representing a running process, to construct it we need to know:
1.(Command) What command to execute
2.(Mode) Will only reading from the stdout be required(read mode) or will it be used interactively requiering the ability to write to its stdin(write mode).
3.(Target) Where shall the stdout be redirected to? cout, a file or a pipe?
For simplicity all the arguments will be represented by strings.
Limiting the valid methods after each provided argument is great for autocompletion, but it requires us to define a scope with the valid methods and what scope it will transition to- for each stage in the construction.
Perhaps a type dependent namespace would be better, but I wanted to reuse the members class if possible.
Each argument interface is represented by a class with method(s) for supplying a constructor argument. The method will return an object having the next interface as its type for supplying the next constructor argument or the finished builder object.
I reuse the same object for all construction stages but the interface changes by static casting.
We start by creating the last interface the client will use before the builder class will be constructed, in this case (3) the target argument. Lets name if after that:
struct Target : protected members_class
{
builder_class havingTarget( const string& _target )
{
this->target = target;
return builder_class ( *(this) ) ;
}
};
The builder class is constructable by giving it an members_class object, we inherit from the members_class, so we can return a constructed builder class by supplying the this pointer.
Before the target interface we have the interface for setting the mode argument:
struct Mode : protected Target
{
Target& inMode( const string& mode )
{
this->mode = mode;
return static_cast<Target&>(*this);
}
};
Mode inherits from target, for switching to the target interface after supplying the mode argument, we cast the this pointer to the target interface.
Last the Command interface:
struct Command : protected Mode
{
Mode& withCommand( const string& command )
{
this->command = command;
return static_cast<Mode&>(*this);
}
};
Inheriting from mode and returning a this pointer casted to the mode type after taking the command argument.
But we have a conflict, the members class is used by the builder class for inhereting the members and we want them to be const. But the builder pattern uses the member class in a way where each argument is provided one at a time.
struct members_class
{
string target;
string mode;
string command;
};
First lets enable a way to supply a template argument that will decide if the members will be const or not:
template <typename T>
using noop = T;
template< template <typename> class constner = noop >
struct members_dyn_const
By default the argument is a no operation, but if std::remove_const_t is supplied the members will not be const as they are declared like this:
constner<const string> target;
constner<const string> mode;
constner<const string> command;
two aliases for the two ways of creating the class:
using members = members_dyn_const<>;
using members_mutable = members_dyn_const<std::remove_const_t>;
Now we want to enable construction of a const members class with a mutable member class:
template< template <typename> class C>
members_dyn_const( members_dyn_const<C> m) : target(m.target), mode(m.mode), command(m.command){}
But we also needs to define default values for the members when it is constructed as a mutable class:
members_dyn_const () : target(""), mode(""), command(""){}
Now we define the builder class inhereting from the const members class, but accepting an mutable members class for construction of the const:
class base_process : protected members
{
public:
base_process( members_mutable _members ) : members( _members ) {}
Now we can construct a builder class with:
process_builder.withCommand( "ls" ).inMode( "read" ).havingTarget( "cout" );
and an immutable class is created with const members.
I havent seen this approach described anywhere else, so I wanted to share it as it might provide inspiration for a better way, but I cant really recommend it and I havent really tested or polished the code beyond a proof of concept.
#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
来源:https://stackoverflow.com/questions/50429631/c-builder-pattern-with-fluent-interface