Home > Net >  Encapsulation along with constructors
Encapsulation along with constructors

Time:02-16

I want the int Medals which I put private to be unable to have negative values, but I don't know how to implement that encapsulation along with constructors. I made it so that each athlete type inherits the Athlete constructor but I don't know where to call the setMedals function for it to work.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

class Athlete {
private:
    int Medals;
public:
    string Name;
    void setMedals(int newMedals) {
        if (newMedals >= 0)
            Medals = newMedals;
    }
    int getMedals() const{
        return Medals;
    }

    virtual string getDescription() = 0;
    Athlete(string _Name, int _Medals) : Name(_Name), Medals(_Medals) {}
};

class Footballer : public Athlete {
public:
    string getDescription() {
        return "Footballer ";
    }
    Footballer(string Name, int Medals) :  Athlete(Name, Medals) {}
};

class Basketballer : public Athlete {
public:
    string getDescription() {
        return "Basketballer ";
    }
    Basketballer(string Name, int Medals) : Athlete(Name, Medals) {}

};

ostream& operator <<(ostream& output, vector<Athlete*> athletes) {
    for (int i = 0; i < athletes.size(); i  ) {
        output << athletes[i]->getDescription() << " " << athletes[i]->Name << ": " << athletes[i]->getMedals() << " Medals" << endl;
    }
    return output;
}

void printAthletes(vector<Athlete*> athletes) {
    sort(athletes.begin(), athletes.end(), [](Athlete* a, Athlete* b) {
        return a->getMedals() > b->getMedals(); });

        cout << athletes;
}

int main() {    
    Footballer Andrew("Andrew", 3), Jack("Jack", 4);
    Basketballer David("David", 5), Rob("Rob", 1);
    vector<Athlete*> Athlete = { &Andrew, &Jack, &David, &Rob };
    printAthletes(Athlete);

    
    return 0;
}

I hope you understand my question cause I don't know how else to phrase it.

CodePudding user response:

From the function:

void setMedals(int newMedals) {
    if (newMedals >= 0)
        Medals = newMedals;
}

It appears you want to set the value of Medals when it is positive and do nothing otherwise. For this to work you will first have to provide a different initializer for Medals. Some value it will take when the value supplied to the constructor is wrong. Note that you can make the member unsigned when anyhow it should store only positive values. Eventually, you can call setMedals in the Athlete constructor. Normally it is preferable to initialize members rather than assignment in the constructor. However, as you want initialization optional assignment, doing both is ok:

class Athlete {
private:
    unsigned Medals = 0;   // <-  initializer here
public:
    string Name;
    void setMedals(int newMedals) {
        if (newMedals >= 0)
            Medals = newMedals;
    }
    int getMedals() const{
        return Medals;
    }

    virtual string getDescription() = 0;
    Athlete(string _Name, int _Medals) : Name(_Name) { // <- no initializer here
        setMedals(_Medals);  // <- call the setter
    }
};

Because the constructor does not provide an initializer for Medals the in-class initializer (= 0) is used. The member is unsigned and can only possibly take positive values, but as you want to check if the value supplied by subclasses or callers of setMedals was negative, the argument must be signed.

CodePudding user response:

tl;dr: Calling non-virtual function inside a constructor is generally fine, although I'd pay attention to it when dealing with larger objects. So, sth like this should do:

class Athlete
{
private:
  unsigned medals{0};
public:
  string name;
  void setMedals(int m) //or just use unsigned...
  {
    if (m >= 0)
      medals = m;
  }
  unsigned getMedals() const{return medals;}

  virtual string getDescription() = 0; //should be const maybe?
  Athlete(string n, int m) : name(move(n))
  {
    setMedals(m); 
  }
};

As for the extended answer: Firstly, a short disclaimer. I wonder if I should be answering that here, or maybe flag it to the moderator to move the topic topic to software engineering or some other, similar SE site, as the discussion is likely to steer away into a general "architectural" one. If either moderators, users or the OP him/herself feel like it, please do so.

Having said that, on topic: the first answer, i.e. to use unsigned int is good enough. However, one may wonder what the whole purpose of getters and setters is, if they are literally a pass-through to access the variable, thus no real encapsulation is there.

For that reason, one may simply consider sth like this (interface is simplified for brevity):

struct Athlete
{
  unsigned medals;
};

If some sort of input validation/processing is needed, e.g. medals cannot exceed 10, one can consider using a setter and getter, e.g.

class Athlete
{
public:
  explicit Athlete(unsigned m)
  : medals {clamp(m, 0, 10)}
  //or throw from constructor
  //depedns what one wants really
  {}
  unsigned getMedals() const { return medals; }
  void setMedals(unsigned m) { medals = clamp(m, 0, 10); }
  //again, you may throw or do anything else
  //clamping is just an example
private:
  unsigned medals;
};

However, a question about object's responsibility arises here. Maybe it's not the Athlete that should care about the number of medals (or whatever the value represents), but the Medals variable iself should be distinct type maintaining its own invariance. Should one decide to chase this approach, it can look like this:

template <typename T, T LO, T HI>
class LimitedInt
{
//all the needed operations
};

struct Athlete
{
  using Medals = LimitedInt<unsigned, 0, 10>;
  Medals medals;
};

Unfortunately, no easy answers here which one is better or worse, it depends on various factors, let alone code style and frameworks used are one of them, e.g. QT uses getters and setters extensively by convention.

CodePudding user response:

I think you should, in your constructor, default the medals value to 0, so that if it is a negative number it won't be assigned to your property. Then in your constructor method, you can call your "setMedals" method.

Hope it was helpful.

CodePudding user response:

You can use unsigned value to make sure that the variable will not have negative values.

unsigned int Medals {0};

And the set function would be:

void setMedals(unsigned int newMedals)
{
    Medals = newMedals
}
  • Related