Introduction
Design principles are the fundamental rules that guide developers in writing clear, flexible, and maintainable code. These principles help prevent complexity, reduce errors, and make code easier to understand and adapt. Here are some of the most widely-used design principles:
SOLID
The SOLID principles are among the most influential design principles, each addressing specific aspects of good software design. Here’s an in-depth look at these principles along with TypeScript examples.
Single Responsibility Principle (SRP)
A class or module should only have one reason to change, meaning it should only have one job or responsibility. This helps keep classes focused and avoids unexpected side effects.
In the initial version, UserService
handles both user creation and email sending, violating SRP. By separating them, each class has a single responsibility, making it easier to maintain and test.
Open-Closed Principle (OCP)
Classes or modules should be open for extension but closed for modification. This means adding new functionality should not require changing existing code, reducing the risk of breaking existing features.
In the initial version, adding a new discount type requires modifying DiscountService
. By using interfaces, we can add new discount types without changing the DiscountService
code.
Liskov Substitution Principle (LSP)
Objects of a subclass should be replaceable with objects of the parent class without affecting the correctness of the program.
In the initial design, Square
inherits from Rectangle
, but changing one dimension breaks its behavior as a square. By separating Rectangle
and Square
, we adhere to LSP.
Interface Segregation Principle (ISP)
Clients should not be forced to implement interfaces they do not use. It’s better to have many small, specific interfaces than a large, general-purpose one.
In the initial version, Robot
is forced to implement eat()
, even though it doesn’t make sense. By segregating interfaces, each class implements only what it needs.
Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules but rather on abstractions. This reduces tight coupling and makes the code more flexible.
In the improved design, UserService
depends on the Database
interface, not the concrete SQLDatabase
. This allows for easier swapping of database implementations.
DRY (Don’t Repeat Yourself)
Avoid duplicating code to reduce errors and simplify updates. When functionality changes, you only need to update it in one place.
KISS (Keep It Simple, Stupid)
Keep code and systems simple, clear, and straightforward, avoiding unnecessary complexity.
Favor simple logic over overly complex solutions or abstractions that aren’t needed immediately.
YAGNI (You Aren’t Gonna Need It)
Only implement features when they’re necessary, avoiding feature bloat and excess complexity.
Avoid adding future-use methods or classes until there’s a clear need.
Summary
Design principles like SOLID, DRY, KISS, and YAGNI keep code clean, maintainable, and adaptable to change. By following these principles, developers can create systems that are easier to understand, extend, and scale. As we continue, we’ll explore how these principles integrate with development methodologies, architectural patterns, and coding standards to create robust software.