#include <iostream> #include <string> struct RepeatArgs { RepeatArgs() throw() : n_(), c_(), prefix_() {} RepeatArgs& set_n(size_t n) throw() { n_ = n; return *this; } RepeatArgs& set_c(char c) throw() { c_ = c; return *this; } RepeatArgs& set_prefix(const std::string& prefix) { prefix_ = prefix; return *this; } size_t n_; char c_; std::string prefix_; }; struct RepeatResult { std::string r_; }; void Repeat(const RepeatArgs& args, RepeatResult& res) throw() { res.r_ = args.prefix_; for (size_t i = 0; i < args.n_; ++i) res.r_ += args.c_; } int main() { RepeatResult res; Repeat(RepeatArgs().set_prefix("hello world").set_c('!').set_n(5), res); std::cout << res.r_ << std::endl; return 0; }Some people would probably prefer the
with_
prefix as opposed to set_
for the builder's setters.Note that the args must be a
const RepeatArgs&
type so that the arguments of Repeat() can be built using a temporary object, like seen in the example above.I chose to leave the fields public (as the default for struct) because the fields of the Args struct directly correspond to the arguments that a function would have taken directly, hence the additional getter abstraction is not needed. The purpose of the setters is to allow chaining by returning reference to
*this
. Contrary to a typical builder pattern, there is no need for a final build()
call because the reference can be used directly.Not having
build()
means that argument checking must be done in the function that takes the Args struct. Some may argue that it is better to do argument checking in build()
, but build()
is supposed to return a reference to the Args struct, so there is no good way to report invalid arguments, sort of raising an exception or aborting in the form of assertion failure.The default constructor in this case simply initializes fields by the default constructor of the respective types, with the plain old types size_t and char to
0lu
and '\0'
, and std::string to the empty string. The default constructor could always give them different default values suitable for the Repeat() function in the constructor's initializer list.The Repeat() function can also use the following interface if return value optimization is desired.
RepeatResult Repeat(const RepeatArgs& args) throw();However, the benefit of the former interface is that the return value can be used to indicate error status. For example,
enum RepeatStatus { ok = 0, out_of_memory = 1, ... }; RepeatStatus Repeat(const RepeatArgs& args, RepeatResult& res) throw();The argument builder pattern can also be used for methods that have complex arguments.
class Xyzzy { public: struct FooArgs { ... }; struct FooResult { ... }; enum FooStatus { ... }; FooStatus Foo(const FooArgs& args, FooResult& res) throw(); };The declaration of the
Foo()
method is now very similar to that generated for a protocol buffer service, but better because of the use of fluent builder interface. The idea is that if protocol buffer could generate classes using the named argument builder pattern, then that would make protocol buffer even simpler to use and still very lightweight.
No comments:
Post a Comment