Exploring the Core Features of Object-Oriented Programming(OOP)

Exploring the Core Features of Object-Oriented Programming(OOP)

24 Dec 2024
Intermediate
59 Views
61 min read
Learn with an interactive course and practical hands-on labs

Java Programming Course

Features of OOPs

The features of Object-Oriented Programming (OOPs) form the foundation of modern programming languages and methodologies. These features empower developers to design software that is modular, reusable, and easy to maintain. In today's fast-paced technological world, features of OOPs are critical for building scalable and robust applications. They enhance collaboration among developers, as code can be organized into self-contained units that are easier to understand and debug.

In the OOPs tutorial, we will learn some exciting concepts related Object Oriented Programming, including object-oriented programming features, encapsulation, polymorphism, inheritance, abstraction, dynamic binding, message binding, and a lot more.

What is Object-Oriented Programming?

Object-oriented programming (OOP) is a way of designing programs by organizing data and actions into objects that are based on real-world things. Each object has attributes (data) and behaviors (methods) that define how it works. OOP makes it easier to model, understand, and solve real-world problems in programming.

Core Features of OOP

The core features of OOP serve as the building blocks of this programming paradigm. They are designed to make software development easier, more efficient, and more organized. These features include:

  • Encapsulation: Bundling data and methods into a single unit to safeguard sensitive information.
  • Inheritance: Enabling code reusability by allowing new classes to inherit from existing ones.
  • Polymorphism: Allowing objects to behave differently based on their context.
  • Abstraction: Hiding implementation details to focus on essential functionalities.

7 Key Features of OOPs

The key features of OOPs enable developers to write modular and reusable code. These features mirror real-world objects and interactions, making programming intuitive and efficient.

  1. Class and Object
  2. Encapsulation
  3. Inheritance
  4. Polymorphism
  5. Abstraction
  6. Dynamic Binding
  7. Message Passing

key features of OOPs

1. Class and Object

A class is a blueprint for creating objects. It defines properties (attributes) and methods (behaviors) that objects will have. And an object is an instance of a class. It represents a specific entity with values for the properties defined in the class.

Class and Object

Example

using System;

class Car
{
    public string Brand { get; set; } // Properties
    public int Speed { get; set; }

    public void DisplayDetails() // Method
    {
        Console.WriteLine($"Brand: {Brand}, Speed: {Speed} km/h");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Car car1 = new Car(); // Creating an object
        car1.Brand = "Toyota";
        car1.Speed = 120;

        Car car2 = new Car();
        car2.Brand = "Honda";
        car2.Speed = 140;

        car1.DisplayDetails();
        car2.DisplayDetails();
    }
}    
class Car:
    def __init__(self, brand, speed):
        self.brand = brand
        self.speed = speed

    def display_details(self):
        print(f"Brand: {self.brand}, Speed: {self.speed} km/h")


# Main function
if __name__ == "__main__":
    car1 = Car("Toyota", 120)  # Creating an object
    car2 = Car("Honda", 140)

    car1.display_details()
    car2.display_details()        
class Car {
    String brand;
    int speed;

    void displayDetails() {
        System.out.println("Brand: " + brand + ", Speed: " + speed + " km/h");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car1 = new Car(); // Creating an object
        car1.brand = "Toyota";
        car1.speed = 120;

        Car car2 = new Car();
        car2.brand = "Honda";
        car2.speed = 140;

        car1.displayDetails();
        car2.displayDetails();
    }
}         
#include <iostream>>
#include <string>

class Car {
public:
    std::string brand;
    int speed;

    void displayDetails() {
        std::cout << "Brand: " << brand << ", Speed: " << speed << " km/h" << std::endl;
    }
};

int main() {
    Car car1; // Creating an object
    car1.brand = "Toyota";
    car1.speed = 120;

    Car car2;
    car2.brand = "Honda";
    car2.speed = 140;

    car1.displayDetails();
    car2.displayDetails();

    return 0;
}       

Output

Brand: Toyota, Speed: 120 km/h
Brand: Honda, Speed: 140 km/h

Explanation

  • Class Car: Defines properties brand and speed and a method displayDetails().
  • Objects car1 and car2: Represent two specific cars with different values for brand and speed. The output will display the details of each car.
Also Read:
Classes and Objects in Java
Classes and Objects in C#
Python Classes and Objects with Examples

2. Encapsulation

