Low Level Design

Topics:

  1. OOP Principles
    1. P.I.E.A.
  2. SOLID
  3. Association, Aggregation, Composition

SOLID


  1. Single Responsibility
  2. Open Close
  3. Lincov Substitution (e.g. inhering methods of interface in class ..method or not more restrictive than super class)
  4. Dependency Inversion Principle

Single-Responsibility Principle

  • A class should have one and only one reason to change, meaning that a class should have only one job.
  • The single responsibility principle states that every Java class must perform a single functionality. Implementation of multiple functionalities in a single class mashup the code and if any modification is required may affect the whole class. It precise the code and the code can be easily maintained. 

How to achieve –

 Let’s understand the single responsibility principle through an example

Suppose, Student is a class having three methods namely printDetails(), calculatePercentage(), and addStudent(). Hence, the Student class has three responsibilities to print the details of students, calculate percentages, and database. By using the single responsibility principle, we can separate these functionalities into three separate classes to fulfill the goal of the principle.

public class Student  
{  
public void printDetails();  
{  
//functionality of the method  
}  
pubic void calculatePercentage();  
{  
//functionality of the method  
}  
public void addStudent();  
{  
//functionality of the method  
}  

The above code snippet violates the single responsibility principle. To achieve the goal of the principle, we should implement a separate class that performs a single functionality only.

public class Student  
{  
public void addStudent();  
{  
//functionality of the method  
}  
}  
public class PrintStudentDetails  
{  
public void printDetails();  
{  
//functionality of the method  
}  
}  
public class Percentage  
{  
public void calculatePercentage();  
{  
//functionality of the method  
}  
}  

Why is that this Principle is Required?

When the Single Responsibility Principle is followed, testing is easier. With a single responsibility, the class will have fewer test cases. Less functionality also means fewer dependencies to other classes. It leads to better code organization since smaller and well-purposed classes are easier to search.

Open-Closed Principle

Objects or entities(e.g., classes, modules, functions)  should be open for extension but closed for modification.

Ways of extending the class include:

  1. Inheriting from the class
  2. Overwriting the required behaviors from the class
  3. Extending certain behaviors of the class

Consider the below method of the class Vehicle:

public class Vehicle {
    public double calculateMileage(Vehicle v) {
        if (v instanceof Car) {
            return v.getValue() * 0.8;
        if (v instanceof Bike) {
            return v.getValue() * 0.5;

    }
}

Suppose we now want to add another subclass called Truck. We would have to modify the above class by adding another if statement, which goes against the Open-Closed Principle.
A better approach would be for the subclasses Car and Truck to override the calculateValue method:

public class Vehicle {
    public double calculateMileage() {...}
}
public class Car extends Vehicle {
    public double calculateMileage() {
        return this.getValue() * 0.8;
}
public class Truck extends Vehicle{
    public double calculateMileage() {
        return this.getValue() * 0.9;
}

Why is that this principle is required?

Using this principle separates the existing code from the modified code so it provides better stability, maintainability and minimizes changes as in your code.

Liskov Substitution Principle –

  1. Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
  2. LSP applies to inheritance hierarchies such that derived classes must be completely substitutable for their base classes.

Consider a typical example of a Square derived class and Rectangle base class:

public class Rectangle {
    private double height;
    private double width;
    public void setHeight(double h) { height = h; }
    public void setWidht(double w) { width = w; }
    ...
}
public class Square extends Rectangle {
    public void setHeight(double h) {
        super.setHeight(h);
        super.setWidth(w);
    }
    public void setWidth(double h) {
        super.setHeight(h);
        super.setWidth(w);
    }
}

The above classes do not obey LSP because you cannot replace the Rectangle base class with its derived class Square. The Square class has extra constraints, i.e., the height and width must be the same. Therefore, substituting Rectangle with Square class may result in unexpected behavior.

Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

Suppose, we have created an interface named Conversion having three methods intToDouble(), intToChar(), and charToString().

public interface Conversion  
{  
public void intToDouble();  
public void intToChar();  
public void charToString();  
}  

The above interface has three methods. If we want to use only a method intToChar(), we have no choice to implement the single method. To overcome the problem, the principle allows us to split the interface into three separate ones.

public interface ConvertIntToDouble  
{  
public void intToDouble();  
}   
public interface ConvertIntToChar  
{  
public void intToChar();  
}  
public interface ConvertCharToString   
{  
public void charToString();  
}  

Now we can use only the method that is required. Suppose, we want to convert the integer to double and character to string then, we will use only the methods intToDouble() and charToString().

public class DataTypeConversion implements ConvertIntToDouble, ConvertCharToString   
{  
public void intToDouble()  
{  
//conversion logic  
}  
public void charToString()  
{  
//conversion logic  
}  
}  

Dependency Inversion Principle

  1. Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.
  2. The Dependency Inversion Principle (DIP) states that we should depend on abstractions (interfaces and abstract classes) instead of concrete implementations (classes). The abstractions should not depend on details; instead, the details should depend on abstractions.

Consider the example below. We have a Car class that depends on the concrete Engine class; therefore, it is not obeying DIP.

public class Car {
    private Engine engine;
    public Car(Engine e) {
        engine = e;
    }
    public void start() {
        engine.start();
    }
}
public class Engine {
   public void start() {...}
}

The code will work, for now, but what if we wanted to add another engine type, let’s say a diesel engine? This will require refactoring the Car class.
However, we can solve this by introducing a layer of abstraction. Instead of Car depending directly on Engine, let’s add an interface:

public interface EngineInterface {
    public void start();
}

Now we can connect any type of Engine that implements the Engine interface to the Car class:

public class Car {
    private EngineInterface engine;
    public Car(EngineInterface e) {
        engine = e;
    }
    public void start() {
        engine.start();
    }
}
public class PetrolEngine implements EngineInterface {
   public void start() {...}
}
public class DieselEngine implements EngineInterface {
   public void start() {...}
}

Association Aggregation and Composition

Refer article : Core Java : Interview Section Article

Published by

Unknown's avatar

sevanand yadav

software engineer working as web developer having specialization in spring MVC with mysql,hibernate

Leave a comment