hello@evolucionapps.com

+52 (664) 615-8173

SOLID Principles in Software Engineering

The SOLID principles are essential guidelines in software engineering that help create more understandable, flexible, and maintainable code. They are key to writing good object-oriented code. Let’s dive into each principle in a simple and friendly way:

Single Responsibility Principle (SRP)

A class should have only one reason to change. Imagine a class as a single task or responsibility. If a class has multiple tasks, it becomes confusing and harder to maintain. For example, in a blog application, a Post class should only handle the blog post details, while a PostRepository class should manage saving posts to the database.

// Violates SRP
public class User {
    public void AddUserToDatabase() { /* code to add user to database */ }
    public void GenerateReport() { /* code to generate report */ }
}

// Adheres to SRP
public class User {
    // User properties and methods
}

public class UserRepository {
    public void AddUserToDatabase(User user) { /* code to add user to database */ }
}

public class UserReport {
    public void GenerateReport(User user) { /* code to generate report */ }
}
Open/Closed Principle (OCP)

Classes should be open for extension but closed for modification. This means you should be able to add new features to a class without changing its existing code. This is often done using inheritance or interfaces. For example, if you need new discount types in an e-commerce app, you can add them without changing the main discount class.

// Violates OCP
public class Discount {
    public double ApplyDiscount(string discountType, double amount) {
        if (discountType == "percentage") {
            return amount * 0.9;
        } else if (discountType == "fixed") {
            return amount - 10;
        }
        return amount;
    }
}

// Adheres to OCP
public abstract class Discount {
    public abstract double ApplyDiscount(double amount);
}

public class PercentageDiscount : Discount {
    public override double ApplyDiscount(double amount) {
        return amount * 0.9;
    }
}

public class FixedDiscount : Discount {
    public override double ApplyDiscount(double amount) {
        return amount - 10;
    }
}
Liskov Substitution Principle (LSP)

Subclasses should be replaceable with their base classes without affecting the program’s core. If a class is a subclass of another, you should be able to use the subclass anywhere the parent class is expected without issues. For example, a Bird class can have a Fly method, but not all birds can fly, so we need to design it properly.

// Violates LSP
public class Bird {
    public virtual void Fly() { /* fly implementation */ }
}

public class Ostrich : Bird {
    public override void Fly() {
        throw new NotImplementedException(); // Ostriches can't fly
    }
}

// Adheres to LSP
public abstract class Bird {
    public abstract void Move();
}

public class FlyingBird : Bird {
    public override void Move() { Fly(); }
    private void Fly() { /* fly implementation */ }
}

public class Ostrich : Bird {
    public override void Move() { Run(); }
    private void Run() { /* run implementation */ }
}
Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use. It’s better to create smaller, specific interfaces than one large one. This way, classes only need to implement the methods they actually use.

// Violates ISP
public interface IWorker {
    void Work();
    void Eat();
}

public class Worker : IWorker {
    public void Work() { /* work implementation */ }
    public void Eat() { /* eat implementation */ }
}

public class Robot : IWorker {
    public void Work() { /* work implementation */ }
    public void Eat() {
        throw new NotImplementedException(); // Robots don't eat
    }
}

// Adheres to ISP
public interface IWorkable {
    void Work();
}

public interface IEatable {
    void Eat();
}

public class Worker : IWorkable, IEatable {
    public void Work() { /* work implementation */ }
    public void Eat() { /* eat implementation */ }
}

public class Robot : IWorkable {
    public void Work() { /* work implementation */ }
}
Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

This principle is about decoupling your code by using interfaces or abstract classes. High-level modules shouldn’t directly depend on low-level modules. Instead, they should both depend on abstractions, making the system more flexible.

// Violates DIP
public class LightBulb {
    public void TurnOn() { /* turn on light bulb */ }
}

public class Switch {
    private LightBulb _lightBulb;

    public Switch() {
        _lightBulb = new LightBulb();
    }

    public void Operate() {
        _lightBulb.TurnOn();
    }
}

// Adheres to DIP
public interface ISwitchable {
    void TurnOn();
}

public class LightBulb : ISwitchable {
    public void TurnOn() { /* turn on light bulb */ }
}

public class Switch {
    private ISwitchable _device;

    public Switch(ISwitchable device) {
        _device = device;
    }

    public void Operate() {
        _device.TurnOn();
    }
}

Applying the SOLID principles in your code helps make it more robust, maintainable, and scalable. These principles provide a solid foundation for building software that can adapt to change and evolve over time.

Unleash Your Potential

Discover Outsourcing Services for Transformative App Development.

We can empower your business with our cutting-edge app development solutions.

Get in Touch.

We’re here to assist you. Whether you have questions about our services, want to discuss a project, or simply need more information, feel free to reach out to us.

+52 (664) 615- 8173

hello@evolucionapps.com

Blvd Sánchez Taboada # 10488 Piso 8 int A
Zona Urbana Rio, CP 22010
Tijuana , B.C. México