In the last episode, you learned how to write a class in C++. Now you know how define member variables (data members) and methods, and you know how to implement different aspects of encapsulation, such as define what is publicly accessible for an outside user of the class, and what is inaccessible. In this lesson, we will examine several examples to firmly establish these notions. We will also see that good encapsulation requires implementing a certain number of measures. As a reminder, at this stage, a class is a more "elaborate" structure, in which you can not only define certain properties or fields, called member variables, but also functions, that are called methods. We saw that a class also distinguishes itself from a structure by the fact that it can delimit what is private and inaccessible to the outside world, and what is on the contrary accessible, that is to say public. Here is the definition of our favorite class Rectangle, that served as an introductive illustration to the fundamental concepts of OOP. To define a class, we use the special keyword "class", followed by the name that we chose for our class. Because this name will be a type in this program, it has to be capitalized. A class is usually caracterised by a certain number of data fields that are also called "data members". Here we logically chose to characterize the class Rectangle by the variables height and width, (TN: "hauteur" means "height", and "largeur", "width"), that we chose to define as two doubles. A class is also characterized by a certain number of specific functionalities called methods. So once we have defined these basic elements, the data members and methods, we can now examine which elements are part of the internal implementation and does not need to be known outside the class, and those that constitute the user interface, which the outside world needs to know and to use in the class in question. Here we have made available to the outside world as functionalities 5 methods that allow the values of the data members width and height to be returned and to be modified, and the surface area to be calculated with the values of the width and height. The components of the interface are specified as being "public". And conversely, the members named "private" are considered as implementation details, and this is usually the case for data members. Note that it is normally not necessary to systematically define get/set methods, for each of the class data fields. Here we defined them because at this stage of the course, we only know how to interact with the contents of a class, on a very basic level. That is why we need setters to set the appropriate values of the data members width and height. Respecting guidelines for encapsulation by declaring private that which pertains to implementation, typically the data members of a class, will give the program designers of the class Rectangle the freedom to modify its implementation without effecting those who use the class. For example, if the programmer decides to modify the class's implementation by using an array of 2 elements instead of 2 doubles here, like this, he only needs to internally adapt the implementation of these methods, for instance here, instead of returning "hauteur" the height, he returns "dims[0]". So he only needs to adapt the implementation of these methods, for the code to still function for the user without any modifications being seen. Note also that the external user of the class Rectangle, can only access the data fields of the class through a predetermined interface, which allows how the data is accessed to be closely regulated. Do you know how to improve this code for the class Rectangle with regard to this point? The code here corresponds to the class written by the program designer of the class Rectangle, let's see what happens on the user's side. The user can be the same person who coded the class Rectangle, but not necessarily. Here is a main program, that is client of the class Rectangle, that was defined on the previous slides. So the class Rectangle henceforth defines a new type that can be used just like any other type that we've used up to now. We can declare a variable of type Rectangle, that we usually call an object, or instance, and start to work with it. When this line has been executed, we now have at disposal in the memory, of an object, of type Rectangle, that has two data fields or data members, one for the width and one for the height. So the first rational thing to do, is to initialize these two members, so as to be able to work with a rectangle with real dimensions. The initialization of an object, strictly speaking, is actually an entire topic that we will soon examine in detail. For the time being, let us be content with assigning values to the selected data members. So the assignment of the width and height, can be done simply with input from the keyboard. We will ask the user to introduce a value for the height, a value for the width, that will be read into a variable "lu"
(TN: = "read") declared beforehand, and that will be be set as value for the data member width of the rectangle with the set method that was written for this purpose. So imagine here that the user introduces the value 5.0 into the variable "lu", and, using the method "setHauteur", along with the dot notation, which means set the height of rect at the value of "lu". So we end up in this situation concretely. The same applies for the width like this. Once the rectangle is properly initialized, we can invoke other functionalities on this rectangle, in this case, calculating its area, and again we use dot notation which means here that I calculate the area of the rectangle. In conclusion here is a refined version of the class Rectangle, based on the externalization of method definitions. So here only the method prototypes are kept in the class rectangle, while they are effectively defined outside the class Rectangle. The link between the method and its class is made by the scope resolution operator. The scope resolution operator ( : : ) enables a local name, for example the name of a method, to be linked with a more global name: the name of the class to which the method belongs. The advantage of externalizing is namely that the class declaration is not cluttered with details of method definitions. What is constituted only of method prototypes and data members, is what is called a "class prototype". And what is constituted of the method definitions is called the "class definition". So exactly like we saw for functions, the prototype allows for agreement between the program user and the program designer of the class Rectangle. The definitions are only known by the program designer. The person who uses the class Rectangle only needs know the public section of his prototype. Note that in a judicious design, we will be careful to specify as const the methods that are not mutators, and do not modify the value of data members. Several small details can be noticed here, notably that certain programmers find it useful to use particular naming conventions especially for data members, such as ending the name with an underscore. This is to avoid any possible shadowing with the names of parameters in methods. You will also notice how the programmer names the accessors and mutators here. In the last version of this program, the method that returned the height of the rectangle, was called "getHauteur", but that here it is simply called "hauteur". The same for the width. Likewise, the method that can modify the height of the rectangle by setting a certain value, was called "setHauteur", but now is called "hauteur". Some programmers find this way of naming accessors and mutators more clear, more readable to use. For example, here, if in a program that uses the class Rectangle, I want to print the height of a rectangle, that was declared beforehand, I only need to write "rect.hauteur()". And not "rect.getHauteur()", which is a bit more bulky. Likewise, if in a program I want to modify the height of a rectangle, I can simply write: "rect.hauteur(30)" which is considered less cumbersome by some programmers. Here we are employing the overloading principle: giving two different methods the same name. The same conditions apply for the methods of a class, as for normal functions. So there can be no amibguity concerning which method is called. The method "hauteur" here is a mutator that is distinguished from the method "hauteur" that is an accessor by its list of arguments, which differ from each other, but also by the presence or absence of the keyword const. So no confusion is possible here.