S.O.L.I.D Principles in Dart
Tags: Solid Principles, Dart, Flutter, Clean Code, Flutter Widget

It’s an acronym for 5 design principles for writing code which is maintainable, scalable and easier to understand. These principles are given by Bob. C Martin (uncle bob) more familiar to the developer world.

Things To Understand Firstly :

What Problem these S.O.L.I.D principles solved?

working with the legacy code. Re-reading the code multiple times to get to the part you need to change Hard to understand what the method does. jumping multiple methods of the same name spending a lot more time to fix a minor bug

Basically, we spend a lot more time reading than writing code.

1. Single Responsibility principle

As it’s clear from its name single responsible. A class should only be responsible for one thing that means a class could change for only one reason.

Let’s first see the bad practise in which maintenance is not pleasant. Because of all the things in one single place validation, getRequest, painting.

class Result

{



checkResult(int rollno)

{

bool isRollnoValidate = isRollnovalidate();

if(isRollnoValidate)

{



ResultModel resultModel = searchResult();

showResult(resultModel);

}

else{

return "Invalid rollno";

}



}



isRollnovalidate()

{



return true;

} // get request

searchResult()

{

// return result;

} //painting

showResult(ResultModel model)

{

// show result in pleasant way

}



}



class ResultModel

{



}

After applying the Single Responsibility principle

Now the developer can focus on the desired functionality.

info :: class Result is responsible for the flow.



class Result {

checkResult(int rollno) {

bool isRollnoValidate = Validate().isRollnovalidate();

if (isRollnoValidate) {

ResultModel resultModel = NetworkApi().searchResult();

Printing().showResult(resultModel);

} else {

return "Invalid rollno";

}

}

}



class Validate {

// this is responsible for validate

isRollnovalidate() {

return true;

}

}



class ResultModel {}



class Printing {

// this class is responsible for printing

showResult(ResultModel model) {

// show result in pleasant way

}

}



class NetworkApi {

// this class is responsible for fetching result

searchResult() {

return ResultModel();

}

}

2. OPEN-CLOSED PRINCIPLE

An entity should be open for extensions but closed for modification

It states that for good practise you should be able to add new features without modifying the existing code.

Let’s first see the bad practice. In this, if we need to mechanical branch result feature then we need to edit the existing code.

class Result

{

mechanicalCheckResult()

{

// some code

}



civilCheckResult()

{

// some code

}



}

After applying Open CLOSED PRINCIPLE. In this, we create a class for adding new functionality.



abstract class Result {

checkResult();

}



class ComputerScience implements Result {

@override

checkResult() {

// some code

}

}



class Civil implements Result {

@override

checkResult() {

// some code

}

}



class Mechanical implements Result {

@override

checkResult() {

// some code

}

}

3. Liskov Substitution Principle

It means how good is your design from abstraction perspective

The architecture guarantee that the subclass will maintain the logic correctness of code.Basically prefer composition (with interfaces) over inheritance

As above let’s first see bad practice

abstract class Result {

checkResult();



codingTestResult();

}



class MechanicalBranch extends Result {

@override

checkResult() {

// some code

}



/*

* Here it is logically incorrect

* */

@override

codingTestResult() {

// some code

}

}



class ComputerScienceBranch extends Result {

@override

checkResult() {

// some codet

}



@override

codingTestResult() {

// some code

}

}

After Applying the Liskov Substitution Principle

abstract class OfflineResult {

checkResult();

}



abstract class CodingResult {

codingTestResult();

}



class MechanicalBranch implements OfflineResult {

@override

checkResult() {

// some code

}

}



class ComputerScienceBranch implements OfflineResult, CodingResult {

@override

checkResult() {

// somecode

}



@override

codingTestResult() {

// somecode

}

}

4. Interface Segregation Principle

It states that no client should be forced to depend on methods it does not use.

Basically client should never depend on anything more than the method its calling.

Bad practiCe :

abstract class Result {

checkResult();



codingTestResult();

}



class MechanicalBranch implements Result {

@override

checkResult() {

// some code

}



/*

* Here we exposed client with the method which none of his * business

* */

@override

codingTestResult() {

// some code

}

}



class ComputerScienceBranch implements Result {

@override

checkResult() {

// some codet

}



@override

codingTestResult() {

// some code

}

}

After applying the Interface Segregation Principle :

abstract class OfflineResult {

checkResult();

}



abstract class CodingResult {

codingTestResult();

}



class MechanicalBranch implements OfflineResult {

@override

checkResult() {

// some code

}

}



class ComputerScienceBranch implements OfflineResult, CodingResult {

@override

checkResult() {

// somecode

}



@override

codingTestResult() {

// somecode

}

}

5. Dependency Inversion Principle

Abstractions should not depend on details(concrete implementations). They should depend on abstractions.

Basically, you should able to change the implementation(background or low level code) without altering the high-level code(actual class you interact to). Its good to extending a abstract class or interface but reverse (means with no abstract method) is a bad practise.

Depending on abstractions gives the freedom to be independent of the implementations. Let's dig into it.

abstract class Payment {

payment();

}



class PaymentViaCreditCard implements Payment

{

@override

payment() {

// some code

}

} class PaymentViaDebitCard implements Payment

{

@override

payment() {

// some code

}

} class PaymentViaBhimUPI implements Payment

{

@override

payment() {

// some code

}

}





class Checkout

{

// our checkout class knows nothing about how payment works

// its knows pay.payment() is paying method

checkOut(Payment pay)

{

pay.payment();

}

}

I highly recommend watching this video. That’s the minimum thing I can do to express my gratitude.

Conclusion :

Don’t get trapped by Solid

: S.O.L.I.D Principles are principles, not rules : Always use common sense while applying this. Your goal is to achieve to make your code maintainable, easy to extend. : Avoid over- fragmenting your code for the sake of SRP or S.O.L.I.D : Don’t try to achieve S.O.L.I.D, use S.O.L.I.D to achieve maintainability. It’s a tool, not your goal