简体   繁体   中英

OOP class design in C++

I have simple question about class design in C++.

Lets assume we have the following class:

class DataBase
{
    public:
        DataBase();


        void addEntry(const std::string& key, double value);
        double getEntry(const std::string& key);

    protected:
        std::map<std::string, double> table;
};

There is another class which holds a pointer to an instance of DataBase class:

class SomeClass
{
    protected:
        DataBase *someDataBase;
};

Here I get confused, as two options come to my mind:

  1. Each instance of SomeClass will have a database of its own. In the sense that only the data added by this instance will be present in this database (dedicated databases).
  2. Each instance of SomeClass will be referring to a central database. Data added by any of the instances of SomeClass will be in one single database (a global database).

Question:

  • What is the name of the aforementioned concepts in OOP?
  • How each of the aforementioned approaches can be achieved in C++
  1. Composition
  2. Dependency injection

With Composition you can just have the DataBase as a member:

class SomeClass
{
    protected:
        DataBase someDataBase;
};

With Dependency injection you basically give SomeClass a pointer to your shared DataBase and SomeClass saves a pointer to it. Be careful if you have a multithreaded application, you need to protect writing to the database and maybe reading as well.

class SomeClass
{
    public:
    SomeClass(DataBase* db) : someDataBase(db) {}

    protected:
        DataBase* someDataBase;
};

How you crate and where you store the shared DataBase is up to you.

What you are looking for is the topic of ownership in C++. When I say ownership, I mean who is responsible for managing the memory that holds the object.

In your first example each SomeClass could own its own DataBase .

class SomeClass
{
    private DataBase *db;
    public SomeClass();
    public SomeClass(DataBase* db);
    public ~SomeClass();
}

SomeClass::SomeClass()
{
    this.db = new DataBase();
}    

SomeClass::SomeClass(DataBase* db)
{
    this.db = db;
}

SomeClass::~SomeClass()
{
    delete this.db;
}

This SomeClass either takes ownership of the DataBase given to it or creates its own (practically you usually do one or the other). This means you can pass in a DataBase object (using a concept known as dependency injection):

DataBase *db = new DataBase();
SomeClass sc(db);
sc.doSomeStuffWithDB();

or just let the class create the DataBase object:

SomeClass sc();
sc.doSomeStuffWithDB();

In the above example you don't have to worry about disposal of the DataBase objects, knowing that SomeClass should take care of disposal in its destructor.

In the other scenario you could share a DataBase without having it be disposed of by your SomeClass (whether it's global or not is irrelevant).

class SomeClass
{
    private DataBase *db;
    public SomeClass(DataBase* db);
}

SomeClass::SomeClass(DataBase* db)
{
    this.db = db;
}

Here we could pass multiple SomeClass objects the same DataBase and not have to worry about them being disposed of by any of the objects.

DataBase *db = new DataBase();
SomeClass *sc1 = new SomeClass(db);
SomeClass *sc2 = new SomeClass(db);
sc1.doSomeStuffWithDB();
delete sc1;
sc2.doSomeStuffWithDB();
delete sc2;
delete db;

In this scenario we were able to reuse the DataBase object before disposing of it external to our SomeClass objects. Practically speaking, this disposal could be managed by another class like DataBaseStore , allowing you to have a reliable way to handle and reuse DataBase objects.

Concept n°1 is composition. The Database is part of the SomeClass .
Concept n°2 doesn't have a name as far as I know.

Implementing concept n°1 :

This is actually pretty straightworward : give SomeClass a member of type Database .

class SomeClass
{
    protected:
        DataBase someDataBase;
};

If you need pointers (eg for polymorphism), use a std::unique_ptr :

class SomeClass
{
    protected:
        std::unique_ptr<DataBase> someDataBase;
};

Implementing concept n°2 :

This depends on the rest of the program. If you can, the simplest way is to have a static Database member inside SomeClass :

class SomeClass
{
    protected:
        static DataBase someDataBase;
// or   static std::unique_ptr<DataBase> someDataBase;
};

If Database can't be statically initialized, or if you don't want all of the SomeClass es to share the same Database , you can make use of the object factory pattern :

class SomeClassFactory {
        // Constructors, etc

        SomeClass createSomeClass(/* args */) {
            return SomeClass(_database, /* args */);
        }

    private:
        Database _database;
// or   std::unique_ptr<Database> _database;
};

class SomeClass {
        friend class SomeClassFactory;

        // Private, only the factory can create SomeClass'es
        SomeClass(Database &database, /* args */)
        : database(database) {}

    protected:
        Database &database;
};

Then all SomeClass es created by the same factory will share the same Database .

1 is object composition.

2 needs another Database* declaration in SomeClass declaration, and both pointers must be initialized.

I don't know whether the concept itself has a name, but the members are referred to as static or non-static .
Your 1. would be non-static and your 2. would be static .

As for how to implement this, you seem to know how to go about the non-static variant, and for the static one, just use the static keyword in the member declaration:

class SomeClass
{
    protected:
        static DataBase *someDataBase;
};

Static members can be accessed with :: , like SomeClass::someDataBase .

Initializing static members in C++ is not that straightforward though, see this question .

I will address both of your options:

  1. You have that with your current setup. Each instance of SomeClass will have a pointer to a DataBase class.

  2. In order to achieve this, you have to take DataBase out of your SomeClass , since SomeClass no longer owns the database. You will utilize the singleton design pattern for your DataBase class to say "there is only one instance of this class at any one time".

To do that, you will write your database class like so:

class DataBase
{
    public:
        DataBase();
        static Database * instance(); // This is the function that is used to get the global database for use.

        void addEntry(const std::string& key, double value);
        double getEntry(const std::string& key);

    protected:
        std::map<std::string, double> table;
    private:
        static DataBase * pDataBase;
};

To implement the instance() method:

static DataBase * DataBase::instance()
{
    if (!pDataBase)
        pDataBase = new DataBase();

    return pDataBase;
}

If you would like more information on singletons, read to your heart's content here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM