D.R.Y vs "avoid macros"

c++, dry

Solution

I would not use a macro here. The clue is in your class "Description", which has an extra member function `init`, which the others don't. So you wouldn't be able to use the macro to define it, but you'd instead expand the macro manually and add the extra line.

To me, this is a bigger violation of DRY than just writing out all the class definitions. Almost not repeating yourself, but doing it just for one case, often ends up harder to maintain that repeating yourself consistently. DRY is about finding good abstractions, not just cutting down on boilerplate.

I might replace those constructors, though, with a `SetAttributes` function in class `Element`. That might cut the amount of boilerplate actually required in each derived class, since constructors are the one thing that can't be inherited from the base. But it depends how similar the implementations are of the constructor of each class.

Problem

I am creating my own implementation of XUL in C++ using the Windows API. The fact that the elements are constructed by the XML parser requires that they have identical interfaces, so that we don't need to write custom code for each element constructor. The result is that most of my elements look like this: ``` class Button : public Element { public: static const char * Type() { return "button"; } private: friend class Element; Button(Element * inParent, const AttributesMapping & inAttributesMapping); }; class Label : public Element { public: static const char * Type() { return "label"; } private: friend class Element; Label(Element * inParent, const AttributesMapping & inAttributesMapping); }; class Description : public Element { public: static const char * Type() { return "description"; } virtual bool init(); private: friend class Element; Description(Element * inParent, const AttributesMapping & inAttributesMapping); }; ``` So there is a lot of code duplication here. I wonder if it would be a good idea to replace them with macro calls like this: ``` #define DECLARE_ELEMENT(ElementType, XULName) \ class ElementType : public Element \ { \ public: \ static const char * Type() { return XULName; } \ \ private: \ friend class Element; \ ElementType( \ Element * inParent, \ const AttributesMapping & inAttributesMapping); \ }; \ DECLARE_ELEMENT(Window, "window") DECLARE_ELEMENT(Button, "button") DECLARE_ELEMENT(Label, "label") ``` I haven't completely worked out the concept yet, so a few things are missing here, like the class definitions, and (maybe) the ability to add methods per element. But I'd like to know your opinion of using macros in this situation. Feel free to share your thoughts. EDIT I am now using a small ruby script that generates the source and header files from a set of templates. I enhanced the scripts so that the files are also automatically marked for addition on SVN, and the Visual Studio project file is modified to include the files. This saves me a lot of manual labor. I'm quite happy with this solution. FYI this is what the templates look like now: ``` #ifndef {{ELEMENT_NAME_UPPER}}_H_INCLUDED #define {{ELEMENT_NAME_UPPER}}_H_INCLUDED #include "XULWin/Element.h" namespace XULWin { class {{ELEMENT_NAME}} : public Element { public: static ElementPtr Create(Element * inParent, const AttributesMapping & inAttr) { return Element::Create<{{ELEMENT_NAME}}>(inParent, inAttr); } static const char * Type() { return "{{ELEMENT_TYPE}}"; } virtual bool init(); private: friend class Element; {{ELEMENT_NAME}}(Element * inParent, const AttributesMapping & inAttributesMapping); }; } // namespace XULWin #endif // {{ELEMENT_NAME_UPPER}}_H_INCLUDED ``` CPP document: ``` #include "XULWin/{{ELEMENT_NAME}}.h" #include "XULWin/{{ELEMENT_NAME}}Impl.h" #include "XULWin/AttributeController.h" #include "XULWin/Decorator.h" namespace XULWin { {{ELEMENT_NAME}}::{{ELEMENT_NAME}}(Element * inParent, const AttributesMapping & inAttributesMapping) : Element({{ELEMENT_NAME}}::Type(), inParent, new {{ELEMENT_NAME}}Impl(inParent->impl(), inAttributesMapping)) { } bool {{ELEMENT_NAME}}::init() { return Element::init(); } } // namespace XULWin ```

Original source