DEV Community 👩‍💻👨‍💻

José Miguel Álvarez Vañó
José Miguel Álvarez Vañó

Posted on • Updated on • Originally published at jmalvarez.dev

Single Responsibility Principle in TypeScript

A class should have just one reason to change.

Every class in our code should be responsible for just a single part of the application. By following this principle we reduce the complexity of our code.

If a class is responsible of multiple parts of our app, it will have to be changed frequently. Therefore, changing one part of the class increases the risk of breaking other parts of itself. The solution is to divide it into multiple classes, each one with one responsibility.


In the following bad example we can see how the Student class has two responsibilities: managing the student data and the course data.

class Student {
  id: number;
  name: string;
  courseId: number;
  courseName: string;
  courseSubjects: string[];

  // constructor

  getCourseSubjects(): string {
    return this.courseSubjects.join(", ");
  }
}
Enter fullscreen mode Exit fullscreen mode

Following the Single Responsibility Principle we can improve this by moving the course data to its own class.

class Student {
  id: number;
  name: string;
  course: Course;

  // constructor
}

class Course {
  id: number;
  name: string;
  subjects: string[];

  // constructor

  getCourseSubjects(): string {
    return this.subjects.join(", ");
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (7)

Collapse
 
lukeshiru profile image
Luke Shiru

You can always just handle it from the type side, instead of having runtime classes:

type Entity = {
    readonly id: number;
    readonly name: string;
};

type Course = Entity & {
    readonly subjects: ReadonlyArray<string>;
};

type Student = Entity & {
    course: Course;
};
Enter fullscreen mode Exit fullscreen mode

And then for the methods, you can just have functions using those types:

const fetchEntity =
    <FetchedEntity extends Entity>(endpoint: string) =>
    (id: Entity["id"]) =>
        fetch(`${API}/${endpoint}/${id}`).then(
            response => response.json() as Promise<FetchedEntity>,
        );

const fetchCourse = fetchEntity<Course>("courses");
const fetchStudent = fetchEntity<Student>("students");

fetchStudent(42)
    .then(({ course }) => fetchCourse(course.id))
    .then(console.log)
    .catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Is actually quite liberating to be able to have this level of flexibility and reusability without the constraint of classes. We end up not needing principles that are there mainly to patch something that shouldn't be broken in the first place (like inheritance is). I recommend taking a look at this 2016 video about how OOP might not be the way to go, tho my position is more against classes than against OOP altogether.

Cheers!

Collapse
 
jmalvarez profile image
José Miguel Álvarez Vañó Author

Thank you for your comment Luke. I will go through the content that you shared in the comment. Your point of view sounds very interesting. I rarely use classes in my daily work but it's the first time I see someone against them.

Yes, it's totally posible not to use classes. In this series of posts that I'm going to be doing my goal is to explain SOLID principles with examples in TypeScript for people who could be interested.

Collapse
 
allobrox profile image
Tamas Rigoczki

You are also breaking SRP in your final example by placing application logic into your model.

The model's purpose is to lay template to your objects. Not to retrive and manipulate them.

You should create CourseService for that purpose.

Collapse
 
jmalvarez profile image
José Miguel Álvarez Vañó Author

Thanks for the comment Tamas. You're totally right, in the example we are just applying SRP to the Student class. We could continue applying SRP to the Course class.

It's important to have in mind that principles should give you guidance. They should not be applied to the extreme without evaluating first if it makes sense.

Collapse
 
allobrox profile image
Tamas Rigoczki

I wouldn't call my objection extreme. Imagine you get the plan of your house and it contains where you can buy bricks, tiles, etc...

You did great job applying SRP to Student class, but moved a violation to Course class.
I am sure a newbie will read your post and in its current state he/she might add API calls to the model in her/his portfolio repo. It might result in a potential rejection in case of a job application.

I think it is important to create technically correct training materials, especially in case of SOLID principles.

Thread Thread
 
jmalvarez profile image
José Miguel Álvarez Vañó Author • Edited on

I didn't mean that your objection is extreme, I'll quote a paragraph from the book "Dive into Design Patterns" by Alexander Sheets which better explains what I meant:

As with everything in life, using these principles mindlessly can cause more harm than good. The cost of applying these principles into a program’s architecture might be making it more complicated than it should be. I doubt that there’s a successful software product in which all of these principles are applied at the same time. Striving for these principles is good, but always try to be pragmatic and don’t take everything writ- ten here as dogma.

You are right that juniors could misunderstand this principle with my example. Thank you for your suggestion, I will update the post to avoid confusion.

Thread Thread
 
devworkssimone profile image
DevWorksSimone

I would instead keep it and add a brief explanation of why even that code break SRP. As a newbie it was helpfull to read through comment but not everyone may do it.

🌚 Life is too short to browse without dark mode