Encapsulation is the process of bundling data (attributes) and methods (functions) into a single unit (class) and restricting direct access to the data using access modifiers (e.g., private). It ensures data security and integrity.

Encapsulation

Example

using System;

class BankAccount
{
    private double balance;

    // Constructor to initialize the balance
    public BankAccount(double initialBalance)
    {
        balance = initialBalance;
    }

    // Getter method for balance
    public double GetBalance()
    {
        return balance;
    }

    // Method to deposit money
    public void Deposit(double amount)
    {
        if (amount > 0)
        {
            balance += amount;
        }
        else
        {
            Console.WriteLine("Invalid deposit amount");
        }
    }

    // Method to withdraw money
    public void Withdraw(double amount)
    {
        if (amount > 0 && amount <= balance)
        {
            balance -= amount;
        }
        else
        {
            Console.WriteLine("Invalid withdrawal amount");
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        BankAccount account = new BankAccount(5000);
        account.Deposit(2000);
        account.Withdraw(1500);
        Console.WriteLine("Current Balance: " + account.GetBalance());
    }
}       
class BankAccount:
    def __init__(self, initial_balance):
        self.__balance = initial_balance  # Private variable using __

    def get_balance(self):
        return self.__balance  # Getter to access private data

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
        else:
            print("Invalid deposit amount")

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
        else:
            print("Invalid withdrawal amount")


if __name__ == "__main__":
    account = BankAccount(5000)  # Create an account with an initial balance
    account.deposit(2000)       # Deposit 2000
    account.withdraw(1500)      # Withdraw 1500
    print("Current Balance:", account.get_balance())        
class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        balance = initialBalance;
    }

    public double getBalance() {
        return balance; // Getter to access private data
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        } else {
            System.out.println("Invalid deposit amount");
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        } else {
            System.out.println("Invalid withdrawal amount");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(5000);
        account.deposit(2000);
        account.withdraw(1500);
        System.out.println("Current Balance: " + account.getBalance());
    }
}    
#include <iostream>
using namespace std;

class BankAccount {
private:
    double balance;

public:
    // Constructor to initialize balance
    BankAccount(double initialBalance) {
        balance = initialBalance;
    }

    // Getter to access the balance
    double getBalance() {
        return balance;
    }

    // Method to deposit amount
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        } else {
            cout << "Invalid deposit amount" << endl;
        }
    }

    // Method to withdraw amount
    void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        } else {
            cout << "Invalid withdrawal amount" << endl;
        }
    }
};

int main() {
    // Create a BankAccount object
    BankAccount account(5000); // Initial balance of 5000
    account.deposit(2000);     // Deposit 2000
    account.withdraw(1500);    // Withdraw 1500
    cout << "Current Balance: " << account.getBalance() << endl;

    return 0;
}      

Output

Current Balance: 5500

Explanation

  • Private attribute balance: Cannot be directly accessed outside the class.
  • Public methods: getBalance(), deposit(), and withdraw() provide controlled access to balance.
  • Encapsulation protects sensitive information like account balance.
Also Read: Understanding Encapsulation in Java

3. Inheritance

Inheritance allows a class (child) to inherit properties and methods from another class (parent). It promotes code reuse and hierarchical relationships.

Inheritance

Example

using System;

class Vehicle
{
    public string Brand { get; set; }

    public void Start()
    {
        Console.WriteLine($"{Brand} vehicle is starting.");
    }
}

class Car : Vehicle
{
    public int Speed { get; set; }

    public void DisplaySpeed()
    {
        Console.WriteLine($"{Brand} car is running at {Speed} km/h.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Car car = new Car
        {
            Brand = "Toyota",
            Speed = 120
        };

        car.Start(); // Inherited from Vehicle
        car.DisplaySpeed();
    }
}    
class Vehicle:
    def __init__(self, brand):
        self.brand = brand

    def start(self):
        print(f"{self.brand} vehicle is starting.")

class Car(Vehicle):
    def __init__(self, brand, speed):
        super().__init__(brand)
        self.speed = speed

    def display_speed(self):
        print(f"{self.brand} car is running at {self.speed} km/h.")

if __name__ == "__main__":
    car = Car("Toyota", 120)
    car.start()  # Inherited from Vehicle
    car.display_speed()      
class Vehicle {
    String brand;

    void start() {
        System.out.println(brand + " vehicle is starting.");
    }
}

class Car extends Vehicle {
    int speed;

