Mendes
Mendes

Reputation: 18451

How to implement pure virtual functions with different parameter structures

I'm building a class with pure virtual functions called Database. The idea is to have a class that handles all the database interfaces (ie: open and close) and can be used on my business layers.

The Database class will be implemented in several 'flavours' for different databases, like mySqlDatabase and OracleDatabase.

I imagined Database having pure virtual methods with no code - just a header file as follows:

Database.hpp

class Database {

    public:
        Database();
        virtual ~Database();

        virtual void open(const std::string databasename) = 0;
        virtual void open(const std::string databasename, const std::string username, const std::string password) = 0;
        virtual void open(const std::string databasename, const std::string schema, const std::string username, const std::string password) = 0;

.
<Other stuff>
.

}

The three open variations are there to support different database connection requirements, from the simplest one (like Sqlite3 that needs only a filename), to Oracle (that needs all of that variables to connect).

I have some questions about the implementations (let's take oracle for example):

a) Shall I need to redeclare the virtual methods again on the derived class header file, like:

    class  OracleDatabase : public Database {

    public:
        OracleDatabase ();
        virtual ~OracleDatabase ();

        void open(const std::string databasename);
        void open(const std::string databasename, const std::string username, const std::string password);
        void open(const std::string databasename, const std::string schema, const std::string username, const std::string password);
}

b) How do I structure the implementation of the open methods in the derived class (let´s take Sqlite3)?

void Sqlite3Database::open(const std::string databasename){
          ...do some stuff...
}

void Sqlite3Database::open(const std::string databasename, const std::string username, const std::string password) {
          ...do some stuff...
    }

void Sqlite3Database::open(const std::string databasename, const std::string schema, const std::string username, const std::string password) {
          ...do some stuff...
    }

Am I using the right strategy? I've been browsing around virtual and pure virtual strategies and think this is the best approach for my problem.

Any suggestions/hints?

OBS: I'm coming from C# world so I do apologize if there is some misconception here.

Upvotes: 2

Views: 702

Answers (1)

Ekleog
Ekleog

Reputation: 1054

For writing query functions (ie. same interface for all databases), pure virtual functions are the way to go.

Here, you are trying to write an open function, for which you might want to consider the Factory Design Pattern: you write your Database withour any open function; and you write a function such as static std::unique_ptr<Database> Sqlite3Database::open(/*...*/).

Using a virtual function like the one you are advocating is not a good idea: anyway you have 3 different functions that completely depend on the database that is used; and worse, your mother class depends on its children: to add a new database with another logging scheme, you have to add a function prototype to Database.

Another way to go would be to use a pure virtual function (preferably protected and called from constructor to preserve RAII; and following the NVI idiom) that takes as argument an initialization string such as the one used by PDO. Not exactly the same as anyway the database type can be inferred from the type instantiated, but the idea is to keep a single argument so as not to have multiple versions of open

(Old answer kept for the principles it tried to explain)

Actually you can do much easier: forget about open, and just do all of your initialization inside Sqlite3Database::Sqlite3Database(/* ... */).

After all, there is no way you can open a database without knowing which kind of DB it is (as you have to know a username/password, and even more: you have to know what arguments are required), so there is no sense in trying to make a virtual pure function out of this.

So, an example of what you could do:

class Database {
    public virtual void create(/* ... */) = 0;
    // ...
};

class Sqlite3Database : public Database {
    Sqlite3Database(string filename);
    public virtual void create(/* ... */) override;
    // ...
};

class MySqlDatabase : public Database {
    MySqlDatabase(int host, short port, string username, string password);
    public virtual void create(/* ... */) override;
};

Upvotes: 3

Related Questions