views:

48

answers:

1

I'm refactoring a bunch of old code. My primary objective is to decouple the behavior from the database. The current implementation accesses the database directly which makes it hard to test and do things like implement caching layers etc... Up until this point I've been using a combination of dependency inversion and readers/writers with great success, that is until I reached some of our more complex types.

I have an abstract base user class which encapsulates information common to all of our user types. Inheriting from the base class is a number of specializations of the base user type, each of which encapsulate information specific to that type.

If I have a pointer or reference to the base class and I need to persist that user to the database how do I know which writer to use? If I were to use the writer for the base class, information specific to the derived classes would be lost. Putting an abstract getUserType() method on the base class which then must be implemented by each derived seems like a bit of a hack. This might be a case for double dispatch, but the implementation details are a little fuzzy for me.

class User
{
public:
    std::string
    name() const
    {
        return m_name;
    }

    void
    name(const std::string& name)
    {
        m_name = name;
    }

private:
    std::string m_name;
}

class EmailUser : User
{
public:
    std::list<std::string>
    emails() const
    {
        return m_emails;
    }

    void
    emails(const std::string<std::string>& emails)
    {
        m_emails = emails;
    }

private:
    std::set<std::string>   m_emails;
}

class UserWriter
{
public:
    virtual void
    write(User& user) = 0;
}

class DBUserWriter : UserWriter
{
public:
    void
    write(User& user)
    {
        SQLExecute("SOME SQL UPDATE STMT %s", user.name());
    }
}

class DBEmailUserWriter : UserWriter
{
public:
    void
    write(User& user)
    {
        m_baseUserWriter.write(user);
        SQLExecute("SOME SQL UPDATE STMT %s", user.email.front());
    }
private:
    DBUserWriter    m_baseUserWriter;
}
+3  A: 

Here's a sample using the double dispatch implementation specified in the wikipedia page:

#include <iostream>
using namespace std;

class Writer;

class User
{
public:
    std::string name() const { return m_name; }
    void name(const std::string& name) { m_name = name; }
    virtual void accept(Writer & writer) const;

private:
    std::string m_name;
};

class EmailUser : public User
{
public:
    std::string email() const { return m_email; }
    void email(const std::string& email) { m_email = email; }
    virtual void accept(Writer & writer) const;

private:
    std::string m_email;
};

class Writer
{
public:
  void process(const User& user) { user.accept(*this); }
  virtual void write(const User& user) { cout << user.name() << endl; }
  virtual void write(const EmailUser& user) { cout << user.email() << endl; }
};

class SubWriter : public Writer
{
public:
  void process(const User& user) { user.accept(*this); }
  void write(const User& user) { cout << "[" << user.name() << endl; }
  void write(const EmailUser& user) { cout << "[" << user.email() << endl; }
};

void User::accept(Writer & writer) const { writer.write(*this); }
void EmailUser::accept(Writer & writer) const { writer.write(*this); }

int main()
{
  User u;
  EmailUser eu;
  Writer w;
  SubWriter s;

  u.name("Hugo Peixoto");
  eu.email("[email protected]");

  w.process(u);
  w.process(eu);

  s.process(u);
  s.process(eu);

  return 0;
}
Hugo Peixoto
So the only drawback I can see with your solution is that it requires me to use the same writer for every user type making it difficult to mix and match caching mechanisms or data stores.
MrEvil
You can have multiple writers. I updated the code, check if that's what you're looking for.
Hugo Peixoto