SOLID, KISS, YAGNI and DRY Principles

nknghiem

Nguyen Khac Nghiem

Posted on November 13, 2023

SOLID, KISS, YAGNI and DRY Principles

SOLID

This principle was given by Robert C. Martin and Michael Feathers to encourage us to create more maintainable, understandable, and flexible software. Including 5 sub-principles:

  • Single responsibility principle (SRP)
  • Open/Closed principle (OCP)
  • Liskov substitution principle (LSP)
  • Interface segregation principle (ISP)
  • Dependency inversion principle (DIP)

Single Responsibility Principle - SRP

This principle stipulates that each Class should have a single responsibility.
SRP

If a Class has many responsibilities, making changes to one of its responsibilities can affect the other ones.

❌ Violating SRP:



public class Animal {
    public void catSays() {
        System.out.println("I am a cat.");
    }

    public void lionSays() {
        System.out.println("I am a lion.");
    }

    public void hippoSays() {
        System.out.println("I am a hippo.");
    }
}


Enter fullscreen mode Exit fullscreen mode

✔️ Following SRP:



public abstract class Animal {
    public abstract void makeSound();
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("I am a cat.");
    }
}

public class Lion extends Animal {
    @Override
    public void makeSound() {
        System.out.println("I am a lion.");
    }
}

public class Hippo extends Animal {
    @Override
    public void makeSound() {
        System.out.println("I am a hippo.");
    }
}


Enter fullscreen mode Exit fullscreen mode

Open/Closed Principle - OCP

This principle stipulates that if there is a new function, you should not modify or add to the existing class, but should write another class that extends the existing class.

OCP

Class should be open for extension but closed for modification.

❌ Violating OCP:



public class Animal {
    private String type;

    public Animal(String type) {
        this.type = type;
    }

    public void draw() {
        if (type.equalsIgnoreCase("cat")) {
            System.out.println("Drawing a cat");
        } else if (type.equalsIgnoreCase("lion")) {
            System.out.println("Drawing a lion");
        }
        // More animals can be added here, violating OCP
    }
}


Enter fullscreen mode Exit fullscreen mode

✔️ Following OCP:



public abstract class Animal {
    public abstract void draw();
}

public class Cat extends Animal {
    @Override
    public void draw() {
        System.out.println("Drawing a cat");
    }
}

public class Lion extends Animal {
    @Override
    public void draw() {
        System.out.println("Drawing a lion");
    }
}

// You can add more animal classes without modifying existing code


Enter fullscreen mode Exit fullscreen mode

Liskov Substitution Principle - LSP

This principle stipulates that subclasses that inherit from a parent class can replace the parent class without affecting the correctness of the program.

LSP

The child Class should be able to process the same requests and deliver the same result as the parent Class or it could deliver a result that is of the same type.

❌ Violating LSP:



public class Animal
{
    public void run()
    {
        System.out.println("Run...");
    }

    public void fly()
    {
        System.out.println("Fly...");
    }
}

public class Bird extends Animal
{
    // Bird can fly and run...
}

public class Cat extends Animal
{
    // Cat can't fly...
}


Enter fullscreen mode Exit fullscreen mode

✔️ Following LSP:



public interface Flyable {
    public void fly();
}

public class Animal
{
    public void run()
    {
        System.out.println("Run...");
    }
}

public class Bird extends Animal implements Flyable
{
    @Override
    public void fly()
    {
        System.out.println("Fly...");
    }
}

public class Cat extends Animal
{

}


Enter fullscreen mode Exit fullscreen mode

Interface Segregation Principle - ISP

This principle stipulates that an interface should not have too many methods that need to be implemented. If an interface is too large, it should be split into many smaller interfaces that handle separate functions.

ISP

No code should be forced to depend on methods it does not use. A Class should perform only actions that are needed to fulfill its role.

❌ Violating ISP:



interface Animal {
    List<Animal> getAll();
    get(String id);
    save(Animal animal);
    update(String id, Animal animal);
    delete(String id);
    getAllWithPaginate(int page, int size);
    getAllWithSort(String sortCriteria);
    /* ... */
}


