What do you mean by Polymorphism, Inheritance, Encapsulation, and Dynamic Binding?

Polymorphism – means the ability of a single variable of a given type to be used to reference objects of different types, and automatically call the method that is specific to the type of object the variable references. In a nutshell, polymorphism is a bottom-up method call. The benefit of polymorphism is that it is very easy to add new classes of derived objects without breaking the calling code (i.e. getTotArea() in the sample code shown below) that uses the polymorphic classes or interfaces. When you send a message to an object even though you don’t know what specific type it is, and the right thing happens, that’s called polymorphism. The process used by object-oriented programming languages to implement polymorphism is called dynamic binding. Let us look at some sample code to demonstrate polymorphism: 

Sample Code:

//client or calling code 
double dim = 5.0; //ie 5 meters radius or width 
List listShapes = new ArrayList(20);
Shape s = new Circle();
listShapes.add(s); //add circle
s = new Square();
listShapes.add(s); //add square
getTotArea(listShapes, dim); //returns 78.5+25.0=103.5

//Later on, if you decide to add a half circle then define 
//a HalfCircle  class, which extends Circle and then provide an 
//area(). method but your called method getTotArea(...) remains same.
s = new HalfCircle();
listShapes.add(s); //add HalfCircle
getTotArea(listShapes, dim); //returns 78.5+25.0+39.25=142.75

/** called method: method which adds up areas of various 
 **  shapes supplied to it. 
 **/
public double getTotArea(List listShapes, double dim) {
 Iterator it = listShapes.iterator();
 double totalArea = 0.0;
 //loop through different shapes     
 while (it.hasNext()) {
  Shape s = (Shape) it.next();
  totalArea += s.area(dim);
  //polymorphic method call     
 }
 return totalArea;
}

For Example: given a base class/interface Shape, polymorphism allows the programmer to define different area(double dim1) methods for any number of derived classes such as Circle, Square, etc. No matter what shape an object is, applying the area method to it will return the right results. Later on, HalfCicle can be added without breaking your called code i.e. method getTotalArea(...)

Depending on what the shape is, the appropriate area(double dim) method gets called and calculated. 

  • Circle -->  Area is 78.5 sqm. 
  • Square --> Area is 25 sqm.
  • HalfCircle -->  Area is 39.25 sqm.

Inheritance – is the inclusion of behavior (i.e. methods) and state (i.e. variables) of a base class in a derived class so that they are accessible in that derived class. The key benefit of Inheritance is that it provides the formal mechanism for code reuse. Any shared piece of business logic can be moved from the derived class into the base class as part of the refactoring process to improve the maintainability of your code by avoiding code duplication. The existing class is called the superclass and the derived class is called the subclass. Inheritance can also be defined as the process whereby one object acquires characteristics from one or more other objects the same way children acquire characteristics from their parents. There are two types of inheritances: 

  • Implementation inheritance (aka class inheritance): You can extend an application’s functionality by reusing functionality in the parent class by inheriting all or some of the operations already implemented. In Java, you can only inherit from one superclass. Implementation inheritance promotes reusability but improper use of class inheritance can cause programming nightmares by breaking encapsulation and making future changes a problem. With implementation inheritance, the subclass becomes tightly coupled with the superclass. This will make the design fragile because if you want to change the superclass, you must know all the details of the subclasses to avoid breaking them. So when using implementation inheritance, make sure that the subclasses depend only on the behavior of the superclass, not on the actual implementation. For example in the above diagram, the subclasses should only be concerned about the behavior known as area() but not how it is implemented.  
  • Interface inheritance (aka type inheritance): This is also known as subtyping. Interfaces provide a mechanism for specifying a relationship between otherwise unrelated classes, typically by specifying a set of common methods each implementing class must contain. Interface inheritance promotes the design concept of the program to interfaces not to implementations. This also reduces the coupling or implementation dependencies between systems. In Java, you can implement any number of interfaces. This is more flexible than implementation inheritance because it won’t lock you into specific implementations which make subclasses difficult to maintain. So care should be taken not to break the implementing classes by modifying the interfaces.  

Which one to use? Prefer interface inheritance to implementation inheritance because it promotes the design concept of coding to an interface and reduces coupling. Interface inheritance can achieve code reuse with the help of object composition. If you look at Gang of Four (GoF) design patterns, you can see that it favors interface inheritance to implementation inheritance. 

Implementation Inheritance: Let’s assume that savings account and term deposit account have similar behavior in terms of depositing and withdrawing money, so we will get the superclass to implement this behavior and get the subclasses to reuse this behavior.  But saving account and term deposit account have specific behavior in calculating the interest. Superclass Account has reusable code as methods deposit (double amount) and withdraw (double amount).  

public abstract class Account {
  public void deposit(double amount) {
   System.out.println("depositing " + amount);
  }

  public void withdraw(double amount) {
   System.out.println("withdrawing " + amount);
  }
  public abstract double calculateInterest(double amount);
}

public class SavingsAccount extends Account {
  public double calculateInterest(double amount) {
   // calculate interest for SavingsAccount          
   return amount * 0.03;
  }
  public void deposit(double amount) {
   super.deposit(amount);
   // get code reuse          
   // do something else      
  }
  public void withdraw(double amount) {

   super.withdraw(amount);
   // get code reuse          
   // do something else      
  }
}

