top of page

Software Design Principles

  • Writer: megha dureja
    megha dureja
  • May 4, 2020
  • 3 min read

Updated: Feb 17, 2021

The design principles are meant to be a guide for designing software that's easy to maintain, extend, and understand.

SOLID — acronym for 5 software design principles that help us to keep technical debt under control.


1. Single Responsibility Principle

Every function, class or module should have one, and only one reason to change.

One reason to change => one responsibility!

How?

Write small classes with very specific names as opposed to large classes with generic names. For example, instead of throwing everything inside an Employee class, separate the responsibilities into EmployeeTimeLog, EmployeeTimeOff, EmployeeSalary, EmployeeDetails, etc. Now you have a designated place for everything and you know exactly where to look when you get back to your code a year later.


2. Open/Closed Principle

Classes, functions, or modules should be opened for extensibility, but closed for modification.

How?

Let’s say you need to implement an e-commerce module with multiple payment options. You can create a class Pay and add different payment options as separate methods. This would work, but it would result in changing the class whenever we want to add or change a payment option.


Instead, try creating a PayableInterface that will guide the implementation of each payment option. Then, for every payment option, you will have a separate class that implements that interface. Now the core of your system is closed for modification (you don’t need to change it every time you add new payment option), but open for extension (you can add new payment options by adding new classes).


3. Liskov Substitution Principle

Subtypes must be substitutable for their base type i.e Implementations of the same interface should never give a different result.

How?

Imagine this scenario: you’re working on a new app and instead of creating your database structure from the beginning you use a file system for testing purposes. Now, you have some repository that communicates with that file system - reads through the files and prepares them, so it can return the data to your program in an array format.

After completing the development process, you decide it’s time to replace the file system with a database. You change your repository to implement all the same methods for working with the database. But, the system breaks, because your database methods now return objects instead of arrays.

So, the best way to make sure you follow this principle is mindful programming. Always keep in mind what the system expects when you’re implementing its functionality.


4. Interface Segregation Principle

Clients should not be forced to depend on methods that they do not use.

How?

Prefer “Functional Interfaces” (Readable, Iterable, etc. “Functional Interface” has only one member) over “Header Interfaces”/”Fat interfaces” (interfaces with save(), read(), update(), move(), etc members)

The ISP Reinforces Other Principles:

By keeping interfaces small, the classes that implement them have a higher chance to fully substitute the interface (ISP <-> LSP);

Classes that implement small interfaces are more focused and tend to have a single purpose (ISP <-> SRP);


5. Dependency Inversion Principle

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

High-level modules are telling about “what” software should do.

Low-level modules are telling about “how” software should do.


PaymentProceesor(brings real value) /H.L

\|/

ProductRepo (Abstraction)

/|\

SqlProductRepo(execute business tasks) / L.L


How?

By combining the dependency injection technique with the concept of binding an interface to a concrete implementation, you can make sure you never depend on concrete classes. This will allow you to easily change the implementation of specific parts of the system without breaking it. A good example of this is switching your database driver from SQL to NoSQL. If you depend on the abstract interfaces for accessing the database, you’d be able to easily change the specific implementations and make sure the system works properly.

class PaymentProcessor {
    private repo: IProductRepo;

    constructor(repo: IProductRepo) {
        this.repo = repo;
    }

    public pay(productId: string): void {
        const product = this.repo.getById(productId);
        this.processPayment(product);
    }

    public processPayment(product: Product): void {
        // do something;
    }
}

const repo = ProductRepoFactory.create(DatabaseTypes.MYSQL);
const paymentProcessor = new PaymentProcessor(repo);
paymentProcessor.pay('some-product-id');

DI- A technique that allows the creation of dependent objects outside of a class and provides those objects to a class.

IoC- Inversion of Control is a design principle in which the control of object creation, configuration, and lifecycle is passed to a container of a framework.


The DIP, DI and IoC are the most effective ways to eliminate code coupling and keep systems easy to maintain and evolve.

Recent Posts

See All

Commentaires


Drop Me a Line, Let Me Know What You Think

Thanks for submitting!

© 2023 by Train of Thoughts. Proudly created with Wix.com

bottom of page