SOLID Design Principles in Dart

SOLID Design Principles in Dart

Β·

4 min read

Generally, we need to have a solid understanding of certain principles in the software engineering world before we dive into becoming a flutter developer.

These principles have played a very important role in the development process of so many successful software we see in the tech industry. I'll take you through what you need to understand about these principles and their use cases.

SOLID Design principles. SOLID is an acronym that represents five key principles in software engineering, these principles are.

  • Single responsibility principle
  • Open-closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

This article explains all you need to know about these principles, and I will also take you through the code implementation of how we can take advantage of these principles.

Keep in mind that following these principles will give you a highly organized, easily maintainable, and well-structured code-base thus making it easy to integrate new features, and collaborate with other developers.

SINGLE RESPONSIBILITY PRINCIPLE

Robert Martin stated that a class should have only one reason to change. This means that a class should be tied to a single functionality, a class should be responsible for only a part of our software functionality, a class should solve only one problem. this makes it easy to update a codebase even as it gets complex over time.

Let's have a look at the block of code below, which is an implementation of the single responsibility principle in dart

class AuthService{
   login(String email, String password) async {
        /// code logic
   }
}

This principle can also be applied in the use of abstract classes also known as interfaces.

The methods in the block of code below also adhere to the single responsibility principle, as we can see each method is responsible for a particular functionality

The class UserService is responsible for a part of our software functionality.

abstract class UserService{
    Future<bool> userExist(String? email);
    Future<UserModel> userByEmail(String? email);
}

OPEN-CLOSED PRINCIPLE

This principle implies that we should rather extend an existing,well-tested class than modify/make changes to it as this might lead to errors or bugs in our code-base.

This could be achieved with the help of abstract classes in dart.

This might increase the complexity of the codebase but it is the right thing to do when you need to add a new feature or modification to a class.

The class below implements the UserService abstract class in the block of code above.

class Authentication implements UserService {
    @override
    Future<UserModel> userByEmail(String? email){
       ///  modification or any change can be applied at this point 
}
}

LISKOV SUBSTITUTION PRINCIPLE

This principle has turned out to be the most difficult to grasp, this article will take you through the code implementation while it breaks down to a more understandable form.

It states that

If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program Wikipedia

Let's assume we have a class Dog which implements an abstract class Animal and Animal has the below methods

abstract class Animal {
    void makeSound();
    void move();
    void fly();
}
class Dog implements Animal{
   @override
   void makeSound(){
         ///make sound logic
   }

  @override
   void move(){
     /// move logic
    }

  //the last method doesn't make sense as we know that a dog cant fly.
  //this a bad practice
  @override
   void fly(){

   }

}

Applying Liskov Substitution Principle should look like this

abstract class Fly{
      void fly();
}
abstract class Animal {
    void makeSound();
    void move();
}

At this point, the Dog class can implement the Animal class while a Bird class can implement both Animal and Fly class

class Bird implements Animal, Fly{
    @override
   void makeSound(){
         ///make sound logic
   }

  @override
   void move(){
     /// move logic
    }

  @override
   void fly(){

   }
}

Prefer composition (with interfaces) over inheritance

INTERFACE SEGREGATION PRINCIPLE

This principle implies that it is better to have a lot of interfaces with few contracts than to have a fewer interface with plenty of contracts.

If this principle is applied, an interface becomes client-specific, this means clients won't have to depend on an interface they do not use.

The block of code below is a violation of the interface segregation principle. Instead of having an interface with plenty of contracts, it is better to make fine-grained interfaces that are client-specific.

abstract class UserService{
     Future<User> login();
     Future<User>  register();
     Future<void> logout();
     Future<List<Post>> getUserPosts(String userId);
     Future<void> deletePost();
     Future<Post> getPostById(String id);


}

Applying the principle we should have smaller interfaces.

abstract class AuthService{
     Future<User> login();
     Future<User>  register();
     Future<void> logout();
}


abstract class PostService{
    Future<List<Post>> getUserPosts();
     Future<void> deletePost();
     Future<Post> getPostById(String id);
}


class UserService implements AuthService,PostService{

}

DEPENDENCY INVERSION PRINCIPLE

This principle states that we should depend more on abstraction rather than concrete implementation. This, in turn, makes our codebase loosely coupled and easy to extend.

We will be using the #Injectable package to implement dependency injection in flutter.

Dependency Injection in flutter

The above article explains in detail a proper way of implementing dependency injection in flutter

0_W5xVnN-RGKIzPAA6.jpeg

Photo by Ian Schneider on Unsplash

That’s it, folks I hope you guys learn something new.

Β