Object Oriented Programming: Lecture Notes 04
Author: A. El-Gadi/Faculty of Computer Engineering/Tripoli University
Objects of the following triangle class can easily be assigned inconsistent values. The triangle inequality rule, for example, is not guaranteed to hold; because side1, side2 and side3 are public (see the code below).
class Triangle{public:
double side1;
double side2;
double side3;
double perimeter(){return side1+side2+side3;}
};
int main(){Triangle t1;
t1.side1=10; t1.side1=7; t1.side1=1.5;/*triangle inequality rule violated*/
return 0;
}
In what follows, we will advance slowly towards a correct design solution to this problem by exploring intermediate steps and finding out how object oriented principles can solve the emerging issues.
One way to solve this problem is to inform our class users to adhere to the rules that govern a triangle when assigning values to its sides of a triangle through the use of comments. Our class would look like the following:
class Triangle{public:
/*When assigning values to side1, side2, and side3, make sure not to violate the triangle inequality; that is, never let any side be greater than the sum of the two other sides*/
double side1;
double side2;
double side3;
double perimeter(){return side1+side2+side3;}
};
This is of course is a very small improvement because it relies solely on how disciplined the user of the class is. The correct approach, which employs the principles of object oriented programming, is to disallow any direct assignment of the sides of the triangle by hiding them i.e. by making them private, as shown in the code below:
class Triangle{private:
double side1;
double side2;
double side3;
public:
double perimeter(){return side1+side2+side3;}
};
The class above is, of course, useless; as there is no way to access its member variables outside the scope of the class. Therefore, we need to supply public member functions to access our private members setter/getter pairs. It should also be obvious that, setter member functions -aka mutator methods- are the appropriate place to validate data before assigning it to the class members. Accordingly, our class should end up looking like the following:
class Triangle{private:
double side1;
double side2;
double side3;
public:
void setSide1(double x){
if(side2+side3>x&&side2+x>side3&&side3+x>side2){side1=x;}
}
double getSide1(){return side1;}
void setSide2(double x){
if(side1+side3>x&&side1+x>side3&&side3+x>side1){side2=x;}
}
double getSide2(){return side2;}
void setSide3(double x){
if(side2+side1>x&&side2+x>side1&&side1+x>side2){side3=x;}
}
double getSide3(){return side3;}
double perimeter(){return side1+side2+side3;}
};
The class above checks if the triangle inequality is met before setting the value of a supplied side length. The class is still useless, nonetheless, because when a triangle object is created, the initial values of side1, side2,and side3 will hold whatever value there is in the memory allocated for the object. It is not guaranteed that those values would not, by coincidence, be in disagreement with the conditions: side2+side3>x; side1+side3>x; side2+side1>x above. Let us assume, for example, that side1, side2,and side3 hold the values 98765445.322222, 734853.987, 9383929.33002 at the moment of the object's creation. If we wish to set the sides to the values 10.5 7.3 and 14.11 our attempt would fail for every side. To solve this problem, it seems reasonable to find a way to perform validation only when the other two sides have been set. This means that we should keep track of which sides have been set and which ones have not been set. The obvious programming technique to employ in such a situation is include a flag corresponding to each side. The flag value is set to true when the corresponding side is set. The initial value of each flag should be false, but for the time being we will have to defer the treatment of this issue to the next step. Our flags belong naturally to the private part of the class for three reasons: 1) They should be safeguarded from any tampering. 2) They are not an intrinsic part of the concept of a triangle. Their reason of being in the class is that of practicality. 3) They constitute an added complexity that is best kept hidden from the user.
class Triangle{private:
double side1;
bool flag1;
double side2;
bool flag2;
double side3;
bool flag3;
public:
void setSide1(double x){
if(!(flag2&&flag3)){side1=x; flag1=true;}
else{
if(side2+side3>x&&side2+x>side3&&side3+x>side2){side1=x; flag1=true;}}
}
double getSide1(){return side1;}
void setSide2(double x){
if(!(flag1&&flag3)){side2=x; flag2=true;}
else{
if(side1+side3>x&&side1+x>side3&&side3+x>side1){side2=x; flag2=true;}}
}
double getSide2(){return side2;}
void setSide3(double x){
if(!(flag2&&flag1)){side3=x; flag3=true;}
else{
if(side2+side1>x&&side2+x>side1&&side1+x>side2){side3=x; flag3=true;}}
}
double getSide3(){return side3;}
double perimeter(){return side1+side2+side3;}
};
This class should work fine, if only we had a way to initialize the flags to false. Fortunately, OOP allows programmers to control the creation of an object through constructors. Constructors can be thought of as member functions that are executed at the moment of an object's instantiation. They are distinguished by having a name identical to the class' name and no return values. Here is our Triangle class after adding a constructor that initializes all flags to false.
class Triangle{private:
double side1;
bool flag1;
double side2;
bool flag2;
double side3;
bool flag3;
public:
void setSide1(double x){
if(!(flag2&&flag3)){side1=x; flag1=true;}
else{
if(side2+side3>x){side1=x; flag1=true;}}
}
double getSide1(){return side1;}
void setSide2(double x){
if(!(flag1&&flag3)){side2=x; flag2=true;}
else{
if(side1+side3>x){side2=x; flag2=true;}}
}
double getSide2(){return side2;}
void setSide3(double x){
if(!(flag2&&flag1)){side3=x; flag3=true;}
else{
if(side2+side1>x){side3=x; flag3=true;}}
}
double getSide3(){return side3;}
double perimeter(){return side1+side2+side3;}
Triangle(){flag1=false; flag2=false; flag3=false;}
};
int main(){Triangle t; /*Triangle() is executed and thus all flags are set to false*/
t.setSide1(10); t.setSide2(20); t.setSide3(150);
cout<<t.getSide1()<<' '<<t.getSide2()<<' '<<t.getSide3()<<endl;
return 0;
}
We will have more to say about constructors later.
}
In what follows, we will advance slowly towards a correct design solution to this problem by exploring intermediate steps and finding out how object oriented principles can solve the emerging issues.
One way to solve this problem is to inform our class users to adhere to the rules that govern a triangle when assigning values to its sides of a triangle through the use of comments. Our class would look like the following:
class Triangle{public:
/*When assigning values to side1, side2, and side3, make sure not to violate the triangle inequality; that is, never let any side be greater than the sum of the two other sides*/
double side1;
double side2;
double side3;
double perimeter(){return side1+side2+side3;}
};
This is of course is a very small improvement because it relies solely on how disciplined the user of the class is. The correct approach, which employs the principles of object oriented programming, is to disallow any direct assignment of the sides of the triangle by hiding them i.e. by making them private, as shown in the code below:
class Triangle{private:
double side1;
double side2;
double side3;
public:
double perimeter(){return side1+side2+side3;}
};
The class above is, of course, useless; as there is no way to access its member variables outside the scope of the class. Therefore, we need to supply public member functions to access our private members setter/getter pairs. It should also be obvious that, setter member functions -aka mutator methods- are the appropriate place to validate data before assigning it to the class members. Accordingly, our class should end up looking like the following:
class Triangle{private:
double side1;
double side2;
double side3;
public:
void setSide1(double x){
if(side2+side3>x&&side2+x>side3&&side3+x>side2){side1=x;}
}
double getSide1(){return side1;}
void setSide2(double x){
if(side1+side3>x&&side1+x>side3&&side3+x>side1){side2=x;}
}
double getSide2(){return side2;}
void setSide3(double x){
if(side2+side1>x&&side2+x>side1&&side1+x>side2){side3=x;}
}
double getSide3(){return side3;}
double perimeter(){return side1+side2+side3;}
};
The class above checks if the triangle inequality is met before setting the value of a supplied side length. The class is still useless, nonetheless, because when a triangle object is created, the initial values of side1, side2,and side3 will hold whatever value there is in the memory allocated for the object. It is not guaranteed that those values would not, by coincidence, be in disagreement with the conditions: side2+side3>x; side1+side3>x; side2+side1>x above. Let us assume, for example, that side1, side2,and side3 hold the values 98765445.322222, 734853.987, 9383929.33002 at the moment of the object's creation. If we wish to set the sides to the values 10.5 7.3 and 14.11 our attempt would fail for every side. To solve this problem, it seems reasonable to find a way to perform validation only when the other two sides have been set. This means that we should keep track of which sides have been set and which ones have not been set. The obvious programming technique to employ in such a situation is include a flag corresponding to each side. The flag value is set to true when the corresponding side is set. The initial value of each flag should be false, but for the time being we will have to defer the treatment of this issue to the next step. Our flags belong naturally to the private part of the class for three reasons: 1) They should be safeguarded from any tampering. 2) They are not an intrinsic part of the concept of a triangle. Their reason of being in the class is that of practicality. 3) They constitute an added complexity that is best kept hidden from the user.
class Triangle{private:
double side1;
bool flag1;
double side2;
bool flag2;
double side3;
bool flag3;
public:
void setSide1(double x){
if(!(flag2&&flag3)){side1=x; flag1=true;}
else{
if(side2+side3>x&&side2+x>side3&&side3+x>side2){side1=x; flag1=true;}}
}
double getSide1(){return side1;}
void setSide2(double x){
if(!(flag1&&flag3)){side2=x; flag2=true;}
else{
if(side1+side3>x&&side1+x>side3&&side3+x>side1){side2=x; flag2=true;}}
}
double getSide2(){return side2;}
void setSide3(double x){
if(!(flag2&&flag1)){side3=x; flag3=true;}
else{
if(side2+side1>x&&side2+x>side1&&side1+x>side2){side3=x; flag3=true;}}
}
double getSide3(){return side3;}
double perimeter(){return side1+side2+side3;}
};
This class should work fine, if only we had a way to initialize the flags to false. Fortunately, OOP allows programmers to control the creation of an object through constructors. Constructors can be thought of as member functions that are executed at the moment of an object's instantiation. They are distinguished by having a name identical to the class' name and no return values. Here is our Triangle class after adding a constructor that initializes all flags to false.
class Triangle{private:
double side1;
bool flag1;
double side2;
bool flag2;
double side3;
bool flag3;
public:
void setSide1(double x){
if(!(flag2&&flag3)){side1=x; flag1=true;}
else{
if(side2+side3>x){side1=x; flag1=true;}}
}
double getSide1(){return side1;}
void setSide2(double x){
if(!(flag1&&flag3)){side2=x; flag2=true;}
else{
if(side1+side3>x){side2=x; flag2=true;}}
}
double getSide2(){return side2;}
void setSide3(double x){
if(!(flag2&&flag1)){side3=x; flag3=true;}
else{
if(side2+side1>x){side3=x; flag3=true;}}
}
double getSide3(){return side3;}
double perimeter(){return side1+side2+side3;}
Triangle(){flag1=false; flag2=false; flag3=false;}
};
int main(){Triangle t; /*Triangle() is executed and thus all flags are set to false*/
t.setSide1(10); t.setSide2(20); t.setSide3(150);
cout<<t.getSide1()<<' '<<t.getSide2()<<' '<<t.getSide3()<<endl;
return 0;
}
We will have more to say about constructors later.
No comments:
Post a Comment