DEV Community

vikash-agrawal
vikash-agrawal

Posted on • Updated on

Design Principles

SOLID

Single Responsibility Principle: A class should be defined to have only one responsibility to avoid compiling and testing the totally dependent changes in case the class is mixed of many responsibilities. Logger and java Persistence API (JPA) are the ideal example for this.
Open/Closed Principle: Open for extension and closed for modification. It mandates to have the interface defined so that any new implementations can be provided independently and plugged without modifying the existing.

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

    }
}
Enter fullscreen mode Exit fullscreen mode

if there is a requirement to calculate the cost for another Vehicle type, say 'Cycle' then above method would required to be modified.
But if the above class is modified in the following way, then any new vehicle type can be added independetly

public class Vehicle {
    public double calculateValue() {...}
}
public class Car extends Vehicle {
    public double calculateValue() {
        return this.getValue() * 0.8;
}
public class Cycle extends Vehicle{
    public double calculateValue() {
        return this.getValue() * 0.2;
}
Enter fullscreen mode Exit fullscreen mode

Liskov Substitution Principle: With the interface-based implementations, you can override the method with parameter of the method being of sub class. Means the type of super class can be replaced with the object of child class.
Interface Segregation Principle: it avoids implementing the unrequired method from the interface. e.g.

public interface Vehicle {
    public void drive();
    public void stop();
    public void refuel();
    public void openDoors();
}
public class Bike implements Vehicle {

    // Can be implemented
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}

    // Can not be implemented
    public void openDoors() {...}
}
Enter fullscreen mode Exit fullscreen mode

Instead it mandates to have another interface inheriting the super interface and have this sub interface declared with specific method.

public interface Vehicle {
    public void drive();
    public void stop();
    public void refuel();
}
public interface FourWheeler extends Vehicle {
    public void openDoors();
}
public class Bike implements Vehicle {
    public void drive() {...}
    public void stop() {...}
    public void refuel() {...}
}
Enter fullscreen mode Exit fullscreen mode

Dependency Inversion: Dependency should be on abstractions (interfaces and abstract classes) instead of concrete implementations (classes).
Consider the example below. We have a Car class that depends on the concrete Engine class

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

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();
}
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() {...}
}
Enter fullscreen mode Exit fullscreen mode

DRY

Don't repeat yourself to avoid the duplication of the code so the maintainability becomes easy. It also helps to find the bugs.

REST

• Uniform Interface: A resource in the system should have only one logical URI and that should provide a way to fetch related or additional data.
• Client Server: This essentially means that client application and server application MUST be able to evolve separately without any dependency on each other.
• Stateless: If client application needs to be a stateful application for the end user, where user logs in once and do other authorized operations thereafter, then each request from the client should contain all the information necessary to service the request – including authentication and authorization details.
• Cacheable: Well-managed caching partially or completely eliminates some client-server interactions, further improving scalability and performance.
• Layer: REST allows you to use a layered system architecture where you deploy the APIs on server A, and store data on server B and authenticate requests in Server C
• Code on demand (optional): You are free to return executable code to support a part of your application e.g. clients may call your API to get a UI widget rendering code.

Microservice

Domain Driven Design: Domain driven design along with related technical logic makes sure to have single responsibility microservice.
Hide implementation detail: with so many microservices being part of the complete application hide the implementations of one microservice from other.
Decentralized: With so many microservices around, it has the flexibility to have a different DB or different schema of the same DB with each of the microservice. With this DB migration becomes easier.
Failure isolation: with one microservice being unavailable due to any reason being scalability or performance impact that particular functionality not the entire application.
Continuous Deliver: With so many microservices being part of single application mandates to have the CI/CD pipeline so it requires to have collaboration between the developers and dev ops.

Best Practices

• Encapsulate what changes: have all the methods and fields declared as private and then based on the need increase the accessibility, don't jump directly to public.
• Favor Composition Over Inheritance: Composition allows changing the behavior of a class at runtime by setting property.
• Programming for Interface Not Implementation: use interface type in variable, return type or argument of method.
• Delegation Principle: Don't do all the stuff yourself. Like if you want any objects to be compared using equals or hashcode, the client doesn't do it, it's the class which does it.

Top comments (0)