-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSingleton.cpp
More file actions
166 lines (144 loc) · 4.52 KB
/
Singleton.cpp
File metadata and controls
166 lines (144 loc) · 4.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* @cite Singleton Pattern states that when constructing an object is expensive then that class should only have a single instance available which should then be used throughout the program.
* Aims at controlling the access to resources and avoid their overuse.
*
* @brief Singleton Pattern can be exemplified by a Database Class that loads up the database only once and provides an API to interact with it.
* What happens when we need data from the database to actually test the Singleton Database Class?
*/
#include <fstream>
#include <gtest/gtest.h>
#include <iostream>
#include <map>
#include <vector>
/**
* @brief Interface for Database classes.
*/
class AbstractDatabase
{
public:
virtual int get_population(const std::string &country) = 0;
};
/**
* @brief Database class that loads up database and provides API to interact with it.
*/
class Database : public AbstractDatabase
{
std::map<std::string, int> populations;
/**
* @brief Contructor is private so that no instantiation is done on the client-side.
*/
Database()
{
std::cout << "Loading Database ...\n";
std::ifstream ifs("db.csv");
std::string line;
while (std::getline(ifs, line))
{
std::string country = line.substr(0, line.find_first_of(','));
std::string population = line.substr(line.find_first_of(',') + 1, line.size());
populations[country] = std::stoi(population);
}
}
public:
// Deleted Copy contructor and equal operator to avoid instatiation by copying.
Database(Database &) = delete;
void operator=(Database &) = delete;
// Returns a static database reference.
static Database &get()
{
static Database db;
return db;
}
// Returns Population (in miliions) for a counrty.
int get_population(const std::string &country)
{
return populations[country];
}
};
/**
* @brief RecordFinder class that finds records on the actual Database.
* !@warning Highly Coupled with Databse class.
*/
class RecordFinder
{
public:
int total_population(std::vector<std::string> countries)
{
int res = 0;
for (auto country : countries)
{
res += Database::get().get_population(country);
}
return res;
}
};
/**
* !@warning Unit testing of RecordFinder becomes integration testing as RecordFinder is highly coupled with Database class.
*/
TEST(TotalPopulationTests, RecordFinderTest)
{
RecordFinder rf;
// ! Has to be hard coded using the values from the actual databse making the test inextensible.
std::vector<std::string> countries = {"India", "China"};
// ! Has to be hard coded using the values from the actual databse making the test inextensible.
EXPECT_EQ(rf.total_population(countries), 1438 + 1380);
}
/**
* @brief Dummy Database that allows the unit testing of RecordFinder class.
* Acts a dependency injection to decouple RecordFinder and Database class.
*/
class DummyDatabase : public AbstractDatabase
{
std::map<std::string, int> dummy;
public:
DummyDatabase()
{
dummy["dummy1"] = 1;
dummy["dummy2"] = 4;
dummy["dummy3"] = 3;
}
int get_population(const std::string &key)
{
return dummy[key];
}
};
/**
* @brief BetterRecordFinder class that finds records on the database provided rather than the actual Database.
* Unlike RecordFinder, it is Decoupled from the Database class.
*/
class BetterRecordFinder
{
AbstractDatabase &db;
public:
explicit BetterRecordFinder(AbstractDatabase &db): db{db} {}
int total_population(std::vector<std::string> countries)
{
int res = 0;
for (auto country : countries)
{
res += db.get_population(country);
}
return res;
}
};
/**
* Unit Testing of BetterRecordFinder is possible because it is not coupled with Database class.
*/
TEST(TotalPopulationTests, BetterRecordFinderTest)
{
DummyDatabase db{};
BetterRecordFinder rf(db);
// Values are taken from the dummy database created internally making the test extensible.
std::vector<std::string> keys = {"dummy1", "dummy2"};
EXPECT_EQ(rf.total_population(keys), 5);
}
int main(int argc, char *argv[])
{
// Getting the reference of the singleton object.
auto db = &(Database::get());
std::string country = "India";
std::cout << "Population of " << country << " is " << db->get_population(country) << " million\n";
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
// return 0;
}