Skip to content

Index

Beginner Basics: Establishing a SOLID Foundation – The Single Responsibility Principle

Welcome to the first installment of Establishing a SOLID Foundation series. In this post, we’ll be exploring the first part of SOLID, the Single Responsibility Principle and how following this principle can lead to great design choices.

So what is the Single Responsibility Principle?

Before diving into code, let’s take a look at a real life example. Let’s say that we open a new restaurant. Clearly, we need to hire a fantastic head chef to prepare the food. Between these two candidates, which one seems to be the better fit?

  • Chef A – spends their time on creating new dishes and preparing the best food possible
  • Chef B – spends their time on taking orders, preparing food, and busing tables

Well that’s easy, you say, we should hire Chef A because he’s focusing on cooking. We can hire other people to take orders and clean tables. It’s pretty obvious that Chef A is the better choice because of the focusing on a single job. So if this is what happens in real life, why is that we see code that is doing way too many things?

For example here’s an example of what the Chef B(efore) class might look like

class Chef
  def initialize
    @position = 0
    @order = nil
    @orderReady = false
  end

  def CookFood(order, tableNumber)
    if order == "chicken with broccoli"
      CookChickenWithBroccoli()
      DeliverFood(order, tableNumber)
    end
  end

  def CookChickenWithBroccoli
    @orderReady = true
  end

  def DeliverFood(order, tableNumber)
    GoToTable(tableNumber)
    GiveFood(order)
  end

  def GoToTable(tableNumber)
    @position = tableNumber
  end

  def GiveFood(order)
    puts "Food delivered"
  end

  def BusTables(tableNumber)
    GoToTable(tableNumber);
    CleanTable(tableNumber)
  end

  def CleanTable(tableNumber)
    puts "Table # " + tableNumber.to_s + " cleaned"
  end

  def TakeOrders(tableNumber)
    GoToTable(tableNumber)
    order = AskForOrder()
    return order
  end

  def AskForOrder()
    return "chicken with broccoli"
  end
end

with an example implementation usage:

1
2
3
4
5
tableNumber = 3
chef = Chef.new()
order = chef.TakeOrders(tableNumber)
chef.CookFood(order, tableNumber)
chef.BusTables(tableNumber)
As you can tell in the Chef example, there are a lot of methods that need to be defined in order to get the different pieces of main functionality working. If any of these main pieces needed to be changed, we would have to modify the Chef class.

Because of the dependencies, another way to describe the SRP is that the class should only have one reason to change. In this case, the Chef class has three reasons for changing. (Fun fact: when dealing with classes that have a lot of reasons to change, it can be a sign that the class is following the God-Object anti-pattern.)

I don’t know, what’s in it for me?

Now that you have a good understanding of the SRP, you might be asking what are some of the benefits of cleaning up your design.

First, classes that only do one job have less dependencies to worry about. Looking back at our code example, it’s clear that the Chef class would have to change if we needed to change the business rules for taking orders, cleaning tables, or for cooking food.

Next, by following the SRP, it’s easier for a team to solve issues. For example. let’s say that you had to fix an error in cleaning the tables and someone else on your team was assigned to update the taking an order scenario. Using the Chef B class definition, the two of you would have to make different changes to the same class.

Finally, by following the SRP, we’re more closely following the idea behind object-oriented design. By definition, we should take complex problems and break them down into their individual actors. Since we define that each class can only have one responsibility, we are ensuring that the problem is being broken down to its smallest pieces.

Ok, ok, you’ve convinced me, how do I take a busy class and make it simple?

Fortunately, if you have a class that has is doing too many things, there’s a really simple fix. Just create more objects that contain the different pieces.

Using our Chef example, I’m going to separate the responsibilities into two new classes. First, I’m going to move all the methods involved in taking orders to a new Waiter class.

class Waiter
  def initialize
    @position = 0
  end

  def DeliverFood(order, tableNumber)
    GoToTable(tableNumber)
    GiveFood(order)
  end

  def GoToTable(tableNumber)
    @position = tableNumber
  end

  def GiveFood(order)
    puts "Food delivered"
  end

  def TakeOrders(tableNumber)
    GoToTable(tableNumber)
    order = AskForOrder()
    return order
  end

  def AskForOrder()
    return "chicken with broccoli"
  end