Enter fullscreen mode Exit fullscreen mode

✔️ Following ISP:



interface CrudAnimal {
    List<Animal> getAll();
    get(String id);
    save(Animal animal);
    update(String id, Animal animal);
    delete(String id);
}

interface PagingAndSortingAnimal {
    getAllWithPaginate(int page, int size);
    getAllWithSort(String sortCriteria);
}


Enter fullscreen mode Exit fullscreen mode

Dependency Inversion Principle - DIP

This principle stipulates:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend upon details. Details should depend upon abstractions.

DIP

The essence of this principle is to avoid dependence on modules, specific components that are easy to change during the coding process. Instead, it should depend on abstract components because these components are less likely to change. The way to apply and implement this principle is to have high-level modules define interfaces, then low-level modules will implement those interfaces.

❌ Violating DIP:



// Abstraction
interface Animal {
    void makeSound();
}

// Low-level Module
class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("I am a cat");
    }
}

// Low-level Module
class Lion implements Animal {
    @Override
    public void makeSound() {
        System.out.println("I am a lion");
    }
}

// Low-level Module
class Hippo implements Animal {
    @Override
    public void makeSound() {
        System.out.println("I am a hippo");
    }
}

// High-level Module
class AnimalFactory {
    // High-level modules depend on low-level modules
    private final Cat animal;

    public AnimalFactory() {
        this.animal = new Cat();
        this.animal.makeSound();
    }

    public Cat getAnimal() {
        return this.animal;
    }
}

public class Main {
    public static void main(String[] args) {
        AnimalFactory factory = new AnimalFactory();
    }
}


Enter fullscreen mode Exit fullscreen mode

✔️ Following DIP:



// Abstraction
interface Animal {
void makeSound();
}

// Low-level Module
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("I am a cat");
}
}

// Low-level Module
class Lion implements Animal {
@Override
public void makeSound() {
System.out.println("I am a lion");
}
}

// Low-level Module
class Hippo implements Animal {
@Override
public void makeSound() {
System.out.println("I am a hippo");
}
}

// High-level Module
class AnimalFactory {
// High-level modules depend on abstractions
private final Animal animal;

public AnimalFactory(Animal animal) {
    this.animal = animal;
    this.animal.makeSound();
}

public Animal getAnimal() {
    return this.animal;
}
Enter fullscreen mode Exit fullscreen mode

}

public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
AnimalFactory factory = new AnimalFactory(animal);
}
}

Enter fullscreen mode Exit fullscreen mode




Keep It Simple, Stupid - KISS

This principle was given by Kelly Johnson with the meaning of emphasizing the importance of simplicity in coding. The simpler the code, the faster the ability to read and understand that code, the simpler it is, the easier it is to maintain and change in the future, this will help save a lot of time.

The ways to approach KISS:

  • Do not abuse design patterns or libraries if not necessary.
  • Divide big problems into smaller problems to handle.
  • Name variables and methods clearly.

You Aren't Gonna Need It - YAGNI

This principle was coined by Kent Beck. It focuses on not complicating a requirement with future assumptions. In other words, don't assume and build the functionality of software before you need to use it.

Don't Repeat Yourself - DRY

DRY is a familiar and core principle in the programming industry that emphasizes reusing code as much as possible. Principles were formulated by Andrew Hunt and David Thomas.

This principle makes parts of the code less repetitive, making it easier and faster to change code segments.

To approach this principle, whenever there is a piece of code that is used twice in different places, you should repackage that piece of code (create functions, create classes, ...) so that it can be called later.

Reference

  1. SOLID Principles
  2. The Principles of Clean Code: DRY, KISS, and YAGNI
💖 💪 🙅 🚩
nknghiem
Nguyen Khac Nghiem

Posted on November 13, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

SOLID, KISS, YAGNI and DRY Principles
programming SOLID, KISS, YAGNI and DRY Principles

November 13, 2023