OOPS In Depth and Fundamentals of Design Patterns

With Real-world Examples | Part 1

OOPS Intro

As we have seen in our previous blog that is why we need classes. To sum it up again, we need classes and objects to make real-world entities or custom data types. A car class can have a color, model, gear mode, etc. This all belongs to car objects in real life, So in the Programming world we can bind related functionalities to an entity and this entity can be defined by classes. Class is a template that defines what an entity is and how it behaves. We have some fields and methods associated with this template. For Ex:-

public class Dog{
  private String name;
  private String color;

  Dog(){
    //Constructor
  }
  void bark(){
    System.out.println("Woop");
  }
}

Here, Dog is an entity having fields like name, color, and methods like bark().

image.png

OOPS Four Pillars

Now, Talking about the OOPS style of programming. It offers us some cool facilities like

  • Encapsulation

  • Abstraction

  • Inheritance

  • Polymorphism

Using these functionalities, we can make our code much more modular and scalable. Scalability can be achieved by using some of the Design patterns like Factory, Singleton, etc (We'll see this in later blogs). These design patterns are derived from SOLID principles (We'll see this also in later blogs). Before going to SOLID or Design patterns, You should be clear with OOPS usage.

Encapsulation

Encapsulation is a way to bind all the related fields and methods in a single capsule or class. Making all fit inside one capsule is called encapsulation. Encapsulation also facilitates Data Hiding.

image.png

Well if you still haven't understood. Let's see with an example:-

public class Student{
  private String name;
  private int marks;
  Student(){} // Default Constructor
  Student(String name, int marks){ // Parameterized Constructor
    this.name = name;
    this.marks = marks;
  }

  public int getMarks(){
    return this.marks;
  }
  public String getName(){
    return this.name;
  }
  public void setName(String t_name){
    this.name = t_name;
  }
  public void setMarks(int t_marks){
    this.marks = t_marks;
  }
}

Now, you'll see that Student class fields and methods are encapsulated inside one class with data hiding using access modifiers like Private used here. But why the heck we are doing this ? Why are we using getName or setName (Similarly for marks)? These are called getters and setters. We do use getters and setters such that name and marks can not be accessed directly. If someone wants to access marks he/she has to call the getter of marks. But Why so????

The point of using getters and setters is to provide one extra layer. We can add some rules in setter before setting the marks. Such that no one can manipulate marks in the way that he/she intended.

  public void setMarks(int t_marks){
    if(t_marks<=100 && t_marks>=0){
          this.marks = t_marks;
          return;
    }else{
      throw new Exception("Enter marks from 0-100 included");
    }
  }

Now, we see that an extra check has been added. That is How, encapsulation leverages Access modifiers (Private, Public, Protected, Default) and Getters, Setters to provide us the data hiding and security.

image.png

Abstraction

Now, you can argue that If encapsulation facilitates the Data Hiding and encapsulating then what the heck is abstraction?

Exactly, this is the confusing part. But I got YOU OKKKK!!!

IMP Abstraction is on the design side but Encapsulation is on the implementation side. You'll get more clarity in implementation.

Let's say we want to make some functionality of Message. In this, we can send a message, set senderID, and check the validity of the message

So, Rahul Came up with this design:- ``` public class Message{ private String senderID; private String status; // Sent, Pending, Seen

Message(){} Message(String senderID, String status){ this.senderID = senderID; this.status = status; }

void send(){ // Send message Logic } } ```

Rahul is right with his design or not? Yes, he is right but this design has many flaws. First of all, we have different kinds of messages like Text, Image, and Voice Messages. So, Rahul has no big deal he can change and add methods like

public class Message{
  private String senderID; 
  private String status; // Sent, Pending, Seen

  Message(){}
  Message(String senderID, String status){
    this.senderID = senderID;
    this.status = status;
  }

  void sendTextMessage(){
    // Send Text message Logic
  }
  void sendVoiceMessage(){
    // Send Voice message Logic
  }
  void sendImageMessage(){
    // Send Image message Logic
  }
}

Now, you see every time when the team needs to change/update the Message, Rahul has to change the whole class. It will be a big problem when many functions or services will be using this class. In each service, we need to change the functionality according to the change made in the message class.

Now, this is where abstraction comes into the picture.

image.png

Let's see a new class:-

public abstract class Message{
    private String senderID; 
    private String status; // Sent, Pending, Seen

    Message(){}
    Message(String senderID, String status){
      this.senderID = senderID;
      this.status = status;
    }
    public abstract void sendMessage(){} // Abstract method
}

Now, this class can be extended by VoiceMessage, ImageMessage, and TextMessage. Now, every time when someone wants to add new functionality he/she needs to extend the base class (Inheritance). Now, this class is open for extension but not for modification (Open-Closed Principle). Let's see:-

public class TextMessage extends Message{
  TextMessage(String senderID, String status){
    super(senderID, status); // calling parent Message class constructor
    }

    @Override
    public void sendMessage(){
      // Text Message send functionality
    }
  }
}

Similarly, Image and Audio Message can be extended from the Base Message class. Here, we are defining Message class as abstract having one abstract method. Before going into more detail, Answer these questions in the comments if you know:-

  • Is it necessary to have at least one method as abstract in an abstract class

  • Is it necessary to include @Override keyword while overriding the base class method

  • what will happen if we do not call super() method in the derived class

Now, In JAVA, we can achieve abstraction using abstract classes and interfaces

  • Abstract classes (0-100% Abstraction)

  • Interface (100% Abstraction)

The only difference between Abstraction and Interface is:-

image.png

Don't be exhausted, we are almost at the end:-

In Abstraction, we can have some methods base implementation that can be overridden while in interfaces all methods have to be abstract and should be overridden in the derived class. Below is the Java libraries implementation:-

image.png

Don't forget to Follow, Like the Blog. Many more to come on the Core Topics of CS in much detail.....