Single Responsibility for Beginners (2/2)
Tags: beginners, programming, ruby
Published: 2023-01-23 16:24:52

In [our last blog](https://dev.to/angus_bowerbrown_96449f1/single-responsibility-principle-for-beginners-12-1e1f), we explored some beginner 'hows' of the Single Responsibility Principle (how to build classes that follow it, how to check that they are following it at any time). This blog is focused on *why* we follow the SRP and what it brings to the table for us as developers.

Blindly following principles of design can be helpful when we're starting out, but they stand to benefit us (or at least me!) most when we understand exactly what kind of headaches they're trying to avoid!

## What's the point of SRP?

Sandi Metz said that designing classes with a Single Responsibility encourages us to _"code for the future"_ 🔮 ✨ 🤔 We'll exploring what she might mean by that throughout this blog but, in general, classes with a single responsibility are:

- More reusable
- More responsive to change

That's pretty much it! That may not seem like much but, unless we can visualise every aspect of our program from very start of our process, these qualities reveal themselves to be incredibly useful over time 😍

Let's show this in action with some examples from last time.


## Example: Back to the Bookshop

[To refresh](https://dev.to/angus_bowerbrown_96449f1/single-responsibility-principle-for-beginners-12-1e1f), as a Bookshop owner, we wanted a program that could keep track of all the books on a particular shelf.

So, we needed a program that could:

- Represent individual books as data objects
- Represent a bookshelf in some way that can store and remove those objects

We crafted both a program that achieved those goals with separate classes:

```
class Book
  attr_accessor :title, :author

  def initialize(title, author)
    @title = title
    @author = author
  end
end

class Bookshelf
  attr_accessor :books

  def initialize
    @books = []
  end

  def add_book(book)
    books << book
  end

  def remove_book(book)
    books.delete(book)
  end
end

```

...and a counter-example of a big `Book` class that handled everything 🤹‍♀️:

```
class Book
  attr_accessor :title, :author

  @@bookshelf = []

  def self.remove_book(book)
    @@bookshelf.delete(book)
  end

  def initialize(title, author)
    @title = title
    @author = author
    @@bookshelf << self
  end
end

```

Both of these examples demonstrate the behaviour we need, but what happens when we need to add a new feature? 

## A New Feature: A Catalogue

_As the bookshop owner, we've now decided to digitise our entire stock of books. As well as keeping track of our bookshelf, we want to represent every book in store as data in some way and have it accessible._

This new idea of a "catalogue" could be useful for a whole bunch of reasons (searching and sorting our stock, keeping track of what new books need to be ordered etc) and our program will now need to:

- Represent individual books as data objects
- Represent a bookshelf in some way that can store and remove those objects
- Represent a catalogue of all books in a way that can also store and remove those objects


Both of our earlier examples take care of two of these concerns, so we'll just have to adapt them to address the third one.

Let's start by looking at the second extract- the `Book` class that handles everything 🤹‍♀️- and see how it accommodates this new idea.

### A Class Divided

Our new `Catalogue` class will need to store data objects that represent books. At the moment, our `Book` class is responsible for generating those objects.

We'll need to make them work together!

If we look at our `Book` class's `#initialize` method though, there's a snag:

```
def initialize(title, author)
    @title = title
    @author = author
    @@bookshelf << self
  end
```

At the moment, whenever a new book object is created, it's automatically pushed to the `@@bookshop` class variable.

Our `Book` class's responsibility of representing books as data objects is currently *entangled* with its responsibility of adding to the bookshelf 🪢

This won't do. Our coming `Catalogue` will have every book in the store; there need be some books in our `Catalogue` that aren't on our `@@bookshelf`.

With that in mind, let's "detangle" these methods:

```
  def self.add_book(book)
    @@bookshelf << book
  end

  def initialize(title, author)
    @title = title
    @author = author
  end

```

Alright! We can now create new book objects without having to add them to our `@@bookshelf`!

That being said, with our `@@bookshelf` and our coming "catalogue", we now have two places where we can "add" books.

It might be better to clarify exactly what data object our `Book` class's `.add_book` and `.remove_book` methods are acting on:

```
class Book
  attr_accessor :title, :author

  @@bookshelf = []
  
  def self.add_book_to_bookshelf(book)
    @@bookshelf << book
  end
  
  def self.remove_book_from_bookshelf(book)
    @@bookshelf.delete(book)
  end

  def initialize(title, author)
    @title = title
    @author = author
  end
end
```

With this in place our final program might look something like this 🫣:

```
class Book
  attr_accessor :title, :author

  @@bookshelf = []
  
  def self.add_book_to_bookshelf(book)
    @@bookshelf << book
  end
  
  def self.remove_book_from_bookshelf(book)
    @@bookshelf.delete(book)
  end

  def initialize(title, author)
    @title = title
    @author = author
  end
end

class Catalogue
  attr_reader :books

  def initialize
    @books = []
  end

  def add_book(book)
    @books << book
  end

  def remove_book(book)
    @books.delete(book)
  end
end
```

Ok! That's one way of doing things!

### Another Way

Let's see how we would go about achieving the same thing with our first example, where the classes had separated responsibilities:

```
class Book
  attr_accessor :title, :author

  def initialize(title, author)
    @title = title
    @author = author
  end
end

class Bookshelf
  attr_accessor :books

  def initialize
    @books = []
  end

  def add_book(book)
    books << book
  end

  def remove_book(book)
    books.delete(book)
  end
end

class Catalogue
  attr_reader :books

  def initialize
    @books = []
  end

  def add_book(book)
    @books << book
  end

  def remove_book(book)
    @books.delete(book)
  end
end
```

Notice about the above:

- We haven't had to change our `Book` class at all for it to be adapted to our new needs
- We actually haven't had to modify any of our code, (only add new stuff) to create our `Catalogue`


Programmers often call classes that follow the SRP "[cohesive](https://en.wikipedia.org/wiki/Cohesion_(computer_science)#High_cohesion)" or say that they have "high cohesion". What they mean by this is that classes that are focused on just one thing work better with other classes.

We didn't design our `Book` class with the `Catalogue` class in mind. However, because it's only responsible for one behaviour (representing books as data) and because that behaviour is useful in many situations, it's more than happy to do it in a new context

This partly what we mean by saying coding classes in line with the SRP is 'coding for the future'. The more "cohesive" our classes are, the easier they are to use in ways we didn't even expect! 

Cohesive classes can act acting as building blocks for our new ideas, instead of obstacles!

##  OOD Mindset

Look again at our two finished examples and consider:

- In which is it clearer what each part of the program does?

- If we were to add a new feature to this program (a way of making sure each object goes to the right place, maybe a sorting method to alphabetise a collection of books), in which do you feel more sure of where you might start?

In many ways, what we're doing right now- considering how refactoring our code can help our own development process- is what's ultimately important about the SRP.

The SRP isn't essential for code to work (our counter examples were perfectly functional); we don't get any prizes for having 100% SRP purity in our code 😢 We also don't *always* have to follow the SRP; we shouldn't feel pressure to completely redesign our app if we have just one dodgy method with no clear home!

What it *does* offer us is a guiding principle to help us with our moment-to-moment decision making process as developers. It helps us write code that's readable and changeable, for ourselves and our collaborators.

## Wrapping up

- SRP makes our classes more reusable and responsive to change

- Classes with a single responsibility are often referred to as being "cohesive" or having "high cohesion"

- Classes with high cohesion work better with other classes

- This makes classes that follow the SRP useful to us down the development pipeline in ways that we can't even anticipate


#### More OOD Posts?

Thanks for reading this little 2 parter! 

By now, as beginners, we hopefully have an idea of [how to enact the SRP in our programs](https://dev.to/angus_bowerbrown_96449f1/single-responsibility-principle-for-beginners-12-1e1f) and what doing so offers us as developers 🎉

These have been really informative and fun to put together; I might explore the other SOLID principles in future posts. Let me know if that's something you would like!