    void displaySpeed() {
        System.out.println(brand + " car is running at " + speed + " km/h.");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        car.brand = "Toyota";
        car.speed = 120;
        car.start(); // Inherited from Vehicle
        car.displaySpeed();
    }
}    
#include <iostream>
#include <string>
using namespace std;

// Base class
class Vehicle {
public:
    string brand;

    void start() {
        cout << brand << " vehicle is starting." << endl;
    }
};

// Derived class
class Car : public Vehicle {
public:
    int speed;

    void displaySpeed() {
        cout << brand << " car is running at " << speed << " km/h." << endl;
    }
};

// Main function
int main() {
    Car car;
    car.brand = "Toyota";
    car.speed = 120;
    car.start(); // Inherited from Vehicle
    car.displaySpeed();

    return 0;
}       

Output

Toyota vehicle is starting.
Toyota car is running at 120 km/h.

Explanation

  • Parent class Vehicle: Defines common attributes and behaviors.
  • Child class Car: Inherits Vehicle's properties and adds new functionality (displaySpeed()).
  • The output shows Car accessing both inherited and its own methods.
Learn More:
Inheritance in C#
Types of Inheritance in C++ with Examples
Understanding Different Types of Inheritance
What is Inheritance in Java: Types of Inheritance in Java

4. Polymorphism

Polymorphism allows the same method or operator to behave differently based on the context. This can be achieved through method overloading and overriding.

Polymorphism

Example

using System;
namespace PolymorphismDemo
{
    // Method Overloading (Compile-time Polymorphism)
    class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
        public double Add(double a, double b)
        {
            return a + b;
        }
    }

    // Method Overriding (Run-time Polymorphism)
    class Animal
    {
        public virtual void Sound() // Use virtual to allow overriding
        {
            Console.WriteLine("This animal makes a sound.");
        }
    }
    class Dog : Animal
    {
        public override void Sound() // Use override to provide a specific implementation
        {
            Console.WriteLine("The dog barks.");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // Method Overloading
            Calculator calc = new Calculator();
            Console.WriteLine("Sum (int): " + calc.Add(5, 10));
            Console.WriteLine("Sum (double): " + calc.Add(5.5, 3.3));
            // Method Overriding
            Animal myAnimal = new Dog();
            myAnimal.Sound(); // Calls Dog's Sound() method
        }
    }
}    
# Method Overloading (Compile-time Polymorphism)
class Calculator:
    # Python does not support method overloading directly. 
    #However, we can achieve similar functionality with default arguments or type checking.
    def add(self, a, b):
        return a + b


# Method Overriding (Run-time Polymorphism)
class Animal:
    def sound(self):
        print("This animal makes a sound.")


class Dog(Animal):
    def sound(self):
        print("The dog barks.")


# Main function
if __name__ == "__main__":
    # Method Overloading example
    calc = Calculator()
    print("Sum (int):", calc.add(5, 10))  # Works for integers
    print("Sum (double):", calc.add(5.5, 3.3))  # Works for floats

    # Method Overriding example
    my_animal = Dog()
    my_animal.sound()  # Calls Dog's sound() method        
// Method Overloading (Compile-time Polymorphism)
class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
}

// Method Overriding (Run-time Polymorphism)
class Animal {
    void sound() {
        System.out.println("This animal makes a sound.");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("The dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Method Overloading
        Calculator calc = new Calculator();
        System.out.println("Sum (int): " + calc.add(5, 10));
        System.out.println("Sum (double): " + calc.add(5.5, 3.3));

        // Method Overriding
        Animal myAnimal = new Dog();
        myAnimal.sound(); // Calls Dog's sound() method
    }
}         
#include <iostream>
using namespace std;

// Method Overloading (Compile-time Polymorphism)
class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }
};

// Method Overriding (Run-time Polymorphism)
class Animal {
public:
    virtual void sound() {  // Using virtual function for overriding
        cout << "This animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override {  // Overriding the sound() method
        cout << "The dog barks." << endl;
    }
};

int main() {
    // Method Overloading
    Calculator calc;
    cout << "Sum (int): " << calc.add(5, 10) << endl;
    cout << "Sum (double): " << calc.add(5.5, 3.3) << endl;

    // Method Overriding
    Animal* myAnimal = new Dog();  // Creating an object of Dog but using Animal pointer
    myAnimal->sound();  // Calls Dog's sound() method

    delete myAnimal;  // Clean up memory
    return 0;
}

       

Output

Sum (int): 15
Sum (double): 8.8
The dog barks.

