Object Oriented Programming (OOP)

From OasisSoftTech.com - Knowledge Base/Java/Springframework/Microservices/Cloud-AWS/AI
Revision as of 13:55, 21 July 2018 by Rasimsen (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Java is a computer programming language that is concurrent, class-based and object-oriented. The advantages of object oriented software development are shown below:

  • Modular development of code, which leads to easy maintenance and modification.
  • Reusability of code.
  • Improved reliability and flexibility of code.
  • Increased understanding of code.

Object-oriented programming contains many significant features, such as encapsulation, inheritance, polymorphism and abstraction. We analyze each feature separately in the following sections.


SOLID principles

SOLID is one of the most popular sets of design principles in object-oriented software development. It’s a mnemonic acronym for the following five design principles:

S – Single-Responsibility Principle. ---- “There should be never more than one reason for a class to change.”

O – Open-Closed Principle. ---- “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”

L – Liskov Substitution Principle. ---- “Subtypes must be substitutable for their base types.”

I – Interface Segregation Principle. ---- “Classes that implement interfaces, should not be forced to implement methods they do not use.”

D – Dependency Inversion Principle. ---- “High-level modules should not depend on low level modules, rather both should depend on abstraction. Abstraction should not depend on details; rather detail should depend on abstraction.”

Encapsulation

Encapsulation provides objects with the ability to hide their internal characteristics and behavior. Each object provides a number of methods, which can be accessed by other objects and change its internal data. In Java, there are three access modifiers: public, private and protected. Each modifier imposes different access rights to other classes, either in the same or in external packages. Some of the advantages of using encapsulation are listed below:

The internal state of every objected is protected by hiding its attributes. It increases usability and maintenance of code, because the behavior of an object can be independently changed or extended. It improves modularity by preventing objects to interact with each other, in an undesired way.

more on Encapsulation..

Polymorphism

we can perform a single action by different ways.

There are two types of polymorphism in java:

  • compile time polymorphism
  • runtime polymorphism.

We can perform polymorphism in java by method overloading and method overriding.

If you overload static method in java, it is the example of compile time polymorphism.

Example

public interface Vegetarian{}
public class Animal{}
public class Deer extends Animal implements Vegetarian{}

Now, the Deer class is considered to be polymorphic since this has multiple inheritance. Following are true for the above examples

  • A Deer IS-A Animal
  • A Deer IS-A Vegetarian
  • A Deer IS-A Deer
  • A Deer IS-A Object

When we apply the reference variable facts to a Deer object reference, the following declarations are legal

Deer d = new Deer();
Animal a = d;
Vegetarian v = d;
Object o = d;


Runtime polymorphism

Runtime polymorphism or Dynamic Method Dispatch is a process in which a call to an overridden method is resolved at runtime rather than compile-time.

In this process, an overridden method is called through the reference variable of a superclass. The determination of the method to be called is based on the object being referred to by the reference variable.

Upcasting

When reference variable of Parent class refers to the object of Child class, it is known as upcasting. For example:

class A{}  
class B extends A{}
A a=new B();//upcasting

Example of Java Runtime Polymorphism

class Bike{  
  void run(){System.out.println("running");}  
}  
class Splender extends Bike{  
  void run(){System.out.println("running safely with 60km");}  
  
  public static void main(String args[]){  
    Bike b = new Splender();//upcasting  
    b.run();  
  }  
}

Inheritance

Inheritance provides an object with the ability to acquire the fields and methods of another class, called base class. Inheritance provides re-usability of code and can be used to add additional features to an existing class, without modifying it.

note: Java doesn’t support Multiple Inheritance

Consider the below Java code. It shows error.

// First Parent class
class Parent1
{
    void fun()
    {
        System.out.println("Parent1");
    }
}
 
// Second Parent Class
class Parent2
{
    void fun()
    {
        System.out.println("Parent2");
    }
}
 
// Error : Test is inheriting from multiple
// classes
class Test extends Parent1, Parent2
{
   public static void main(String args[])
   {
       Test t = new Test();
       t.fun();
   }
}

Output:

Compiler Error

From the code, we see that, on calling the method fun() using Test object will cause complications such as whether to call Parent1’s fun() or Parent2’s fun() method.

Multiple inheritance is not supported by Java using classes , handling the complexity that causes due to multiple inheritance is very complex. It creates problem during various operations like casting, constructor chaining etc and the above all reason is that there are very few scenarios on which we actually need multiple inheritance, so better to omit it for keeping the things simple and straightforward

How are above problems handled for Default Methods and Interfaces ?

Java 8 supports default methods where interfaces can provide default implementation of methods. And a class can implement two or more interfaces. In case both the implemented interfaces contain default methods with same method signature, the implementing class should explicitly specify which default method is to be used or it should override the default method.

// A simple Java program to demonstrate multiple
// inheritance through default methods.
interface PI1
{
    // default method
    default void show()
    {
        System.out.println("Default PI1");
    }
}
 
interface PI2
{
    // Default method
    default void show()
    {
        System.out.println("Default PI2");
    }
}
 
// Implementation class code
class TestClass implements PI1, PI2
{
    // Overriding default show method
    public void show()
    {
        // use super keyword to call the show
        // method of PI1 interface
        PI1.super.show();
 
        // use super keyword to call the show
        // method of PI2 interface
        PI2.super.show();
    }
 
    public static void main(String args[])
    {
        TestClass d = new TestClass();
        d.show();
    }
}

Output:

Default PI1
Default PI2

If we remove implementation of default method from “TestClass”, we get compiler error. See this for a sample run.

If there is a diamond through interfaces, then there is no issue if none of the middle interfaces provide implementation of root interface. If they provide implementation, then implementation can be accessed as above using super keyword.

// A simple Java program to demonstrate how diamond
// problem is handled in case of default methods
 
interface GPI
{
    // default method
    default void show()
    {
        System.out.println("Default GPI");
    }
}
 
interface PI1 extends GPI { }
 
interface PI2 extends GPI { }
 
// Implementation class code
class TestClass implements PI1, PI2
{
    public static void main(String args[])
    {
        TestClass d = new TestClass();
        d.show();
    }
}

Output:

Default GPI

Abstraction

Abstraction is the process of separating ideas from specific instances and thus, develop classes in terms of their own functionality, instead of their implementation details. Java supports the creation and existence of abstract classes that expose interfaces, without including the actual implementation of all methods. The abstraction technique aims to separate the implementation details of a class from its behavior.

more on Abstraction..

Differences between Interface and Abstract

pghvG.jpg

Differences between Abstraction and Encapsulation

Abstraction and encapsulation are complementary concepts. On the one hand, abstraction focuses on the behavior of an object. On the other hand, encapsulation focuses on the implementation of an object’s behavior. Encapsulation is usually achieved by hiding information about the internal state of an object and thus, can be seen as a strategy used in order to provide abstraction.

difference-between-abstraction-and-encapsulation.jpg

Composition vs Inheritance

Both composition and inheritance are object oriented programming concepts. They are not tied up with any specific programming language such as java. Before we compare composition over inheritance programmatically, let’s have a quick definition about them.

Composition

Composition is the design technique in object oriented programming to implement has-a relationship between objects. Composition in java is achieved by using instance variables of other objects. For example, Person has-a Job relationship is implemented like below in java object oriented programming.

package com.journaldev.composition;

public class Job {
// variables, methods etc.
}
package com.journaldev.composition;

public class Person {

    //composition has-a relationship
    private Job job;

    //variables, methods, constructors etc.
}

Inheritance

Inheritance is the design technique in object oriented programming to implement is-a relationship between objects. Inheritance in Java is implemented using extends keyword.

For example, Cat is a Animal relationship in java programming will be implemented like below.

package com.journaldev.inheritance;
 
public class Animal {
// variables, methods etc.
}
package com.journaldev.inheritance;
 
public class Cat extends Animal{
}


Composition over Inheritance

Both composition and inheritance promotes code reuse through different approaches. So which one to choose? How to compare composition vs inheritance. You must have heard that in programming you should favor composition over inheritance. Let’s see some of the reasons that will help you in choosing composition vs inheritance:

  • Inheritance is tightly coupled whereas composition is loosely coupled.
  • There is no access control in inheritance whereas access can be restricted in composition.
  • Composition provides flexibility in invocation of methods that is useful with multiple subclass scenario.
    • One more benefit of composition over inheritance is testing scope.


That’s all for composition vs inheritance. You have got enough reasons to choose composition over inheritance. Use inheritance only when you are sure that superclass will not be changed, otherwise go for composition.

details:

  • Inheritance is tightly coupled whereas composition is loosely coupled. Let’s assume we have below classes with inheritance.
package com.journaldev.java.examples;

public class ClassA {

	public void foo(){	
	}
}

class ClassB extends ClassA{
	public void bar(){
		
	}
}

For simplicity we have both the superclass and subclass in single package. But mostly they will be in separate codebase. There could be many classes extending the superclass ClassA. A very common example of this situation is extending Exception class.

package com.journaldev.java.examples;

public class ClassA {

	public void foo(){	
	}
	
	public int bar(){
		return 0;
	}
}

As soon as you start using new ClassA implementation, you will get compile time error in ClassB as The return type is incompatible with ClassA.bar(). The solution would be to change either the superclass or the subclass bar() method to make them compatible.

If you would have used Composition over inheritance, you will never face this problem. A simple example of ClassB implementation using Composition can be like below.

class ClassB{
	ClassA classA = new ClassA();
	
	public void bar(){
		classA.foo();
		classA.bar();
	}
}
  • There is no access control in inheritance whereas access can be restricted in composition. We expose all the superclass methods to the other classes having access to subclass. So if a new method is introduced or there are security holes in the superclass, subclass becomes vulnerable. Since in composition we choose which methods to use, it’s more secure than inheritance. For example, we can provide ClassA foo() method exposure to other classes using below code in ClassB.
class ClassB {
	
	ClassA classA = new ClassA();
	
	public void foo(){
		classA.foo();
	}
	
	public void bar(){	
	}
	
}

This is one of the major advantage of composition over inheritance.

  • Composition provides flexibility in invocation of methods that is useful with multiple subclass scenario. For example, let’s say we have below inheritance scenario.
abstract class Abs {
	abstract void foo();
}

public class ClassA extends Abs{

	public void foo(){	
	}
	
}

class ClassB extends Abs{
		
	public void foo(){
	}
	
}

class Test {
	
	ClassA a = new ClassA();
	ClassB b = new ClassB();

	public void test(){
		a.foo();
		b.foo();
	}
}

So what if there are more subclasses, will composition make our code ugly by having one instance for each subclass? No, we can rewrite the Test class like below.

class Test {
	Abs obj = null;
	
	Test1(Abs o){
		this.obj = o;
	}
	
	public void foo(){
		this.obj.foo();
	}

}

This will give you flexibility to use any subclass based on the object used in the constructor.


  • One more benefit of composition over inheritance is testing scope. Unit testing is easy in composition because we know what all methods we are using from other class. We can mock it up for testing whereas in inheritance we depend heavily on superclass and don’t know what all methods of superclass will be used. So we will have to test all the methods of superclass. This is an extra work and we need to do it unnecessarily because of inheritance.

That’s all for composition vs inheritance. You have got enough reasons to choose composition over inheritance. Use inheritance only when you are sure that superclass will not be changed, otherwise go for composition.

Immutable Class in Java

Immutable class is good for caching purpose because you don’t need to worry about the value changes. Other benefit of immutable class is that it is inherently thread-safe, so you don’t need to worry about thread safety in case of multi-threaded environment.


To create immutable class in java, you have to do following steps.

  • Declare the class as final so it can’t be extended.
  • Make all fields private so that direct access is not allowed.
  • Don’t provide setter methods for variables
  • Make all mutable fields final so that it’s value can be assigned only once.
  • Initialize all the fields via a constructor performing deep copy.
  • Perform cloning of objects in the getter methods to return a copy rather than returning the actual object reference.

To understand points 4 and 5, let’s run the sample Final class that works well and values doesn’t get altered after instantiation.

FinalClassExample.java

package com.journaldev.java;

import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;
	
	public int getId() {
		return id;
	}


	public String getName() {
		return name;
	}

	/**
	 * Accessor function for mutable objects
	 */
	public HashMap<String, String> getTestMap() {
		//return testMap;
		return (HashMap<String, String>) testMap.clone();
	}

	/**
	 * Constructor performing Deep Copy
	 * @param i
	 * @param n
	 * @param hm
	 */
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");
		this.id=i;
		this.name=n;
		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = hm.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, hm.get(key));
		}
		this.testMap=tempMap;
	}
	
	
	/**
	 * Constructor performing Shallow Copy
	 * @param i
	 * @param n
	 * @param hm
	 */
	/**
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Shallow Copy for Object initialization");
		this.id=i;
		this.name=n;
		this.testMap=hm;
	}
	*/
	
	/**
	 * To test the consequences of Shallow Copy and how to avoid it with Deep Copy for creating immutable classes
	 * @param args
	 */
	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		//Lets see whether its copy by field or reference
		System.out.println(s==ce.getName());
		System.out.println(h1 == ce.getTestMap());
		//print the ce values
		System.out.println("ce id:"+ce.getId());
		System.out.println("ce name:"+ce.getName());
		System.out.println("ce testMap:"+ce.getTestMap());
		//change the local variable values
		i=20;
		s="modified";
		h1.put("3", "third");
		//print the values again
		System.out.println("ce id after local variable change:"+ce.getId());
		System.out.println("ce name after local variable change:"+ce.getName());
		System.out.println("ce testMap after local variable change:"+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());

	}

}

