Understand how real-world objects can become a part of fundamental elements in the code
Recognize objects from nouns
Generate blueprints for objects and understand classes
Recognize attributes to generate fields
Recognize actions from verbs to generate methods
Work with UML diagrams and translate them into object-oriented code
Organize blueprints to generate different classes
Let's imagine, we have to develop a new simple application, and we receive a description with the requirements. The application must allow users to calculate the areas and perimeters of squares, rectangles, circles, and ellipses.
calculateSquareArea: This receives the parameters of the square and returns the value of the calculated area for the shape
calculateRectangleArea: This receives the parameters of the rectangle and returns the value of the calculated area for the shape
calculateCircleArea: This receives the parameters of the circle and returns the value of the calculated area for the shape
calculateEllipseArea: This receives the parameters of the ellipse and returns the value of the calculated area for the shape
calculateSquarePerimeter: This receives the parameters of the square and returns the value of the calculated perimeter for the shape
calculateRectanglePerimeter: This receives the parameters of the rectangle and returns the value of the calculated perimeter for the shape
calculateCirclePerimeter: This receives the parameters of the circle and returns the value of the calculated perimeter for the shape
However, let's forget a bit about programming languages and functions. Let's recognize the real-world objects from the application's requirements. It is necessary to calculate the areas and perimeters of four elements, that is, four nouns in the requirements that represent real-life objects:
We can design our application by following an object-oriented paradigm. Instead of creating a set of functions that perform the required tasks, we can create software objects that represent the state and behavior of a square, rectangle, circle, and an ellipse. This way, the different objects mimic the real-world shapes. We can work with the objects to specify the different attributes required to calculate their areas and their perimeters.
Now, let's move to the real world and think about the four shapes. Imagine that you have to draw the four shapes on paper and calculate both their areas and perimeters. What information do you require for each of the shapes? Think about this, and then, take a look at the following table that summarizes the data required for each shape:
Length of side
Width and height
Radius (usually labeled as r)
Semi-major axis (usually labeled as a) and semi-minor axis (usually labeled as b)
The data required by each of the shapes is going to be encapsulated in each object. For example, the object that represents a rectangle encapsulates both the rectangle's width and height. Data encapsulation is one of the major pillars of object-oriented programming.
Imagine that you want to draw and calculate the areas of four different rectangles. You will end up with four rectangles drawn, with their different widths, heights, and calculated areas. It would be great to have a blueprint to simplify the process of drawing each rectangle with their different widths and heights.
In object-oriented programming, a class is a blueprint or a template definition from which the objects are created. Classes are models that define the state and behavior of an object. After defining a class that defines the state and behavior of a rectangle, we can use it to generate objects that represent the state and behavior of each real-world rectangle.
Objects are also known as instances. For example, we can say each rectangle object is an instance of the rectangle class.
The following image shows four rectangle instances drawn, with their widths and heights specified: Rectangle #1, Rectangle #2, Rectangle #3, and Rectangle #4. We can use a
rectangle class as a blueprint to generate the four different
rectangle instances. It is very important to understand the difference between a class and the objects or instances generated through its usage. Object-oriented programming allows us to discover the blueprint we used to generate a specific object. Thus, we are able to infer that each object is an instance of the
We already know the information required for each of the shapes. Now, it is time to design the classes to include the necessary attributes that provide the required data to each instance. In other words, we have to make sure that each class has the necessary variables that encapsulate all the data required by the objects to perform all the tasks.
Let's start with the Square class. It is necessary to know the length of side for each instance of this class, that is, for each
square object. Thus, we need an encapsulated variable that allows each instance of this class to specify the value of the length of side.
The variables defined in a class to encapsulate data for each instance of the class are known as attributes or fields. Each instance has its own independent value for the attributes or fields defined in the class.
Square class defines a floating point attribute named
LengthOfSide whose initial value is equal to
0 for any new instance of the class. After you create an instance of the
Square class, it is possible to change the value of the
For example, imagine that you create two instances of the
Square class. One of the instances is named square1, and the other is square2. The instance names allow you to access the encapsulated data for each object, and therefore, you can use them to change the values of the exposed attributes.
Imagine that our object-oriented programming language uses a dot (
.) to allow us to access the attributes of the instances. So,
square1.LengthOfSide provides access to the length of side for the
Square instance named
square2.LengthOfSide does the same for the
Square instance named
You can assign the value
square2.LengthOfSide. This way, each
Square instance is going to have a different value for the
Now, let's move to the Rectangle class. We can define two floating-point attributes for this class:
Height. Their initial values are also going to be
0. Then, you can create two instances of the
rectangle1 and rectangle2.
You can assign the value
rectangle1.Height. This way,
rectangle1 represents a 10 x 20 rectangle. You can assign the value
rectangle2.Height to make the second
Rectangle instance, which represents a 30 x 50 rectangle.
The following table summarizes the floating-point attributes defined for each class:
So far, we have designed four classes and identified the necessary attributes for each of them. Now, it is time to add the necessary pieces of code that work with the previously defined attributes to perform all the tasks. In other words, we have to make sure that each class has the necessary encapsulated functions that process the attribute values specified in the objects to perform all the tasks.
Let's start with the Square class. The application's requirements specified that we have to calculate the areas and perimeters of squares. Thus, we need pieces of code that allow each instance of this class to use the LengthOfSide value to calculate the area and the perimeter.
The functions or subroutines defined in a class to encapsulate the behavior for each instance of the class are known as methods. Each instance can access the set of methods exposed by the class. The code specified in a method is able to work with the attributes specified in the class. When we execute a method, it will use the attributes of the specific instance. A good practice is to define the methods in a logical place, that is, in the place where the required data is kept.
CalculateArea: This returns a floating-point value with the calculated area for the square. The method returns the square of the
LengthOfSideattribute value (LengthOfSide2 or LengthOfSide ^ 2).
CalculatePerimeter: This returns a floating-point value with the calculated perimeter for the square. The method returns the
LengthOfSideattribute value multiplied by
4(4 * LengthOfSide).
Imagine that, our object-oriented programming language uses a dot (
.) to allow us to execute methods of the instances. Remember that we had two instances of the
LengthOfSide equal to
LengthOfSide equal to
20. If we call
square1.CalculateArea, it would return the result of 102, which is
100. On the other hand, if we call
square2.CalculateArea, it would return the result of 202, which is
400. Each instance has a diverse value for the
LengthOfSide attribute, and therefore, the results of executing the
CalculateArea method are different.
If we call
square1.CalculatePerimeter, it would return the result of 4 * 10, which is
40. On the other hand, if we call
square2.CalculatePerimeter, it would return the result of 4 * 20, which is
CalculateArea: This returns a floating-point value with the calculated area for the rectangle. The method returns the result of the multiplication of the
Widthattribute value by the
Heightattribute value (Width * Height).
CalculatePerimeter: This returns a floating-point value with the calculated perimeter for the rectangle. The method returns the sum of two times the
Widthattribute value and two times the
Heightattribute value (2 * Width + 2 * Height).
Remember that, we had two instances of the
rectangle1 representing a 10 x 20 rectangle and
rectangle2 representing a 30 x 50 rectangle. If we call
rectangle1.CalculateArea, it would return the result of 10 * 20, which is
200. On the other hand, if we call
rectangle2.CalculateArea, it would return the result of 30 * 50, which is
1500. Each instance has a diverse value for both the
Height attributes, and therefore, the results of executing the
CalculateArea method are different.
If we call
rectangle1.CalculatePerimeter, it would return the result of 2 * 10 + 2 * 20, which is
60. On the other hand, if we call
rectangle2. CalculatePerimeter, it would return the result of 2 * 30 + 2 * 50, which is
CalculateArea: This returns a floating-point value with the calculated area for the circle. The method returns the result of the multiplication of π by the square of the
Radiusattribute value (π * Radius2 or π * (Radius ^ 2)).
CalculatePerimeter: This returns a floating-point value with the calculated perimeter for the circle. The method returns the result of the multiplication of π by two times the
CalculateArea: This returns a floating-point value with the calculated area for the ellipse. The method returns the result of the multiplication of π by the square of the
Radiusattribute value (π * SemiMajorAxis * SemiMinorAxis).
CalculatePerimeter: This returns a floating-point value with the calculated approximation of the perimeter for the ellipse. Perimeters are very difficult to calculate for ellipses, and therefore, there are many formulas that provide approximations. An exact formula needs an infinite series of calculations. Thus, let's consider that the method returns the result of a formula that isn't very accurate and that we will have to improve on it later. The method returns the result of 2 * π * SquareRoot ((SemiMajorAxis2 + SemiMinorAxis2) / 2).
The following figure shows an updated version of the UML diagram with the four classes, their attributes, and their methods:
So far, our object-oriented solution includes four classes with their attributes and methods. However, if we take another look at these four classes, we would notice that all of them have the same two methods:
CalculatePerimeter. The code for the methods in each class is different, because each shape uses a different formula to calculate either the area or the perimeter. However, the declarations or the contracts for the methods are the same. Both methods have the same name, are always parameterless, and both return a floating-point value.
When we talked about the four classes, we said we were talking about four different geometrical shapes or simply, shapes. Thus, we can generalize the required behavior for the four shapes. The four shapes must declare the
CalculatePerimeter methods with the previously explained declarations. We can create a contract to make sure that the four classes provide the required behavior.
The contract will be a class named
Shape, and it will generalize the requirements for the geometrical shapes in our application. The
Shape class declares two parameterless methods that return a floating-point value:
CalculatePerimeter. Then, we can declare the four classes as subclasses of the Shape class that inherit these definitions, but provide the specific code for each of these methods.
We can define the
Shape class as an abstract class, because we don't want to be able to create instances of this class. We want to be able to create instances of
Ellipse. In this case, the
Shape abstract class declares two abstract methods. We call
CalculatePerimeter abstract methods because the abstract class declares them without an implementation, that is, without code. The subclasses of
Shape implement the methods because they provide code while maintaining the same method declarations specified in the
Shape superclass. Abstraction and hierarchy are the two major pillars of object-oriented programming.
Object-oriented programming allows us to discover whether an object is an instance of a specific superclass. After we changed the organization of the four classes and they became subclasses of the
Shape class, any instance of
Ellipse is also an instance of the
Shape class. In fact, it isn't difficult to explain the abstraction because we are telling the truth about the object-oriented model that represents the real world. It makes sense to say that a rectangle is indeed a shape, and therefore, an instance of a
Rectangle class is a
Shape class. An instance of a
Rectangle class is both a
Shape class (the superclass of the
Rectangle class) and a
Rectangle class (the class that we used to create the object).
When we were implementing the
Ellipse class, we discovered a specific problem for this shape; there are many formulas that provide approximations of the perimeter value. Thus, it makes sense to add additional methods that calculate the perimeter using other formulas.
We can define the following two additional parameterless methods, that is, two methods without any parameter. These methods return a floating-point value to the
Ellipse class to solve the specific problem of the ellipse shape. The following are the two methods:
CalculatePerimeterWithRamanujanII: This uses the second version of a formula developed by Srinivasa Aiyangar Ramanujan
CalculatePerimeterWithCantrell: This uses a formula proposed by David W. Cantrell
This way, the
Ellipse class implements the methods specified in the
Shape superclass. The
Ellipse class also adds two specific methods that aren't included in any of the other subclasses of
The following diagram shows an updated version of the UML diagram with the abstract class, its four subclasses, their attributes, and their methods:
Shape class and its four subclasses. Then, you can create instances of each of the subclasses and call the different methods.
The object-oriented model known as prototype-based programing is also known by other names such as classless programming, instance-based programming, or prototype-oriented programming.
In this chapter, you learned how to recognize real-world elements and translate them into the different components of the object-oriented paradigm: classes, attributes, methods, and instances. You understood the differences between classes (blueprints or templates) and the objects (instances). We designed a few classes with attributes and methods that represented blueprints for real-life objects. Then, we improved the initial design by taking advantage of the power of abstraction, and we specialized in the