Explanation

  • Method Overloading: The add method is defined with different parameter types.
  • Method Overriding: The sound method in Dog overrides the sound method in Animal.
Also Read:
What is Polymorphism in Java? Types of Polymorphism
Polymorphism in C++: Types of Polymorphism
Method Overloading And Overriding In Java (With example)

5. Abstraction

Abstraction hides implementation details and shows only essential features. It can be achieved using abstract classes and interfaces.

Example

using System;

abstract class Shape
{
    // Abstract method
    public abstract void Draw();
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle.");
    }
}

class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();

        shape1.Draw();
        shape2.Draw();
    }
}  
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def draw(self):
        pass

class Circle(Shape):
    def draw(self):
        print("Drawing a circle.")

class Rectangle(Shape):
    def draw(self):
        print("Drawing a rectangle.")

def main():
    shape1 = Circle()
    shape2 = Rectangle()

    shape1.draw()
    shape2.draw()

if __name__ == "__main__":
    main()    
abstract class Shape {
    abstract void draw(); // Abstract method
}

class Circle extends Shape {
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Rectangle extends Shape {
    void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();

        shape1.draw();
        shape2.draw();
    }
}         
#include <iostream>
using namespace std;

// Abstract class Shape
class Shape {
public:
    virtual void draw() = 0; // Pure virtual function
    virtual ~Shape() {} // Virtual destructor
};

// Circle class inheriting from Shape
class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing a circle." << endl;
    }
};

// Rectangle class inheriting from Shape
class Rectangle : public Shape {
public:
    void draw() override {
        cout << "Drawing a rectangle." << endl;
    }
};

int main() {
    Shape* shape1 = new Circle(); // Creating object of Circle
    Shape* shape2 = new Rectangle(); // Creating object of Rectangle

    shape1->draw(); // Calling draw method for Circle
    shape2->draw(); // Calling draw method for Rectangle

    // Clean up dynamically allocated memory
    delete shape1;
    delete shape2;

    return 0;
}   

Output

Drawing a circle.
Drawing a rectangle.

Explanation

  • Abstract class Shape: Defines the abstract method draw without implementation.
  • Subclasses (Circle, Rectangle): Provide specific implementations of draw.
  • The output shows different behaviors for a draw() depending on the object.
Also Read:
Interfaces and Data Abstraction in C++
What is Abstraction in Java with Examples & Its Uses

6. Dynamic Binding

Dynamic binding occurs when a method call is resolved at runtime instead of compile-time. It supports polymorphism by determining the actual method to invoke based on the object.

Example

using System;

class Animal
{
    public virtual void Sound()
    {
        Console.WriteLine("This animal makes a sound.");
    }
}

class Cat : Animal
{
    public override void Sound()
    {
        Console.WriteLine("The cat meows.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Animal animal = new Cat(); // Dynamic Binding
        animal.Sound(); // Calls Cat's Sound() at runtime
    }
}    
class Animal:
    def sound(self):
        print("This animal makes a sound.")

class Cat(Animal):
    def sound(self):
        print("The cat meows.")

# Main block
if __name__ == "__main__":
    animal = Cat()  # Dynamic Binding
    animal.sound()  # Calls Cat's sound() at runtime       
class Animal {
    void sound() {
        System.out.println("This animal makes a sound.");
    }
}

class Cat extends Animal {
    void sound() {
        System.out.println("The cat meows.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Cat(); // Dynamic Binding
        animal.sound(); // Calls Cat's sound() at runtime
    }
}     
#include <iostream>
using namespace std;

// Base class
class Animal {
public:
    virtual void sound() { // Virtual function for dynamic binding
        cout << "This animal makes a sound." << endl;
    }
};

// Derived class
class Cat : public Animal {
public:
    void sound() override { // Override the base class sound method
        cout << "The cat meows." << endl;
    }
};

int main() {
    Animal* animal = new Cat(); // Dynamic Binding
    animal->sound(); // Calls Cat's sound() at runtime
    delete animal; // Free memory
    return 0;
}      

Output

The cat meows.

Explanation

