SOLID, KISS, YAGNI and DRY Principles
Nguyen Khac Nghiem
Posted on November 13, 2023
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.
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.");
}
}
✔️ 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.");
}
}
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.
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
}
}
✔️ 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
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.
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...
}
✔️ 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
{
}
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.
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);
/* ... */
}
✔️ 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);
}
Dependency Inversion Principle - DIP
This principle stipulates:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
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();
}
}
✔️ 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;
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Cat();
AnimalFactory factory = new AnimalFactory(animal);
}
}
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
Posted on November 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.