DEV Community

Lionnel Tsuro
Lionnel Tsuro

Posted on

SOLID principles in Dart(Flutter)

The SOLID principles are a set of principles for software development aimed at making software more maintainable, scalable, and flexible. In this article, we will discuss how to apply the SOLID principles in Flutter with code examples.

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
  }
}

Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}

Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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

class UserService {
  UserRepository userRepository;

  UserService(this.userRepository);

  void saveUser(User user) {
    userRepository.save(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

This class violates the DIP because UserService depends on the concrete implementation of UserRepository. A better approach would be to depend on an abstraction:

abstract class UserRepository {
  void save(User user);
}

class FirebaseUserRepository implements UserRepository {
  void save(User user) {
    // save user to Firebase
  }
}

class UserService {
  UserRepository userRepository;

  UserService(this.userRepository);

  void saveUser(User user) {
    userRepository.save(user);
  }
}
Enter fullscreen mode Exit fullscreen mode

By depending on an abstraction, UserService is no longer tied to a specific implementation of UserRepository, making it more flexible and maintainable.

Conclusion

The SOLID principles are important for building maintainable, scalable, and flexible software. By following these principles in your Flutter code, you can create code that is easier to maintain and modify. Remember, the SOLID principles are not rules, but rather guidelines that can help you write better code.

Top comments (0)