2

I'm working on an application which needs to open a database file. There are 2 "versions" of this database: one of them is more general data storage, and the other contains "less" information. That being said, database table structures are different, so I need to issue different queries to get "same" information from each of them.

My first thought is to create a query factory abstract interface, which will only have pure virtual methods returning the queries:

class IQueryFactory
{
public:
    virtual QString getNames() const = 0;
    virtual QString getSurnames() const = 0;
    // ...
};

and have this interface implemented for both "versions" of database:

class GeneralQueryFactory : public IQueryFactory
{
public:
    QString getNames() const override
    {
        return "SELECT DISTINCT Name FROM People;";
    }

    QString getSurnames() const override
    {
        return "SELECT DISTINCT Surname FROM People;";
    }

    // ...
}

class SpecificQueryFactory : public IQueryFactory
{
public:
    QString getNames() const override
    {
        return "SELECT DISTINCT FirstName FROM Employees;";
    }

    QString getSurnames() const override
    {
        return "SELECT DISTINCT LastName FROM Employees;";
    }

    // ...
}

I'm creating an instance of GeneralQueryFactory and SpecificQueryFactory at program startup, then when a database is to be loaded, I check if some table in database, and based on that store the corresponding query factory pointer with the database name in map:

GeneralQueryFactory generalFactory;
SpecificQueryFactory specificFactory;

// ...

if (...)
    dbInfo.add(dbPath, &app.generalFactory);
else
    dbInfo.add(dbPath, &app.specificFactory);

Later I use the query factory as follows (in another function):

auto queryFct = dbInfo.getQueryFactory(dbPath);
QSqlQuery sqlQuery(queryFct->getNames());
sqlQuery.exec();
// ...

I wanna know if this is done correctly. Is the usage of this design pattern correct for this problem? What can I improve? Thanks.

3
  • 1
    There is no factory in your code. You have an abstract class and two concrete classes that implement it (basic polymorphism). And you have a map, which is effectively an implementation of the strategy pattern.
    – David Arno
    Mar 9, 2020 at 8:06
  • Have you considered using the repository pattern instead, i.e. moving the query logic into the classes? To me that would seem like a more natural design, whereas your current classes just describe a name → SQL snippet mapping. You don't even need virtual methods right now, you'd get the same effect with multiple values of struct Queries{QString getNames, getSurnames;}.
    – amon
    Mar 9, 2020 at 9:09
  • @DavidArno I read about the strategy pattern, and my bad that's what I implemented. @amon my dbInfo acts as repository, it has both add and get methods for query strategy. what else should be different? Mar 9, 2020 at 15:32

1 Answer 1

3
  1. Your "Factory" is not a factory (ar least not in the way the GoF book defines it). But I think this is just a misnomer, what you have shown us here is the Strategy pattern. So consider to rename these classes to match the correct GoF terms.

    A factory method is one which makes the decision which strategy object to use, i.e.

     IQueryStrategy &PickQueryStrategy()
     {
         if (...)
           return &app.generalFactory;
        else
           return &app.specificFactory;
     }
    
  2. If providing different sets of static strings is really the only thing which is required here, the strategy pattern is probably overdesigned. Two string lists which are kept in a dictionary, where the database type is some enum used as the key value for this dictionary requires less boilerplate code and would be probably simpler. The dbInfo object then just gets one of those lists assigned, and pick the right query by using another enum as an index.

    The code which uses this list would then look like this:

    auto queryList = dbInfo.getQueryList(dbPath);
    QSqlQuery sqlQuery(queryList[QueryIndex::FirstName]);
    

    However, as we don't know your "real" code, which may be more complex, using the strategy pattern may be perfectly justified, you have to decide this for yourself if there will be some different behaviour for each of the databases required.

10
  • this if block is in the code that opens the file (only at which point I know the database version). So from there I need to set the query strategy. But yes, the idea of map is better. I think the key should be a string, because adding another version of database would be difficult with enum. And how should the concrete query be taken from list (or maybe unordered_set?)? Mar 9, 2020 at 7:46
  • @bb8: Sure you have to set the query strategy with some code like dbInfo.add(PickQueryStrategy()). But in the explanation of the term "Factory method", the part dbInfo.add does not belong, that's why I left it out. If you think a map is sufficient for your case, then go ahead. An enum as a key gives you some type safety, similar to the type safety you got from from strategy classes. If you think you don't need that safety, you can use a string.
    – Doc Brown
    Mar 9, 2020 at 11:02
  • okay, my question was mostly about retrieving the concrete query from the map, i.e. the type of the values in the map, is it list/unordered_set/map? Because I can't see how would I get the necessary query from that list (storage). By name? Mar 9, 2020 at 13:15
  • @bb8: by a second enum (see my edit).
    – Doc Brown
    Mar 9, 2020 at 13:39
  • 1
    @bb8: what about implementing your "query strategy" object, but not with one method per SQL, but with just one method in the interface, which gets an enum as a parameter and returns the appropriate SQL string? That offers you the opportunity to keep the query strings inside the CPP, without declaring them extern.
    – Doc Brown
    Mar 16, 2020 at 6:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.