This repository demonstrates the application of SOLID principles in Dart, focusing on object-oriented programming practices. The SOLID principles are a set of design guidelines that aim to make software designs more understandable, flexible, and maintainable.
- Introduction
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- Conclusion
SOLID is an acronym that stands for five design principles intended to improve software design and reduce the risk of software rot. The principles are:
- Single Responsibility Principle (
SRP
) - Open/Closed Principle (
OCP
) - Liskov Substitution Principle (
LSP
) - Interface Segregation Principle (
ISP
) - Dependency Inversion Principle (
DIP
)
By following these principles, developers can create systems that are easier to manage, scale, and understand.
The Single Responsibility Principle states that a class should have only one reason to change, meaning it should have only one job or responsibility. This principle promotes the idea that a class should encapsulate only one part of the functionality provided by the software.
- Improved maintainability: Changes in requirements can be implemented in a single class without affecting others.
- Enhanced readability: Smaller classes with focused responsibilities are easier to understand.
- Easier testing: Classes with single responsibilities are easier to test in isolation.
class User {
void save() {
// Save user to database
}
void sendEmail() {
// Send email to user
}
}
class User {
void save() {
// Save user to database
}
}
class EmailService {
void sendEmail() {
// Send email to user
}
}
The Open/Closed Principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This principle encourages developers to write code that can be extended with new features without changing existing code.
- Reduced risk of bugs: Existing code remains unchanged, minimizing the chance of introducing new bugs.
- Easier to add new features: New functionality can be added through inheritance or composition.
class Shape {
String type;
double calculateArea() {
throw "Unsupported operation";
}
}
abstract class Shape {
double calculateArea();
}
class Circle extends Shape {
double radius;
@override
double calculateArea() {
return 3.14 * radius * radius;
}
}
class Rectangle extends Shape {
double width;
double height;
@override
double calculateArea() {
return width * height;
}
}
The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. This principle ensures that a subclass can stand in for its superclass without issues.
- Improved code reusability: Subtypes can be used interchangeably with their base types.
- Enhanced maintainability: Following LSP leads to a more predictable codebase.
class Rectangle {
double width;
double height;
}
class Square extends Rectangle {
@override
set width(double value) {
super.width = value;
super.height = value;
}
@override
set height(double value) {
super.width = value;
super.height = value;
}
}
abstract class Shape {
double area();
}
class Rectangle implements Shape {
double width;
double height;
@override
double area() {
return width * height;
}
}
class Square implements Shape {
double side;
@override
double area() {
return side * side;
}
}
The Interface Segregation Principle states that no client should be forced to depend on methods it does not use. This principle encourages developers to create smaller, more specific interfaces rather than large, general-purpose ones.
- Reduced dependencies: Clients only depend on what they use, leading to fewer changes.
- Enhanced flexibility: Specific interfaces can be changed without affecting other parts of the code.
abstract class Worker {
void work();
void eat();
}
class Engineer implements Worker {
@override
void work() {
// Engineer specific work
}
@override
void eat() {
// Engineer specific eating habits
}
}
abstract class Worker {
void work();
}
abstract class Eater {
void eat();
}
class Engineer implements Worker, Eater {
@override
void work() {
// Engineer specific work
}
@override
void eat() {
// Engineer specific eating habits
}
}
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules, but both should depend on abstractions. This principle promotes the use of interfaces or abstract classes to decouple dependencies.
- Increased flexibility: High-level modules can be changed independently of low-level modules.
- Improved testability: Dependencies can be mocked or stubbed in unit tests.
class UserService {
final Database database;
UserService() {
this.database = Database();
}
void saveUser(User user) {
database.save(user);
}
}
class Database {
void save(User user) {
// Save user to database
}
}
abstract class UserRepository {
void save(User user);
}
class DatabaseRepository implements UserRepository {
@override
void save(User user) {
// Save user to database
}
}
class UserService {
final UserRepository userRepository;
UserService(this.userRepository);
void saveUser(User user) {
userRepository.save(user);
}
}
By adhering to SOLID principles, Dart developers can create more flexible, maintainable, and scalable applications. Each principle contributes to code that is easier to understand, extend, and refactor. Implementing these principles not only enhances the quality of the software but also improves collaboration and reduces the cost of changes in the long run.
For any questions or feedback, please reach out via email: [email protected]