public class TermDepositAccount extends Account {
  public double calculateInterest(double amount) {
   // calculate interest for SavingsAccount         
   return amount * 0.05;
  }
  public void deposit(double amount) {
   super.deposit(amount);
   // get code reuse         
   // do something else     
  }

  public void withdraw(double amount) {
   super.withdraw(amount);
   // get code reuse         
   // do something else     
  }
}

Interface inheritance with composition

Let’s look at an interface inheritance code sample, which makes use of composition for reusability. In the following example, the methods deposit(…) and withdraw(…) share the same piece of code in AccountHelper class. The method calculateInterest(…) has its specific implementation in its own class. 

public interface Account {
 public abstract double calculateInterest(double amount);
 public abstract void deposit(double amount);
 public abstract void withdraw(double amount);
}

Code to interface so that the implementation can change. 

public interface AccountHelper {
 public abstract void deposit(double amount);
 public abstract void withdraw(double amount);
}

class AccountHelperImpl has reusable code as methods deposit (double amount) and withdraw (double amount)

public class AccountHelperImpl implements AccountHelper {
 public void deposit(double amount) {
  System.out.println("depositing " + amount);
 }

 public void withdraw(double amount) {
  System.out.println("withdrawing " + amount);
 }
}

public class SavingsAccountImpl implements Account {

 // composed helper class (i.e. composition).      
 AccountHelper helper = new AccountHelperImpl();
 public double calculateInterest(double amount) {
  // calculate interest for SavingsAccount          
  return amount * 0.03;
 }

 public void deposit(double amount) {
  helper.deposit(amount);
  // code reuse via composition     
 }

 public void withdraw(double amount) {
  helper.withdraw(amount);
  // code reuse via composition    
 }
}

public class TermDepositAccountImpl implements Account {
 // composed helper class (i.e. composition).      
 AccountHelper helper = new AccountHelperImpl();
 public double calculateInterest(double amount) {
  //calculate interest for SavingsAccount          
  return amount * 0.05;
 }
 public void deposit(double amount) {
  helper.deposit(amount);
  // code reuse via composition    
 }

 public void withdraw(double amount) {
  helper.withdraw(amount);
  // code reuse via composition     
 }
}
 

The Test class: 

public class Test {
 public static void main(String[] args) {
  Account acc1 = new SavingsAccountImpl();
  acc1.deposit(50.0);

  Account acc2 = new TermDepositAccountImpl();
  acc2.deposit(25.0);
  acc1.withdraw(25);
  acc2.withdraw(10);

  double cal1 = acc1.calculateInterest(100.0);
  double cal2 = acc2.calculateInterest(100.0);
  System.out.println("Savings --> " + cal1);
  System.out.println("TermDeposit -->  " + cal2);
 }
}

The output:  

depositing 50.0 
depositing 25.0 
withdrawing 25.0 
withdrawing 10.0 
Savings --> 3.0 
TermDeposit -->  5.0 

Why would you prefer code reuse via composition over inheritance? 

Both the approaches make use of polymorphism and give code reuse (in different ways) to achieve the same results but: 

  • The advantage of class inheritance is that it is done statically at compile-time and is easy to use. The disadvantage of class inheritance is that because it is static, implementation inherited from a parent class cannot be changed at the run. time. In object composition, functionality is acquired dynamically at run-time by objects collecting references to other objects. The advantage of this approach is that implementations can be replaced at run-time. This is possible because objects are accessed only through their interfaces, so one object can be replaced with another just as long as they have the same type. For Example, the composed class AccountHelperImpl can be replaced by another more efficient implementation as shown below if required: 
public class EfficientAccountHelperImpl implements AccountHelper {
 public void deposit(double amount) {
  System.out.println("efficient depositing " + amount);
 }
 public void withdraw(double amount) {
  System.out.println("efficient withdrawing " + amount);
 }
}
  • Another problem with class inheritance is that the subclass becomes dependent on the parent class implementation. This makes it harder to reuse the subclass, especially if part of the inherited implementation is no longer desirable and hence can break encapsulation.  Also, a change to a superclass can not only ripple down the inheritance hierarchy to subclasses but can also ripple out to code that uses just the subclasses making the design fragile by tightly coupling the subclasses with the superclass. But it is easier to change the interface/implementation of the composed class. 

Due to the flexibility and power of object composition, most design patterns emphasize object composition over inheritance whenever it is possible. Many times, a design pattern shows a clever way of solving a common problem through the use of object composition rather than a standard, less flexible, inheritance-based solution. 

Encapsulation – refers to keeping all the related members (variables and methods) together in an object. Specifying member variables as private can hide the variables and methods. Objects should hide their inner workings from the outside view. Good encapsulation improves code modularity by preventing objects from interacting with each other in an unexpected way, which in turn makes future development and refactoring efforts easy. 

Sample Code:

Class MyMarks {
 private int vmarks = 0;
 private String name;
 public void setMarks(int mark) throws MarkException {
  if (mark > 0) this.vmarks = mark;
  else {
   throw new MarkException("No negative Values");
  }
 }
 public int getMarks() {
  return vmarks;
 }
 //getters and setters for attribute name goes here. 
}

Being able to encapsulate members of a class is important for security and integrity. We can protect variables from unacceptable values. The sample code above describes how encapsulation can be used to protect the MyMarks object from having negative values. Any modification to member variable “vmarks” can only be carried out through the setter method setMarks(int mark). This prevents the object “MyMarks” from having any negative values by throwing an exception. 

Core Java Inheritance