Output of the above immutable class in java example program is:

Performing Deep Copy for Object initialization
true
false
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{2=second, 1=first}
ce testMap after changing variable from accessor methods:{2=second, 1=first}

Now let’s comment the constructor providing deep copy and uncomment the constructor providing shallow copy. Also uncomment the return statement in getTestMap() method that returns the actual object reference and then execute the program once again.

Performing Shallow Copy for Object initialization
true
true
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{3=third, 2=second, 1=first}
ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}

As you can see from the output, HashMap values got changed because of shallow copy in the constructor and providing direct reference to the original object in the getter function.

Stack and Heap

https://qph.fs.quoracdn.net/main-qimg-1c8eb714ca00e19167803a5e52b67c4f


Is Java a pure object-oriented programming language?

Java is not because it supports Primitive datatype such as int, byte, long... etc, to be used, which are not objects. Contrast with a pure OOP language like Smalltalk, where there are no primitive types, and boolean, int and methods are all objects.

A Pure Object-Oriented Programming Language should be true for all the 7 conditions mentioned below:

  • Inheritance
  • Encapsulation/Data Hiding
  • Polymorphism
  • Abstraction
  • All predefined types are objects
  • All operations are performed by sending messages to objects
  • All user defined types are objects


Coupling

The degree of dependency between two modules. We always want low coupling.

