Organizing the blueprints – classes
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: CalculateArea
and 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 CalculateArea
and 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: CalculateArea
and 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.
Tip
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 Square
, Rectangle
, Circle
, or Ellipse
. In this case, the Shape
abstract class declares two abstract methods. We call CalculateArea
and 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 Square
, Rectangle
, Circle
, or 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 RamanujanCalculatePerimeterWithCantrell
: 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 Shape
.
The following diagram shows an updated version of the UML diagram with the abstract class, its four subclasses, their attributes, and their methods: