I am trying to build an OOP design for an election system.
A citizen can nominate themselves to be a contender. So all contenders are citizens. I was thinking of keeping the relation ship IS-A by having contender extend citizen, but I am not able to understand how i will have a citizen nominate themselves then.
Citizen can become followers of contenders and receive a mail about any idea they post. Further more if they receive a mail and were also a contender they would send mail to their followers. Contenders can post at most 3 ideas in their manifesto and low quality contenders are dropped from the election (ie if they have at least 1 idea which has rating <5 from more than 3 people). Also if a person gives a rating > 5 to an idea, they are auto added as followers of the contender
Also if a contender is removed their rating of an idea still counts as they were a citizen also when they made it.
eg.
class Citizen {
public:
bool Citizen::nominate(Eboard* b) {
return b->addContender(Contender(this));
}
}
class Contender: public Citizen {
Citizen* c;
public:
Contender(Citizen* c);
}
(1) I first thought it is fine to not have an ISA relationship. But with the mail constraint, i need to maintain a list of base class objects, as followers can be citizen or contenders.
(2) When system is required to maintain some constraints, take additional actions upon things like candidate giving rating- should the system be passed by reference from the main or should this be directly able to get the system singleton and operate on that.
Searching what can be done I got this helpful answer https://softwareengineering.stackexchange.com/questions/317297/inheritance-vs-additional-property-with-null-value?newreg=f39ae86e18e04b2f8136380610c4ffc8 which seemed to fit with what i could do
Can someone please help me with the best design choice here?
I was not able to get this so i did something like this in which i made the property whether a citizen is a contender which forced me to have functions like postIdea for citizens also.
#include <iostream>
#include <list>
#include <vector>
#include <unordered_map>
using namespace std;
class Citizen;
class Idea;
class Manifesto {
public:
vector<Idea*> ideas;
Citizen* contender;
Manifesto(Citizen* contender) {
this->contender = contender;
}
bool addIdea(Idea* idea) {
if (ideas.size() == 3) {
return false;
}
ideas.push_back(idea);
}
};
//the rating is more than 5, then citizen is added as a follower of the contender.
class Idea {
public:
unordered_map<Citizen*, int> citizenRating;
Citizen* contender;
Idea(Citizen* contender){
this->contender = contender;
}
void setRating(Citizen* citizen, int rating) {
citizenRating[citizen] = rating;
}
bool isRatedLt5ByGt3Voters() {
int cnt = 0;
for(auto iter = citizenRating.begin(); iter != citizenRating.end();iter++) {
if (iter->second < 5) {
cnt++;
}
}
return cnt > 3;
}
};
class Citizen {
public:
bool isContender;
list<Citizen*> followers;
list<Citizen*> following; // for removing
Manifesto* manifesto;
Citizen() {
isContender = false;
manifesto = nullptr;
}
bool nominate() {
cout<<"here";
isContender = true;
manifesto = new Manifesto(this);
}
bool setRating(Idea* idea, int rating) {
idea->setRating(this, rating);
// Ideally logic below this should be done by election system
// not by the citizen, so probably call like Election.setRating(citizen, idea, rating) which will call this
// and do below logic
if (rating > 5) {
makeFollower(manifesto->contender);
}
for(auto idea:manifesto->ideas) {
if (idea->isRatedLt5ByGt3Voters()) {
removeAsContender();
break;
}
}
}
void makeFollower(Citizen* contender) {
contender->followers.push_back(this);
following.push_back(contender);
}
bool postIdea(Idea* idea) {
this->manifesto->addIdea(idea);
sendMail();
}
bool sendMail() {
for(auto el: followers) {
el->getMail();
}
}
bool getMail() {
std::cout<<"Got mail";
sendMail();
}
bool removeAsContender() {
this->isContender = false;
followers.clear();
for (auto iter = following.begin(); iter != following.end(); iter++) {
for (auto iter2 = (*iter)->followers.begin(); iter2 != (*iter)->followers.end(); iter2++) {
if (*iter2 == this) {
(*iter)->followers.erase(iter2);
}
}
}
}
};
int main() {
std::cout << "Hello, World!" << std::endl;
Citizen c;
c.nominate();
c.postIdea(new Idea(&c));
Citizen c2;
c2.makeFollower(&c);
c.postIdea(new Idea(&c));
return 0;
}
In the second version of my attempt i try to separate what the election does from what citizen does. So eg. the election should be responsible for sending mail , or adding followers on citizen's actions. Is this design better?
#include <iostream>
#include <unordered_set>
#include <vector>
#include <unordered_map>
using namespace std;
class Citizen;
class Idea;
class Manifesto {
public:
vector<Idea *> ideas;
Citizen *contender;
Manifesto(Citizen *contender) {
this->contender = contender;
}
bool addIdea(Idea *idea) {
if (ideas.size() == 3) {
return false;
}
ideas.push_back(idea);
}
};
class Idea {
public:
unordered_map<Citizen *, int> citizenRating;
Citizen *contender;
Idea(Citizen *contender) {
this->contender = contender;
}
void setRating(Citizen *citizen, int rating) {
citizenRating[citizen] = rating;
}
bool isRatedLt5ByGt3Voters() {
int cnt = 0;
for (auto iter = citizenRating.begin(); iter != citizenRating.end(); iter++) {
if (iter->second < 5) {
cnt++;
}
}
return cnt > 3;
}
};
class Election;
class Citizen {
public:
bool isContender;
unordered_set<Citizen *> followers;
unordered_set<Citizen *> following; // for removing
Manifesto *manifesto;
Election *election;
Citizen(Election *election) {
isContender = false;
manifesto = nullptr;
this->election = election;
}
// Citizen should be able to get all contenders.
unordered_set<Citizen *> getAllContenders();
// Citizen should be able to nominate self.
bool nominate();
// These are defined outside as they need to dereference Election
// and Election needs to dereference citizen
bool setRating(Idea *idea, int rating) {
idea->setRating(this, rating);
}
bool postIdea(Idea *idea) {
this->manifesto->addIdea(idea);
}
};
class Election {
unordered_set<Citizen *> contenders;
public:
unordered_set<Citizen *> getAllContenders() {
return contenders;
}
void addContender(Citizen *contender) {
contenders.insert(contender);
}
void nominate(Citizen *citizen) {
citizen->nominate();
contenders.insert(citizen);
}
void makeFollower(Citizen *citizen, Citizen *contender) {
contender->followers.insert(citizen);
citizen->following.insert(contender);
}
bool removeAsContender(Citizen *contender) {
contender->isContender = false;
contenders.erase(contender);
contender->followers.clear();
for (auto iter = contender->following.begin(); iter != contender->following.end(); iter++) {
(*iter)->followers.erase(contender);
}
}
public:
bool setRating(Citizen *citizen, Citizen *contender, Idea *idea, int rating) {
citizen->setRating(idea, rating);
if (rating > 5) {
makeFollower(citizen, contender);
}
for (auto idea:contender->manifesto->ideas) {
if (idea->isRatedLt5ByGt3Voters()) {
removeAsContender(contender);
break;
}
}
};
bool postIdea(Citizen *contender, Idea *idea) {
contender->postIdea(idea);
sendMail(contender);
}
bool sendMail(Citizen *contender) {
cout << "in send " << contender->followers.size();
for (auto el: contender->followers) {
cout << "trying ";
getMail(el);
}
}
bool getMail(Citizen *c) {
std::cout << "Got mail";
if (c->isContender) {
sendMail(c);
}
}
};
unordered_set<Citizen *> Citizen::getAllContenders() {
return election->getAllContenders();
}
bool Citizen::nominate() {
isContender = true;
manifesto = new Manifesto(this);
election->addContender(this);
}
int main() {
Election e;
std::cout << "Hello, World!" << std::endl;
Citizen c(&e);
c.nominate(); // citizen can self nominate
Idea *idea = new Idea(&c);
e.postIdea(&c, idea);
Citizen c2(&e);
e.setRating(&c2, &c, idea, 8);
e.postIdea(&c, new Idea(&c));
cout << "CONT" << e.getAllContenders().size();
return 0;
}
I do not like this design as Citizen class should not have anything to do with functions like sendMail
, but since the same object can be made a contender anytime, i can only think of composition which fails due to it also requiring to extend citizen.