2

The problem is as follows:

consider the following class

class data : public base_data
{
  public:
    int a;
    std::string b;
    double c;
    ... // many other members
};

Assume it makes perfect sense to expose the data members of this class.

Now consider there are many such classes, each with different members, perhaps all deriving from the same base class "base_data".

Now, these classes need to be exported, imported, constructed, 'set' and 'get' from other arbitrary representations of data.

For example:

using any_map = boost::unordered_map < std::string, boost::any > ;

is one such representation.

Additionally, all of these operations need to be done in mass, i.e. polymorphically through a collection of base_data* objects.

One solution to this problem is to provide an interface in base_data as follows

class base_data
{
 public:
   virtual void set(const any_map&) = 0;
   virtual any_map get() const = 0;
};

each derived class knows its members, so it knows how to make the translation. additionally derived classes can provide constructors of the form

data(const any_map&) {...}

To allow easily defining an abstract factory pattern.

Another solution to this problem is to provide static translation functions under some namespace for each derived type, e.g.

static data convert(const any_map&);
static any_map convert(const data&);

So we avoid the pollution of the derived classes at the cost of a "less OO" solution and probably the ability to perform these translation operations in mass.

This also makes a lot more sense if we consider the possibility of needing to support many representations other than any_map, e.g.

using boost::ptree;
using json_class;
using xml_class;

But once again, it is not polymorphic.

Most "translation" design patterns that I have read about deal with interfaces, but I have not found one that formally addresses translation/conversion of data in the context of polymorphism.

I am looking for a reference to a design pattern that formally addresses this problem, advice on how to proceed with the implementation and/or pointing out glaring flaws in my approach.

  • You probably want to explain why serialization (e.g. boost::serialization) doesn't work for you. – n. m. could be an AI Jun 10 '15 at 09:39
  • *"So we avoid the pollution of the derived classes"*... is sand on the beach pollution? *"also makes a lot more sense if we consider"*... does it? What's the number of representations got to do with the choice of polymorphic access to the translation functions? Anyway, I suggest you use the ***visitor*** pattern (via virtual dispatch for the derived types), with different visitors for each format you're dealing with: that way you implement visitor once per `base_data`-derived class, and it can be converted into/from any number of formats. – Tony Delroy Jun 10 '15 at 10:19
  • n.m. not sure I understand how boost::serialization solves anything. I'm not an expert on the library but as far as I understand it provides the worst of both solutions - modifying the derived classes and the absence on polymorphic behavior. I could be wrong about the latter but either way it is only one type of representation that is not necessarily the one (or the only one) I will need. – Omri Bashari Jun 10 '15 at 11:23
  • Tony D - I probably used strong words but the basic reason I don't like the existence of any_map as part of the base interface and derived classes is that if I want to change or add another representation I need to violate the Open/Closed principle. Can you provide a code example for how you would use the visitor pattern to solve this problem? – Omri Bashari Jun 10 '15 at 11:26
  • @OmriBashari code posted. Cheers. – Tony Delroy Jun 11 '15 at 13:56

1 Answers1

1

As requested in comments, there's code below illustrating use of the Visitor Pattern as I'd described. Just add additional Visitors for input, JSON, CSV or whatever formats you require. Note that the visitors don't need to be modified to deal with different record structures - the implementation below just needs to know how to handle the different field types involved via virtual dispatch. All this ends up similar to the boost serialisation library, which I recommend looking at too.

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

struct Visitor
{
    typedef const char* Identifier; // or string...

    Visitor(std::ostream& os) : os_(os) { }

    virtual Visitor& pre(Identifier) { return *this; }
    template <typename T> Visitor& operator()(Identifier id, const T& t)
    {
        std::ostringstream oss;
        oss << t;
        return operator()(id, oss.str());
    }
    virtual Visitor& operator()(Identifier, double) = 0;
    virtual Visitor& operator()(Identifier, const std::string&) = 0;
    virtual Visitor& post() { return *this; }

    std::ostream& os_;
};

struct Visitor__XML_Out : Visitor
{
    using Visitor::Visitor;

    Visitor& pre(Identifier i) override
    { os_ << '<' << i << '>'; i_ = i; return *this; }

    Visitor& operator()(Identifier f, double x) override
    { return out(f, x); }

    Visitor& operator()(Identifier f, const std::string& x) override
    { return out(f, x); }

    Visitor& post() override
    { os_ << "</" << i_ << '>'; return *this; }

  private:
    template <typename T>
    Visitor& out(Identifier f, const T& x)
    {
        os_ << '<' << f << '>' << x << "</" << f << '>';
        return *this;
    }

    Identifier i_;
};

struct Base_Data
{
   virtual void visit(Visitor& v) = 0;
};

struct Data : Base_Data
{
    int a_;
    std::string b_;
    double c_;

    Data(int a, const std::string& b, double c)
      : a_(a), b_(b), c_(c)
    { }

    void visit(Visitor& v) override
    {
        v.pre("Data")("a", a_)("b", b_)("c", c_).post();
    }
};

int main()
{
    Data d { 42, "hawk", 8.8 };
    Visitor__XML_Out xml(std::cout);
    d.visit(xml);
    std::cout << '\n';
}

Output:

<Data><a>42</a><b>hawk</b><c>8.8</c></Data>
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Thanks! This solves the data representation abstraction problem. I understand why you made virtual methods for each basic type, but this basically screams "template" (and I understand why you couldn't do that too). Could that be improved? – Omri Bashari Jun 12 '15 at 18:16
  • @OmriBashari You can tune it to taste - to illustrate, I've removed the `Visitor` overload for `int`s and added a template that provided a default handling for arbitrary types. A reason you might still want some hardcoded overrides is that different `Visitor`s may want to use different io manipulators (e.g. `std::fixed`, `std::setw`, `std::setprecision`) or perform escaping/quoting of types (that should really be done for arbitrary strings put into XML, but I couldn't be bothered). Hopefully that's clear. Cheers. – Tony Delroy Jun 13 '15 at 02:12