In the object-oriented paradigm, you think in terms of objects and classes. A class can be thought of as a template that acts as a basis for creating objects of that type. For example, a Vehicle class can represent real-world automobiles with the following attributes:
vin (a unique vehicle identification number)
manufacturer
model
modelYear
finalAssemblyCountry
A concrete instance of Vehicle, representing a real-world vehicle, could be:
vin: WAUZZZ8K6AA123456
manufacturer: Audi
model: A4
modelYear: 2009
finalAssemblyCountry:Germany
Let's put these attributes in action in Scala.
Go to the Scala/SBT console and write the following lines of code:
Define Vehicle Scala class as per the preceding specifications:
scala> class Vehicle(vin: String, manufacturer: String, model: String, modelYear: Int, finalAssemblyCountry: String) defined class Vehicle
Create an instance of Vehicle class:
scala> val theAuto = new Vehicle("WAUZZZ8K6AA123456", "Audi", "A4", 2009, "Germany") theAuto: Vehicle = Vehicle@7c6c2822
Following is the IntelliJ Scala worksheet:
The object-oriented approach puts data and behavior together. The following are core tenets of object-oriented programming:
Encapsulation: It provides a mechanism to shield implementation details and internal properties
Abstraction: It provides constructs such as classes and objects to model real-world problems
Inheritance: It provides constructs to reuse implementation and behavior using subclassing
Polymorphism: It facilitates mechanisms for an object to react to a message based on its actual type
Let's look at encapsulation and abstraction in Scala Read-Evaluate-Print-Loop (REPL). We'll use Scala'sconstruct class to define a template for a real-worldVehicle, as shown in the following code:
Let us define Vehicle class, this is an example of abstraction because we are taking real-world complex entities and defining a simple model to represent them. When internals of implementations is hidden then it is an example of encapsulation. Publicly visible methods define behavior:
scala> class Vehicle(vin: String, manufacturer: String, model: String, modelYear: Int, finalAssemblyCountry: String) { // class is an example of abstraction | private val createTs = System.currentTimeMillis() // example of encapsulation (hiding internals) | def start(): Unit = { println("Starting...") } // behavior | def stop(): Unit = { println("Stopping...") } // behavior | } defined class Vehicle
Now let create an instance of Vehicle. This is also an abstraction because Vehicle class is a template representing a simplified model of real-world vehicles. An instance of Vehicle represents a very specific vehicle but it is still a model:
scala> val theAuto = new Vehicle("WAUZZZ8K6AA123456", "Audi", "A4", 2009, "Germany") // object creation is an example of abstraction theAuto: Vehicle = Vehicle@2688b2be
Perform start behavior on the object:
scala> theAuto.start() Starting...
Perform stop behavior on the object:
scala> theAuto.stop() Stopping...
To reiterate the main points aforementioned, the ability to define a class is an example of abstraction. Inside the class, we have an attribute called createTs (creation timestamp). The scope of this attribute is private and this attribute cannot be accessed from outside the class. The ability to hide internal details is an example of encapsulation.
Now let's look at inheritance and polymorphism in Scala REPL. We'll define a new class called SportsUtilityVehicle by extending the Vehicle class, as shown in the following code:
Define SportsUtilityVehicle class that provides an extension to Vehicle class:
scala> class SportsUtilityVehicle(vin: String, manufacturer: String, model: String, modelYear: Int, finalAssemblyCountry: String, fourWheelDrive: Boolean) extends Vehicle(vin, manufacturer, model, modelYear, finalAssemblyCountry) { // inheritance example | def enableFourWheelDrive(): Unit = { if (fourWheelDrive) println("Enabling 4 wheel drive") } | override def start(): Unit = { | enableFourWheelDrive() | println("Starting SUV...") | } | } defined class SportsUtilityVehicle
Create an instance of SUV object but assign to Vehicle type object, this is permissible because every SUV object is also a Vehicle:
scala> val anotherAuto: Vehicle = new SportsUtilityVehicle("WAUZZZ8K6A654321", "Audi", "Q7", 2019, "Germany", true) anotherAuto: Vehicle = SportsUtilityVehicle@3c2406dd
Perform start behavior on the object, on doing so the object exhibits the behavior of an SUV class. This is the polymorphism property facilitated by the object-oriented paradigm:
Inheritance is a powerful construct that allows usto reuse code. We created an instance ofSportsUtilityVehicleand assigned it to a type of vehicle. When we invoke thestartmethodon this object, the runtime system automatically determines the actual type of object and calls thestartmethod defined inSportsUtilityVehicle. This is an example of polymorphism, where we can treat objects as base types; however, at runtime, the appropriate behavior is applied depending upon the true type of the object.
The following is a UML diagram with a more formal representation of the inheritance relationship:
It captures the following important properties:
The Vehicle is a super-class or base-class
SportsUtilityVehicle is a sub-class that extends the Vehicle base-class
This relationship can be envisioned as a parent-child relationship
This diagram shows that Vehicle is a base class and the SportsUtilityVehicle subclass extends this base class. The subclass adds its own additional attributes and behavior. One way to look at inheritance is as a generalization-specialization construct. The base class represents a generalized set of attributes and behavior. The derived class or subclass adds its specialization by either altering some of the base class behaviors or adding its own.
The following is a screenshot of the same example in IDE:
IDEs such IntelliJ help us to visualize many of the properties of classes and objects in a user-friendly way. A good IDE certainly acts as a great productivity tool. In the preceding example, the IDE screen is divided into the following three parts:
Structure: Structural properties of classes and objects, such as methods and attributes