[OOPS 5] Abstraction and Interfaces

[OOPS 5] Abstraction and Interfaces

-in Java ☕

📗Abstraction

😶‍🌫️In Java, abstraction is the process of hiding the implementation details of a class from the outside world and providing a simplified, high-level interface for using the class.

🤫 Think of abstraction as a magician's trick, where the magician shows you only the parts of the trick that you need to see, while hiding the rest of the details that are not relevant to the trick.

📺 Here's an example: let's say you have a TV class that has methods for turning on/off the TV, changing the channel, and adjusting the volume.

public class TV {
    private boolean isOn;
    private int channel;
    private int volume;

    public void turnOn() {
        isOn = true;
        System.out.println("TV turned on");
    }

    public void turnOff() {
        isOn = false;
        System.out.println("TV turned off");
    }

    public void changeChannel(int channel) {
        this.channel = channel;
        System.out.println("Channel changed to " + channel);
    }

    public void adjustVolume(int volume) {
        this.volume = volume;
        System.out.println("Volume adjusted to " + volume);
    }
}

👁️ In this example, the TV class exposes all of its implementation details, such as the isOn, channel, and volume variables, as well as the details of how the methods work.

🎬 To abstract away these implementation details and provide a simplified, high-level interface for using the TV class, we can create an abstract class called AbstractTV defines only the public methods that outside code should use, without providing any implementation details.

abstract class AbstractTV {
    public abstract void turnOn();
    public abstract void turnOff();
    public abstract void changeChannel(int channel);
    public abstract void adjustVolume(int volume);
}

🙅‍♀️ Notice that the AbstractTV class does not provide any implementation details for the methods, but only defines their signature (i.e., their name, return type, and parameters).

📺 To use the TV class with abstraction, we can create a new class called SmartTV extends the AbstractTV class and provides its own implementation details for the methods.

class SmartTV extends AbstractTV {
    private TV tv;

    public SmartTV() {
        tv = new TV();
    }

    public void turnOn() {
        tv.turnOn();
    }

    public void turnOff() {
        tv.turnOff();
    }

    public void changeChannel(int channel) {
        tv.changeChannel(channel);
    }

    public void adjustVolume(int volume) {
        tv.adjustVolume(volume);
    }
}

🤝 In this example, the SmartTV class provides a simplified, high-level interface for using the TV class, without exposing any of its implementation details.

👨‍💻 The SmartTV class uses composition to create an instance of the TV class, and delegates the method calls to it.

📘Abstraction v/s Encapsulation

🆗! Now, are you confused between abstraction and encapsulation? They have notable differences.

🤐 Encapsulation is the concept of hiding the implementation details of an object from the outside world, whereas abstraction is the concept of hiding the implementation details of a class from the outside world.

📦 Encapsulation is like putting an object in a box and only allowing certain parts of it to be accessed from the outside, while abstraction is like putting a bunch of objects in a box and only showing the outside world what they can do, without revealing how they do it.

🏠 For example, let's say we have a House class that has several private instance variables, such as numberOfRooms, numberOfBathrooms, and squareFootage.

class House {
    private int numberOfRooms;
    private int numberOfBathrooms;
    private int squareFootage;

    // constructor, getters, and setters
}

🕵️‍♀️ To encapsulate the implementation details of the House class, we can make the instance variables private and provide public getter and setter methods for accessing and modifying them.

class House {
    private int numberOfRooms;
    private int numberOfBathrooms;
    private int squareFootage;

    public House(int numberOfRooms, int numberOfBathrooms, int squareFootage) {
        this.numberOfRooms = numberOfRooms;
        this.numberOfBathrooms = numberOfBathrooms;
        this.squareFootage = squareFootage;
    }

    public int getNumberOfRooms() {
        return numberOfRooms;
    }

    public void setNumberOfRooms(int numberOfRooms) {
        this.numberOfRooms = numberOfRooms;
    }

    public int getNumberOfBathrooms() {
        return numberOfBathrooms;
    }

    public void setNumberOfBathrooms(int numberOfBathrooms) {
        this.numberOfBathrooms = numberOfBathrooms;
    }

    public int getSquareFootage() {
        return squareFootage;
    }

    public void setSquareFootage(int squareFootage) {
        this.squareFootage = squareFootage;
    }
}

📏 In this example, we have encapsulated the implementation details of the House class by making the instance variables private and providing public getter and setter methods for accessing and modifying them.

🏘️ To abstract away the implementation details of the House class, we can create an abstract class called AbstractHouse that defines only the public methods that outside code should use, without providing any implementation details.

abstract class AbstractHouse {
    public abstract int getNumberOfRooms();
    public abstract void setNumberOfRooms(int numberOfRooms);
    public abstract int getNumberOfBathrooms();
    public abstract void setNumberOfBathrooms(int numberOfBathrooms);
    public abstract int getSquareFootage();
    public abstract void setSquareFootage(int squareFootage);
}

🙅‍♂️ Notice that the AbstractHouse class does not provide any implementation details for the methods, but only defines their signature (i.e., their name, return type, and parameters).

🏠 To use the House class with abstraction, we can create a new class called SmartHouse that extends the AbstractHouse class and provides its own implementation details for the methods.

public class SmartHouse extends AbstractHouse {
    private House house;

    public SmartHouse(int numberOfRooms, int numberOfBathrooms, int squareFootage) {
        house = new House(numberOfRooms, numberOfBathrooms, squareFootage);
    }

