SOLID: Single Responsibility Principle in Dart

Görkem
3 min readAug 1, 2023

When it comes to creating maintainable, clean and sustainable software, some principles and standards such as SOLID are the game changers. In this article we will briefly walk through the Single Responsibility Principle.

Some of the content and diagrams in this article have been quoted from the Uncle Bob’s speech in the link below.

I highly recommend you to watch the whole video for better understanding.

Let’s start with describing the main rule of SRP.

A class should have one, and only one, reason to change.

It means exactly that we should not be changing our class for different kind of reasons. Let’s have a look at the Uncle Bob’s example.

In the diagram above, Payroll module is dependent on the Employee class and Employee class has 3 different functions inside.

According to the description of SRP, let’s ask a question and try to understand if the Employee class violates the SRP.

Does the Employe class has more than one reason to change?

- Yes. It has three or more different reasons to change.

Calculation of payment, reporting and writing employee (database related) are totally different reasons to change the Employee class. That’s why it violates the Single Responsibility Principle and has to be separated into smaller parts.

Let’s go with a simple Dart example. In the example we’ll see a bad and good example.

For the sake of simplicity, let’s assume a simple example. In the example we will go ahead with a use case which a user has to perform authentication in the app.

Here is a BAD example which violates the Single Responsibility Principle.

class User {
const User({
this.id,
required this.name,
});

final String? id;
final String name;
}

class AuthenticationRepository {
Future<User> signUp(String email, String password) async {
//Sign up the user.
//...

return User(id: 'userId1', name: 'Signed Up User');
}

Future<User> signIn(String email, String password) async {
//Sign in the user.
//...

return User(id: 'userId2', name: 'Signed In User');
}

Future<User> signOut(User user) async {
//Sign out the user.
//...

return User(id: null, name: '');
}

Future<void> log(String? message, {bool reportCrashlytics = false}) async {
if (reportCrashlytics) {
//Report crashlytics.
}

print(message);
}
}

void main() async {
final authRepository = AuthenticationRepository();

const email = 'user@email.com';
const password = '*******';

final user = await authRepository.signIn(email, password);
await authRepository.log(user.name);
}

We can easily understand if it applies SRP. In order to understand it, just ask this question yourself.

For what reasons would I make a change in AuthenticationRepository?

As we can see in the code above, there might be two reasons to change it. We would make changes in such cases:

  • If we would like to change authentication behaviour.
  • If we would like to change logging behaviour.

Just remember that:

A class should have one, and only one, reason to change.

What if we would separate the logging functionality into another class. Let’s create a Logger class and put the log method inside it.

Here is a GOOD example which follows the Single Responsibility Principle.

class User {
const User({
this.id,
required this.name,
});

final String? id;
final String name;
}

class AuthenticationRepository {
Future<User> signUp(String email, String password) async {
//Sign up the user.
//...

return User(id: 'userId1', name: 'Signed Up User');
}

Future<User> signIn(String email, String password) async {
//Sign in the user.
//...

return User(id: 'userId2', name: 'Signed In User');
}

Future<User> signOut(User user) async {
//Sign out the user.
//...

return User(id: null, name: '');
}
}

class Logger {
Future<void> log(String? message, {bool reportCrashlytics = false}) async {
if (reportCrashlytics) {
//Report crashlytics.
}

print(message);
}
}

void main() async {
final logger = Logger();
final authRepository = AuthenticationRepository();

const email = 'user@email.com';
const password = '*******';

final user = await authRepository.signIn(email, password);
await logger.log(user.name);
}

In the code above AuthenticationRepository and Logger classes have their only one reason to be changed.

As a conclusion let’s summarize the SRP with Uncle Bob’s words.

- Don’t put functions that change for different reasons in the same class.

- Don’t mix concerns in classes.

--

--