I've been developing a GUI library for Windows (as a personal side project, no aspirations of usefulness). For my main window class, I've set up a hierarchy of option classes (using the Named Parameter Idiom), because some options are shared and others are specific to particular types of windows (like dialogs).
The way the Named Parameter Idiom works, the functions of the parameter class have to return the object they're called on. The problem is that, in the hierarchy, each one has to be a different class -- the createWindowOpts
class for standard windows, the createDialogOpts
class for dialogs, and the like. I've dealt with that by making all the option classes templates. Here's an example:
template <class T>
class _sharedWindowOpts: public detail::_baseCreateWindowOpts {
public: ///////////////////////////////////////////////////////////////
// No required parameters in this case.
_sharedWindowOpts() { };
typedef T optType;
// Commonly used options
optType& at(int x, int y) { mX=x; mY=y; return static_cast<optType&>(*this); }; // Where to put the upper-left corner of the window; if not specified, the system sets it to a default position
optType& at(int x, int y, int width, int height) { mX=x; mY=y; mWidth=width; mHeight=height; return static_cast<optType&>(*this); }; // Sets the position and size of the window in a single call
optType& background(HBRUSH b) { mBackground=b; return static_cast<optType&>(*this); }; // Sets the default background to this brush
optType& background(INT_PTR b) { mBackground=HBRUSH(b+1); return static_cast<optType&>(*this); }; // Sets the default background to one of the COLOR_* colors; defaults to COLOR_WINDOW
optType& cursor(HCURSOR c) { mCursor=c; return static_cast<optType&>(*this); }; // Sets the default mouse cursor for this window; defaults to the standard arrow
optType& hidden() { mStyle&=~WS_VISIBLE; return static_cast<optType&>(*this); }; // Windows are visible by default
optType& icon(HICON iconLarge, HICON iconSmall=0) { mIcon=iconLarge; mSmallIcon=iconSmall; return static_cast<optType&>(*this); }; // Specifies the icon, and optionally a small icon
// ...Many others removed...
};
template <class T>
class _createWindowOpts: public _sharedWindowOpts<T> {
public: ///////////////////////////////////////////////////////////////
_createWindowOpts() { };
// These can't be used with child windows, or aren't needed
optType& menu(HMENU m) { mMenuOrId=m; return static_cast<optType&>(*this); }; // Gives the window a menu
optType& owner(HWND hwnd) { mParentOrOwner=hwnd; return static_cast<optType&>(*this); }; // Sets the optional parent/owner
};
class createWindowOpts: public _createWindowOpts<createWindowOpts> {
public: ///////////////////////////////////////////////////////////////
createWindowOpts() { };
};
It works, but as you can see, it requires a noticeable amount of extra work: a type-cast on the return type for each function, extra template classes, etcetera.
My question is, is there an easier way to implement the Named Parameter Idiom in this case, one that doesn't require all the extra stuff?
Maybe not what you want to hear, but I for one think it's ok to have lots of ugly type-casts and template parameters in library-code that's (more or less) hidden from the client as long as it is safe and makes the life of the client a lot easier. The beauty in library code is not in the code itself, but in the code it enables the clients to write. Take STL for example.
I've also developed a small GUI-library as a personal project with basically the same aspirations as you and some of the code gets pretty ugly in it, but in the end it allows me to write beautiful client code (at least in my (possibly perverted) eyes) and that's what counts IMHO.
How about...?
template <class T>
class _sharedWindowOpts: public detail::_baseCreateWindowOpts {
protected: // (protected so the inheriting classes may also use it)
T & me() { return static_cast<T&>(*this); } // !
public:
// No required parameters in this case.
_sharedWindowOpts() { };
typedef T optType;
// Commonly used options
optType& at(int x, int y) { mX=x; mY=y; return me(); }; // !
// ...
};
Could you just chain the method calls by reverse order of inheritance?
So in your example you'd do something like
Window window = CreateWindow("foo").menu(hmenu).owner(hwnd).at(0,0).background(hbr);
I realize it's not 100% transparent but seems a little easier and almost correct.
I don't know if I'm in love with this answer, but here's a possibility using template argument deduction. NOTE I do not have my compiler on me, I'll double-check it tomorrow unless somebody else out there wants to give it a whirl.
class sharedWindowOpts
{
public:
sharedWindowOpts() {};
// Commonly used options
template <class optType>
static optType& at(int x, int y, optType& opts) { opts.mX=x; opts.mY=y; return opts; };
template <class optType>
static optType& background(HBRUSH b, optType& opts) { opts.mBackground=b; return opts; };
// etc...
}
class createWindowOpts : public sharedWindowOpts
{
public:
createWindowOpts() : sharedwindowOpts() {};
// These can't be used with child windows, or aren't needed
template <class optType>
static optType& menu(HMENU m, optType& opts) { opts.mMenuOrId=m; return opts; };
template <class optType>
static optType& owner(HWND hwnd, optType& opts) { opts.mParentOrOwner=hwnd; return opts; };
}
Then you would call CreateWindow like this:
CreateWindow( createWindowOpts::owner(hwnd,
createWindowOpts::at(0, 100, // can use createWindowOpts because it doesn't hide sharedWindowsOpts::at
createWindowOpts::menu(hmenu, createWindowOpts() ) ) ) );
The obnoxious things about this, of course, are having to use the static method calling syntax and all the extra parentheses. If you replace the static member functions with non-member functions this can be eliminated. It does avoid the type-casting and the extra template classes, though.
Personally, I'd rather have the odd code in the library as with your method, than everywhere the library is being used like in mine.
Templates are hot.
But POP (Plain old Polymorphism) isn't dead.
Why not return a (smart)pointer to the subclass?
I know I'm a year late and a dollar short, but I'll pitch in my solution anyways.
//////// Base..
template<typename DerivedBuilder, typename Options>
class Builder
{
protected:
Builder() {}
DerivedBuilder& me() { return *static_cast<DerivedBuilder*>(this); }
Options options;
};
////////////////////////// A //////////////////////////
class Options_A
{
public:
Options_A() : a(7) {}
int a;
};
class Builder_A;
class A
{
public:
virtual ~A() {}
virtual void print() { cout << "Class A, a:" << a << endl; }
protected:
friend class Builder_A;
A(const Options_A& options) : a(options.a) {}
int a;
};
template<typename DerivedBuilder, typename Options = Options_A>
class BuilderT_A : public Builder<DerivedBuilder, Options>
{
public:
using Builder<DerivedBuilder, Options>::options;
using Builder<DerivedBuilder, Options>::me;
DerivedBuilder& a(int p) { options.a = p; return me(); }
};
class Builder_A : public BuilderT_A<Builder_A>
{
public:
shared_ptr<A> create()
{
shared_ptr<A> obj(new A(options));
return obj;
}
};
////////////////////////// B //////////////////////////
class Options_B : public Options_A
{
public:
Options_B() : b(8) {}
int b;
};
class Builder_B;
class B : public A
{
public:
virtual ~B() {}
virtual void print() { cout << "Class B, a:" << a << ", b:" << b << endl; }
protected:
friend class Builder_B;
B(const Options_B& options) : A(options), b(options.b) {}
int b;
};
template<typename DerivedBuilder, typename Options = Options_B>
class BuilderT_B : public BuilderT_A<DerivedBuilder, Options>
{
public:
using Builder<DerivedBuilder, Options>::options;
using Builder<DerivedBuilder, Options>::me;
DerivedBuilder& b(int p) { options.b = p; return me(); }
};
class Builder_B : public BuilderT_B<Builder_B>
{
public:
shared_ptr<B> create()
{
shared_ptr<B> obj(new B(options));
return obj;
}
};
////////////////////////// C //////////////////////////
class Options_C : public Options_B
{
public:
Options_C() : c(9) {}
int c;
};
class Builder_C;
class C : public B
{
public:
virtual ~C() {}
virtual void print() { cout << "Class C, a:" << a << ", b:" << b << ", c:" << c << endl; }
protected:
friend class Builder_C;
C(const Options_C& options) : B(options), c(options.c) {}
int c;
};
template<typename DerivedBuilder, typename Options = Options_C>
class BuilderT_C : public BuilderT_B<DerivedBuilder, Options_C>
{
public:
using Builder<DerivedBuilder, Options>::options;
using Builder<DerivedBuilder, Options>::me;
DerivedBuilder& c(int p) { options.c = p; return *static_cast<DerivedBuilder*>(this); }
};
class Builder_C : public BuilderT_C<Builder_C>
{
public:
shared_ptr<C> create()
{
shared_ptr<C> obj(new C(options));
return obj;
}
};
///////////////////////////////////////////////////////////////////////////
int main()
{
shared_ptr<A> a = Builder_A().a(55).a(1).create();
a->print();
shared_ptr<B> b = Builder_B().b(99).b(2).a(88).b(4).a(2).b(3).create();
b->print();
shared_ptr<C> c = Builder_C().a(99).b(98).c(97).a(96).c(6).b(5).a(4).create();
c->print();
return 0;
}
/* Output:
Class A, a:1
Class B, a:2, b:3
Class C, a:4, b:5, c:6
*/
C derives from B, and B derives from A. I've repeated parameters to show they can put in any order desired.
来源:https://stackoverflow.com/questions/210939/better-way-to-use-c-named-parameter-idiom