    public int getNumberOfRooms() {
        return house.getNumberOfRooms();
    }

    public void setNumberOfRooms(int numberOfRooms) {
        house.setNumberOfRooms(numberOfRooms);
    }

    public int getNumberOfBathrooms() {
        return house.getNumberOfBathrooms();
    }

    public void setNumberOfBathrooms(int numberOfBathrooms) {
        house.setNumberOfBathrooms(numberOfBathrooms);
    }

    public int getSquareFootage() {
        return house.getSquareFootage();
    }

    public void setSquareFootage(int squareFootage) {
        house.setSquareFootage(squareFootage);
    }
}

🏠 By using the SmartHouse class instead of the House class directly, the outside code can interact with the AbstractHouse interface without being aware of the implementation details of the House class.

📗Interfaces

🤝 In Java, an interface is a contract or agreement between two classes. It defines a set of methods that a class that implements the interface must implement. (Confusing isn't it?🤔)

🆗🫡 Let me explain with an example (a beautiful one 😊)

🕵️‍♀️ Let's say you're building a game and you want to have different types of characters, like warriors, mages, and archers. Each type of character will have different abilities, such as the ability to attack, defend, or cast spells.

📝 You can define an interface called Character that specifies the abilities that all characters must have:

interface Character {
    void attack();
    void defend();
    void castSpell();
}

🎭 Then, you can create classes that implement the Character interface, such as Warrior, Mage, and Archer, and provide their own implementation details for the methods:

In Java, to access the methods defined in an interface, you need to "implement" the interface in a class. This is similar to inheritance in that the class that implements the interface inherits the methods defined in the interface. But instead of using the "extends" keyword, you use the "implements" keyword to indicate that the class is implementing the interface.

When a class implements an interface, it must provide the implementation details for all of the methods defined in the interface. This means that the body of each method is provided by the implementing class, not by the interface itself. 🏭

public class Warrior implements Character {
    public void attack() {
        System.out.println("The warrior swings his sword!");
    }

    public void defend() {
        System.out.println("The warrior raises his shield!");
    }

    public void castSpell() {
        System.out.println("The warrior cannot cast spells.");
    }
}

public class Mage implements Character {
    public void attack() {
        System.out.println("The mage casts a fireball!");
    }

    public void defend() {
        System.out.println("The mage creates a magical barrier!");
    }

    public void castSpell() {
        System.out.println("The mage can cast many powerful spells!");
    }
}

public class Archer implements Character {
    public void attack() {
        System.out.println("The archer fires an arrow!");
    }

    public void defend() {
        System.out.println("The archer dodges the enemy's attacks!");
    }

    public void castSpell() {
        System.out.println("The archer cannot cast spells.");
    }
}

👨‍💻 By using the Character interface, you can write code that works with any class that implements the interface, without knowing the specific implementation details of each class. For example:

class Game {
    public static void main(String[] args) {
        Character[] characters = {new Warrior(), new Mage(), new Archer()};

        for (Character character : characters) {
            character.attack();
            character.defend();
            character.castSpell();
            System.out.println();
        }
    }
}

🚀 This code creates an array of characters, which can be any class that implements the Character interface, and calls the methods on each character. The output will be different for each type of character, but the code that calls the methods is the same for all of them. This is the power of interfaces in Java!

An interface is a completely "abstract class" that is used to group related methods with empty bodies.

💡 Here are a few important things to remember about Java interfaces:

👋 In Java, interfaces are a way to define a set of methods that must be implemented by any class that implements the interface. 🤝 Like abstract classes, interfaces cannot be used to create objects. In other words, you cannot create an instance of an interface.

🙅‍♂️ Interface methods do not have a body. Instead, the body is provided by the class that implements the interface. When you implement an interface, you must override all of its methods, providing the implementation details for each one.

👥 Interface methods are by default abstract and public, meaning that they must be implemented by any class that implements the interface, and they can be accessed from anywhere in the program.

🔍 Interface attributes are by default public, static, and final. This means that they are accessible from anywhere in the program, they belong to the interface rather than any implementing class, and their values cannot be changed once they are initialized.

🚫 It's important to note that an interface cannot contain a constructor. This is because interfaces cannot be used to create objects. Instead, they are used to define a set of methods that must be implemented by any class that wants to implement the interface.

🟡When do we use Interference?

👉 One main reason to use interfaces is to achieve abstraction. By defining a set of methods that a class must implement, we can ensure that those methods are available in any class that implements the interface. This makes our code more modular and easier to maintain.

👉 Another reason to use interfaces is when we want to create a common contract that multiple classes can follow. For example, if we have different classes that can be saved to a database, we can define a "Saveable" interface that requires each implementing class to have a "save" method. This makes it easier to write code that can work with any class that implements the "Saveable" interface.

👉 Interfaces can also be used to achieve polymorphism. By defining an interface that multiple classes can implement, we can write code that can work with any object that implements that interface. This can make our code more flexible and easier to extend in the future.

👉 Finally, interfaces can be used to define constants that are shared across multiple classes. By defining a constant in an interface, we can ensure that it is available to any class that implements the interface.

Thank You Soo Much for your valuable time.😊🥳👋


👋 Hi there! Let's connect and collaborate!

Here are some ways to reach me:

🔹 GitHub: github.com/mithindev

🔹 Twitter: twitter.com/MithinDev

🔹 LinkedIn: linkedin.com/in/mithin-dev-a-397983247

Looking forward to connecting with you!