Cohesion

The measure of how strongly-related is the set of functions performed by a module. We always want high cohesion.

All methodologies try to reduce coupling and increase cohesion. Information Hiding reduces coupling by isolating the details of the implementation of state.

  • OOP adds another step in the reduction of coupling with the enforcement of encapsulation and the introduction of dynamic binding and polymorphism.
  • Inheritance allows us to increase cohesion by defining hierarchies based on generalization and specialization, in which we can separate the functionality that belongs to the superclass from its subclasses.
  • AOP provides a solution for the problem of cross-cutting concerns, so that both the aspects and the affected methods may become more cohesive.

Separation of concerns (SoC)

Separation of concerns is the process of breaking a computer program into distinct features that overlap in functionality as little as possible. A concern is any piece of interest or focus in a program. Typically, concerns are synonymous with features or behaviors.

http://en.wikipedia.org/wiki/Separation_of_concerns

The separation allows:

  • To allow people to work on individual pieces of the system in isolation;
  • To facilitate reusability;
  • To ensure the maintainability of a system;
  • To add new features easily;
  • To enable everyone to better understand the system;

Single Responsibility Principle (SRP)

Single Responsibility Principle – every object should have a single responsibility, and that all its services should be narrowly aligned with that responsibility. On some level Cohesion is considered as synonym for SRP.

http://en.wikipedia.org/wiki/Single_responsibility_principle