  • The sound method is called on an Animal reference pointing to a Cat object.
  • At runtime, the JVM determines that Cat's sound method should be invoked.

7. Message Passing

Message passing allows objects to communicate with each other by calling each other’s methods.

Example

using System;
class Player
{
    public void Move(string direction)
    {
        Console.WriteLine("Player is moving " + direction);
    }
}
class Game
{
    public void Play()
    {
        Player player = new Player();
        player.Move("forward"); // Message passing
    }
}
class MainClass
{
    public static void Main(string[] args)
    {
        Game game = new Game();
        game.Play();
    }
}   
class Player:
    def move(self, direction):
        print(f"Player is moving {direction}")

class Game:
    def play(self):
        player = Player()
        player.move("forward")  # Message passing

if __name__ == "__main__":
    game = Game()
    game.play()        
class Player {
    void move(String direction) {
        System.out.println("Player is moving " + direction);
    }
}

class Game {
    void play() {
        Player player = new Player();
        player.move("forward"); // Message passing
    }
}

public class Main {
    public static void main(String[] args) {
        Game game = new Game();
        game.play();
    }
}       
#include <iostream>
#include <string>
using namespace std;

class Player {
public:
    void move(const string& direction) {
        cout << "Player is moving " << direction << endl;
    }
};

class Game {
public:
    void play() {
        Player player;
        player.move("forward"); // Message passing
    }
};

int main() {
    Game game;
    game.play();
    return 0;
}    

Output

Player is moving forward

Explanation

  • The Game object sends a message (method call) to the Player object to perform an action (move).
  • Message passing enables interaction between objects in an OOP system.

Advantages of OOP Features

  • Code Reusability: Inheritance allows existing code to be reused, reducing redundancy.
  • Scalability and Maintainability: Modular design makes it easier to update and expand software.
  • Improved Modularity: Encapsulation ensures that each component is self-contained and manageable.
  • Real-World Problem Mapping: OOP concepts align closely with real-world entities, making problem-solving intuitive.

Applications of OOP

The features of OOP are widely used across various domains, including:

  • Web Development: Frameworks like Django (Python) and Spring (Java) rely on OOP for modular web applications.
  • Game Development: OOP helps model characters, actions, and environments using objects.
  • Enterprise Applications: Large-scale systems like ERP and CRM solutions use OOP for scalability and maintainability.
  • Mobile Development: OOP principles are fundamental in Android and iOS development.
Read More: Top 50 OOPs Interview Questions and Answers
    Conclusion

    The features of OOP have revolutionized software development by providing a framework that is modular, reusable, and adaptable. They simplify complex programming tasks, making code easier to write, understand, and maintain. Mastering OOP concepts is a crucial step for any aspiring developer, as these principles lead to better software design and equip developers to tackle real-world challenges effectively.

    Dear Student, To enhance your knowledge and clear your doubts, Join our Tech Trendy Free Masterclasses to upgrade your tech skills with the latest skills trends, design, and practices. If you are interested in development, Scholarhat provides you with the Full-Stack .NET Developer Certification Training Course and .NET Solution Architect Certification Training, so don't be late and enroll now.

    FAQs

    No, we cannot create an object of an abstract class in Java because an abstract class cannot have a complete implementation. It serves as a template for other classes to inherit from and implement its abstract methods.

    OOP is important because it promotes modular, reusable, and maintainable code. The key features like encapsulation, inheritance, and polymorphism make it easier to model real-world problems, improve code organization, and enhance collaboration among developers. OOP also simplifies debugging, testing, and software scalability. 

    The super() keyword is used to call the constructor of the parent class. It can also be used to access the parent class's methods and properties. In the case of inheritance, super() ensures that the parent class is initialized before the child class. 

    No, static methods cannot be overridden in Java because they are bound at compile-time, not at runtime. However, you can hide a static method in a subclass by defining a static method with the same name and signature. 
    Share Article
    About Author
    Shailendra Chauhan (Microsoft MVP, Founder & CEO at ScholarHat)

    Shailendra Chauhan, Founder and CEO of ScholarHat by DotNetTricks, is a renowned expert in System Design, Software Architecture, Azure Cloud, .NET, Angular, React, Node.js, Microservices, DevOps, and Cross-Platform Mobile App Development. His skill set extends into emerging fields like Data Science, Python, Azure AI/ML, and Generative AI, making him a well-rounded expert who bridges traditional development frameworks with cutting-edge advancements. Recognized as a Microsoft Most Valuable Professional (MVP) for an impressive 9 consecutive years (2016–2024), he has consistently demonstrated excellence in delivering impactful solutions and inspiring learners.

    Shailendra’s unique, hands-on training programs and bestselling books have empowered thousands of professionals to excel in their careers and crack tough interviews. A visionary leader, he continues to revolutionize technology education with his innovative approach.
    Accept cookies & close this