SOLID Principles in Dart

Madhan
4 min readJun 26, 2024

--

This article will examine how to apply the SOLID principles in Dart with code examples.

The SOLID principles are principles for software development aimed at making software more maintainable, scalable, and flexible.

S — Single Responsibility Principle (SRP)

The Single Responsibility Principle (SRP) states that a class should have only one reason to change. In other words, a class should have only one responsibility. A class that has multiple responsibilities is hard to maintain and modify.

Here is an example of a class that violates the SRP:

class User {
int id;
String name;

void save() {
// save user to database
}

void sendEmail() {
// send email to user
}
}

This class violates the SRP because it has two responsibilities: saving the user to the database and sending an email to the user. A better approach would be to split the class into two separate classes:

class User {
int id;
String name;
}

class UserRepository {
void save(User user) {
// save user to database
}
}

class EmailService {
void sendEmail(User user) {
// send email to user
}
}

O — Open/Closed Principle (OCP)

The Open/Closed Principle (OCP) states that a class should be open for extension but closed for modification. In other words, you should be able to extend a class’s behavior without modifying its source code.

Here is an example of a class that violates the OCP:

class Rectangle {
double width;
double height;

double area() {
return width * height;
}
}

If we want to create a new class for calculating the area of a circle, we would have to modify the Rectangle class which is currently violating the OCP. A better approach would be to create an interface for calculating the area and have each shape implement the interface:

abstract class Shape {
double area();
}

class Rectangle implements Shape {
double width;
double height;

double area() {
return width * height;
}
}

class Circle implements Shape {
double radius;

double area() {
return pi * pow(radius, 2);
}
}

L — Liskov Substitution Principle (LSP)

The Liskov Substitution Principle (LSP) states that objects of a superclass should be able to be replaced with objects of a subclass without affecting the correctness of the program. In other words, a subclass should be able to replace its superclass without breaking the code.

Here is an example of a class that violates the LSP:

class Rectangle {
double width;
double height;

double area() {
return width * height;
}
}

class Square extends Rectangle {
double side;

@override
double set width(double value) => side = value;

@override
double set height(double value) => side = value;
}

This class violates the LSP because a Square cannot be used in place of a Rectangle without affecting the correctness of the program. A better approach would be to create a separate class for Square:

abstract class Shape {
double area();
}

class Rectangle implements Shape {
double width;
double height;

double area() {
return width * height;
}
}

class Square implements Shape {
double side;

double area() {
return pow(side, 2);
}
}

I — Interface Segregation Principle (ISP)

The Interface Segregation Principle (ISP) states that a class should not be forced to implement interfaces it does not use. In other words, a class should only depend on the interfaces it needs.

Here is an example of a class that violates the ISP:

abstract class Shape {
double area();
double perimeter();
}

class Rectangle implements Shape {
double width;
double height;

double area() {
return width * height;
}

double perimeter() {
return 2 * (width + height);
}
}

class Circle implements Shape {
double radius;

double area() {
return pi * pow(radius, 2);
}

double perimeter() {
return 2 * pi * radius;
}
}

This class violates the ISP because a Circle does not have a perimeter. A better approach would be to create separate interfaces for area and perimeter:

abstract class Area {
double area();
}

abstract class Perimeter {
double perimeter();
}

class Rectangle implements Area, Perimeter {
double width;
double height;

double area() {
return width * height;
}

double perimeter() {
return 2 * (width + height);
}
}

class Circle implements Areaand {
double radius;

double area() {
return pi * pow(radius, 2);
}
}

D — Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules. Both should depend on abstractions. In other words, you should depend on abstractions, not on concrete implementations.

Here is an example of a class that violates the DIP:

How to Apply the DIP?

Let’s illustrate the DIP using a simple example of a MessagingService class:

// MessagingService Interface
abstract class MessagingService {
void sendMessage(String message);
}

// Email Service
class EmailService implements MessagingService {
@override
void sendMessage(String message) {
print("Sending email: $message");
}
}

// SMS Service
class SmsService implements MessagingService {
@override
void sendMessage(String message) {
print("Sending SMS: $message");
}
}

In this example, we have a messaging service interface with two implementations: EmailService and SMSService.

Violating DIP:

Let’s see what happens if we violate the DIP by directly instantiating low-level modules in the high-level module:

class User {
final EmailService _emailService = EmailService();

void sendMessage(String message) {
_emailService.sendMessage(message);
}
}

Here, the User class tightly couples itself to the EmailService, violating the DIP. If we later want to use the SMS service instead, we’d need to modify the User class, leading to rigidity and reduced maintainability.

Adhering to the DIP:

To adhere to the DIP, we should rely on abstractions (interfaces or abstract classes) in the high-level module:

class User {
User(this._messagingService);

final MessagingService _messagingService;

void sendMessage(String message) {
_messagingService.sendMessage(message);
}
}

By injecting the MessagingService dependency, the User class becomes independent of concrete implementations. We can easily switch between EmailService and SMSService without altering the User class.

--

--

Madhan

Learning from errors and sharing it with budding developers in simple way. Flutter 🌐| Dart 🎯| Kotlin 📱| HTML 🗒️ | CSS🖼️ | Javascript 📜 | Python | SQL