end

Next, I’m going to extract every method needed to bus tables into our new Busboy class:

class Busboy
  def initialize
    @position = 0
  end

  def BusTables(tableNumber)
    GoToTable(tableNumber);
    CleanTable(tableNumber)
  end

  def GoToTable(tableNumber)
    @position = tableNumber
  end

  def CleanTable(tableNumber)
    puts "Table # " + tableNumber.to_s + " cleaned"
  end
end

Now that we’ve broken up the responsibilities, the next step is to look at some common functionality that classes might share. For example, it looks like the Waiter and Busboy class both use @position and the GoToTable method, so why don’t we create a new class called BaseService

1
2
3
4
5
6
7
8
class BaseService
  def initialize
    @position = 0
  end
  def GoToTable(tableNumber)
    @position = tableNumber
  end
end

and allow both the Waiter and Busboy classes to inherit?

class Waiter < BaseService
  def initialize
    super
  end

  def DeliverFood(order, tableNumber)
    GoToTable(tableNumber)
    GiveFood(order)
  end

  def GiveFood(order)
    puts "Food delivered"
  end

  def TakeOrders(tableNumber)
    GoToTable(tableNumber)
    order = AskForOrder()
    return order
  end

  def AskForOrder()
    return "chicken with broccoli"
  end
end

class Busboy < BaseService
  def initialize
    super
  end

  def BusTables(tableNumber)
    GoToTable(tableNumber);
    CleanTable(tableNumber)
  end

  def CleanTable(tableNumber)
    puts "Table # " + tableNumber.to_s + " cleaned"
  end
end

Great, we’ve finished refactoring the overly-obsessive Chef from having control on everything in the restaurant to just keep his attention on cooking great food.

class Chef
  def initialize
    @orderReady = false
  end

  def CookFood(order, tableNumber)
    if order == "chicken with broccoli"
      CookChickenWithBroccoli()
    end
  end

  def CookChickenWithBroccoli
    @orderReady = true
  end
end

Wow, after separating the concerns, our chef class has been massively condensed down to focus on just cooking food, but how do all of these individual classes interact with one another? Does it look like the classes are now focusing on performing one job well?

1
2
3
4
5
6
7
8
tableNumber = 3
chef = Chef.new()
waiter = Waiter.new()
busboy = Busboy.new()
order = waiter.TakeOrders(tableNumber)
chef.CookFood(order, tableNumber)
waiter.DeliverFood(order, tableNumber)
busboy.BusTables(tableNumber)

Something to keep in mind when separating responsibilities is that a single responsibility does not equal a single method. If the methods are all related to performing the same task, then it’s not a violation of the SRP.

TL;DR

In short, the Single Responsibility Principle (SRP) reinforces the idea that every class should have one job and should do that job well. By following this principle, you’re much more likely to create more readable and maintainable code. When you run across classes that are doing too much, the best solution is to extract the extra functionality into another class. After extraction, don’t forget to refactor and reorganize as needed.

Establishing a SOLID Foundation Series

Establish solid intro

If you’ve been working at any dev shop worth its salt, it’s a safe bet that you’ve heard someone mention writing SOLID code or that something isn’t SOLID. Well, what exactly do they mean by SOLID?

As part of this Beginner Basics series, we’re going to first look at what does SOLID mean and why is it so important. For the next five weeks, we’ll explore a different aspect of SOLID by in terms of which principle does each letter represent, some code samples that follow the principle and code samples that break the principle. . As this series progresses, I’ll be adding the code samples to my public Bitbucket account for you to clone (You do know how to use Mercurial and Bitbucket, right?)

What is SOLID?

In a nutshell, SOLID is a mnemonic created by Michael Feathers to help developers remember the five principles of great code construction introduced by Robert C. Martin (“Uncle Bob”). By following these principles, it’s much more likely that the code designed will be easier to maintain and to extend. As such, SOLID code follow these principles:

(S)ingle Responsibly Principle – Every class should do just one thing and do it well (O)pen/Closed Principle – Code should be open for extension, but closed for modifications (L)iskov Substitution Principle – Make sure that two classes that are interchangeable have the same behavior (I)nterface Segregation Principle – Better to use multiple specific interfaces then to use a single general interface (D)ependency Inversion Principle – Code should depend on abstractions, not on concrete classes

What’s so important about being SOLID?

When building software, its paramount that you have a solid foundation. A common analogy for building software is that it’s like building a house. If the foundation is weak, then code starts becoming stiff, changes become much harder to make and the next thing you know the shower is draining into the kitchen. If your code isn’t SOLID, then it’s going to be harder to add new features/modifications, more difficult to support, and easier to introduce features bugs. As a developer, you should strive to write code that is bug free, easily maintainable, and simple to expand. By following SOLID principles, we can carry out these goals and our code can be SOLID.

Next Steps

Stay tuned for the next part of the series when we begin exploring the first part of SOLID, the Single Responsibility Principle. Until then, stay sharp, keep learning, and code on.

Establishing a SOLID Foundation Series

The Basics: Source Control

As the first part of the Basics of Software Development series, we’re going to talk about source control is, why you should be using it and commonly used source control management (SCM) tools.

Source Control, what’s that?

To put it simply, SCM is a set of tools that allow users to keep track of source code by using a repository. The most common workflow is to get a copy of the source code from SCM, make changes to some of the files (refactoring for example) and committing those changes back.

Benefits of Source Control

  • Allows for users to undo uncommitted changes
  • Promotes developers to refactor code
  • Keeps track of what changes have been made over time
  • Great for seeing what changes have been made over time (for example between versions)
  • By definition, it’s a backup of the source code
  • Perfect if your workstation crashes and you don’t have a backup
  • Makes it easier for other developers to get the latest changes
  • Instead of one developer making changes and “pushing” them to the team, source control allows the team to “pull” changes when ready.

Without source control, anytime that code changes were made, the developer who made changes would have to push the code to the other developers. This may not sound terrible, but what if you were in the middle of rewriting a file? You would have to figure out what files have been changed, hope that the files that were changed aren’t files that you’re currently working on. After ensuring all of that, you need to merge your changes to the latest version and hope that nothing broke. As you can see, this is a horrible workflow, prone for errors. However, using a SCM solves these issues.

Choosing Source Control

All SCM tools work either as centralized or distributed. Before you can choose a source control, you need to figure what type of source control type works best for your situation (tech requirements, ease of setup, company policies, etc..)

In a centralized source control implementation, there is a main server (central repository) that holds the source code and history of changes. When a developer checks out source code, they are only getting the source code (no revision history). After the developer makes changes and commits the changes, the files that have been modified are sent back to the central repository. Since the repository has the source code and history of changes, the repository is known to have the “blessed” or the “single source of truth” copy of the source code.

Since the centralized implementation utilizes the central server, it’s very easy to see what’s the latest and greatest. However, due to the centralization, if the server ever goes down, the team cannot commit changes.

In a distributed source control implementation, the big difference is that there is not a central repository. This means that every developer has both the latest source code and the revision history.

The main advantage to this implementation is that a developer can commit changes and continue working if there is no network connection. However, due to the lack of a central repository, there is no such thing as “blessed” version.

Common Source Control Implementations

Team Foundation Server (TFS) – This is a centralized source control that is most likely working with .NET code and Visual Studio. Even though It is possible to use TFS for other languages and IDE’s, you have to use Team Explorer Everywhere and is currently supported with Eclipse.

Subversion (SVN) – This is a centralized source control platform that is designed to be used for any programming language and IDE.

Git – This is one of the most widely used distributed source control platform. This platform is most commonly used with GitHub.

Mercurial – Another common distributed source control platform. This is most commonly used with Bitbucket.

Visual SourceSafe – Very old solution from Microsoft, if your company is using this for source control, you need to migrate to another solution. This was the precursor for TFS and should no longer be used for source control.