An Introduction to Object-Oriented Programming (OOPs) and Its Key Concepts
Object-oriented programming (OOP) is a programming paradigm that emphasizes the use of objects, which are instances of classes, to organize and structure code.
Object
In object-oriented programming (OOP), an object is an instance of a class that encapsulates data and behavior. A class is a blueprint for creating objects that defines a set of properties and methods that all objects of that class will have.
Class
A class is a blueprint or prototype (template) from which objects are created. This section defines a class that models the state and behavior of a real-world object. It intentionally focuses on the basics, showing how even a simple class can clearly model state and behavior. Classes are characterized by properties and functions. For example, if the class is a car, it may have the following properties:
Price,
Type,
Maximum,
speed,
Number of seats
And it may have the following functions:
Number Of Seats,
Number Of Wheels,
getYearBuilt
Creating a Class
Use the class keyword to declare a class in Dart. A class definition starts with the keyword class followed by the class name; and the class body enclosed by a pair of curly braces. A class code includes the class name and a group of properties or attributes which will construct the class.
Example: The following example shows how you can create a class called car step by step:
Step 1: Declare an empty class called “car”, as illustrated in the following code:
class car {
}
main() {
}
Step 2: The car class has been created. Now, you may add some properties to this car class as illustrated in the following code:
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats;
}
main() {
}
Step 3: To create an instance of the class or object, use the new keyword followed by the class name. Here, you can create an object depending on the “car” class (blue print) which you have created in step 1, using the following code. Ciaz is the name of the object which has all car class properties or attributes values.
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats;
}
main() {
var Ciaz =new car(); }
The following code displays the object Ciaz with some properties values:
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats;
}
main() {
var Ciaz=new car();
Ciaz.MaxSpeed=200;
Ciaz.color="red";
Ciaz.NumOfSeats=5;
Ciaz.type="Camry";
}
For example, if you add the following command to the main() function, you can print a specific attribute or property value (type) of Ciaz object : print("Car Type is: ${Ciaz.type}");
If we configure a specific value for NumOfSeats to the object “Ciaz”, it will override any other values as illustrated in the following code:
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats = 4;
}
main() {
var Ciaz =new car();
Ciaz.MaxSpeed=200;
Ciaz.color="red";
Ciaz.NumOfSeats=5;
Ciaz.type="Camry";
print("Car Type is: ${Ciaz.type}");
print("Number of Seats is : ${tCiaz.NumOfSeats}");
}
Try running this code in your system and see the output.
Adding Methods to Classes
You can add your methods or functions to a class, then conjunct these methods with the objects which you want to initiate from this class. In the following example, CarSpeed() method has been added to the car class as illustrated in the grey highlighted part of the following code:
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats = 4;
void CarSpeed(){
print("Car Speed is : $MaxSpeed");
}
}
main() {
var Ciaz=new car();
Ciaz.MaxSpeed=200;
Ciaz.color="red";
Ciaz.type="Camry";
Ciaz.NumOfSeats=7;
print("Number of Seats is : ${toyota.NumOfSeats}");
Ciaz.CarSpeed();
}
Try this example by yourself.
Providing Constructors for Your Classes
Constructor is a block of code that initializes the newly created object. A constructor resembles an instance method in Dart but it’s not considered a method because it doesn’t have a return type. Dart defines a constructor with the same name as that of the class.
Example: In the previous example, you initialized toyota object from the car class using the following command:
var toyota= new car();
Now, you will construct the same object using a constructor which has the same class name. First, I will create the car class again as illustrated in the following code:
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats;
car(type, color, MaxSpeed, NumOfSeats){
this.type=type;
this.color=color;
this.MaxSpeed=MaxSpeed;
this.NumOfSeats=NumOfSeats;
}
}
main() {
}
Now, we can construct or initiate an object using the car constructor as illustrated in the grey highlighted color part of the following code:
The last complete code is as follows -
class car {
String type;
String color;
int MaxSpeed;
int NumOfSeats;
car.initialize(){
type="Mini Van";
color="Green";
MaxSpeed=230;
NumOfSeats=2;
}
}
main() {
var toyota=new car.initialize();
print("Car Type is :${toyota.type}");
print("Number of Seats is :${toyota.NumOfSeats}");
}
Run and see the final output in your system.
OOP is based on four main principles:
Encapsulation: the practice of hiding internal data and functionality of an object and only exposing what is necessary through public methods.
Here's an example to illustrate encapsulation:
Let's say we have a Person class that has a private age variable and a public method getAge() to retrieve the age:
public class Person {
private int age;
public Person(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
In this example, the age variable is encapsulated because it is marked as private, which means that it cannot be accessed directly from outside the Person class. Instead, we expose a public method getAge() to retrieve the age.
If we create an instance of the Person class and try to access the age variable directly, like this:
Person john = new Person(25);
int johnsAge = john.age; // This will give a compile error because age is private
We would get a compile error because age is private and cannot be accessed from outside the class. Instead, we can access the age variable through the getAge() method:
Person john = new Person(25);
int johnsAge = john.getAge(); // This is the correct way to retrieve the age
By encapsulating the age variable and only exposing a public method to retrieve it, we ensure that the internal state of the Person object is protected and cannot be modified from outside the class. This helps us to maintain the integrity of the object and avoid unintended side effects caused by external modifications.
Inheritance: the ability to create new classes based on existing ones, inheriting their attributes and behaviors.
Here's an example to illustrate inheritance:
Let's say we have a Vehicle class that has some common attributes and behaviors of all vehicles, such as a make, model, year, and getInfo() method that returns a string representation of the vehicle:
public class Vehicle {
private String make;
private String model;
private int year;
public Vehicle(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
public String getInfo() {
return year + " " + make + " " + model;
}
}
Now let's say we want to create a Car class that extends Vehicle and adds some additional attributes and behaviors that are specific to cars, such as a numDoors and a getSpeed() method:
public class Car extends Vehicle {
private int numDoors;
public Car(String make, String model, int year, int numDoors) {
super(make, model, year);
this.numDoors = numDoors;
}
public int getNumDoors() {
return numDoors;
}
public void setNumDoors(int numDoors) {
this.numDoors = numDoors;
}
public int getSpeed() {
// Some code to calculate the car's current speedreturn speed;
}
}
In this example, the Car class extends the Vehicle class, which means that it inherits all the attributes and behaviors of the Vehicle class, such as the make, model, and year attributes, and the getInfo() method. It also adds some additional attributes and behaviors that are specific to cars, such as the numDoors attribute and the getSpeed() method.
If we create an instance of the Car class, we can access both the inherited attributes and behaviors from the Vehicle class and the additional attributes and behaviors from the Car class:
Car myCar = new Car("Ciaz", "Suzuki", 2021, 4);
String carInfo = myCar.getInfo(); // This will return carInfo
int numDoors = myCar.getNumDoors(); // This will return 4
int speed = myCar.getSpeed(); // This will return the car's current speed
By using inheritance, we can create new classes that reuse existing code and add additional functionality, making our code more modular and easier to maintain.
Polymorphism: the ability of objects of different classes to be treated as if they were of the same type, allowing for more flexibility and reusability in code.
Here's an example to illustrate polymorphism through method overriding
class Animal {
void makeSound() {
print("Some sound");
}
}
class Dog extends Animal {
@override
void makeSound() {
print("Bark");
}
}
class Cat extends Animal {
@override
void makeSound() {
print("Meow");
}
}
void main() {
List<Animal> animals = [new Dog(), new Cat()]; animals.forEach((animal) => animal.makeSound());
}
In this example, we have an Animal base class and two derived classes, Dog and Cat. Each of these classes overrides the makeSound() method from the Animal class to produce a different sound. In the main() function, we create a list of Animal objects that contains both Dog and Cat instances. We then iterate through the list and call the makeSound() method on each animal.
Since each animal has its own implementation of the makeSound() method, the output will be different for each animal:
Bark
Meow
This is an example of polymorphism because the same method (makeSound()) is being called on different objects (Dog and Cat), but each object is producing a different behavior. This allows us to write more flexible and reusable code, as we can create generic code that works with a range of different objects without needing to know their specific types.
Abstraction: the process of identifying the essential features of an object and ignoring the details that are not relevant to the current context.
Here's an example to illustrate abstraction in Dart:
abstract class Shape {
void draw();
}
class Circle implements Shape {
@override
void draw() {
print("Drawing a circle");
}
}
class Rectangle implements Shape {
@override
void draw() {
print("Drawing a rectangle");
}
}
void main() {
List<Shape> shapes = [new Circle(), new Rectangle()];
shapes.forEach((shape) => shape.draw());
}
In this example, we have an abstract Shape class that defines the behavior of a shape, but doesn't specify how that behavior is implemented. We then create two classes, Circle and Rectangle, that implement the Shape interface and provide their own implementation of the draw() method.
In the main() function, we create a list of Shape objects that contains both Circle and Rectangle instances. We then iterate through the list and call the draw() method on each shape. Since each shape has its own implementation of the draw() method, the output will be different for each shape:
Drawing a circle
Drawing a rectangle
This is an example of abstraction because the Shape class defines the behavior of a shape, but doesn't specify how that behavior is implemented. This allows us to write more flexible and reusable code, as we can create generic code that works with a range of different shapes without needing to know their specific types. We can also add new shapes to our program simply by implementing the Shape interface and providing our own implementation of the draw() method.
Thanks for reading, and happy coding!
Pre-Requisites You Need to Know part 2 -> Mastering the ART of DART