Single responsibility principle applied to multifunctional device

Single responsibility principle

First of all, let’s ask the logical question “What is a responsibility ?” – In terms of SRP ‘responsibility’ is usually defined as ‘a reason to change’. So we can paraphrase this principle to a ‘Single reason to change’. Indeed this is all it is about. We should design our software in a way that a single entity should be stable to requirement changes and should be changeable only because of a single reason. Every module, package, class, function or any other entity of execution should be responsible for only one part of the functionality. Also, the entity should entirely encapsulate this responsibility. Modules should be separated based on the way that they might change.. As a rule, we should:

  • gather together the things that change for the same reasons(increase the cohesion)
  • separate those things that change for different reasons(decrease the coupling)

An entity should not assume more than one responsibility. Otherwise, responsibilities become coupled and it will have more than one reason to change. In such a case if the requirements for a single responsibility change then the code should change to satisfy those new requirements but this can dissatisfy the other responsibilities.
One of the benefits of the SRP is a lighter dependency graph. If you overload a single entity with more than one responsibility then each of the objects depending on some of those responsibilities also gets unnecessary dependencies on the rest of the embedded and tightly coupled responsibilities. Maintaining dependencies you do not need is not a good pattern by any means. Consider the following example:

public class Smartphone {
    public void dialUp(String number)
    public void saveContact(AddressBook record)
    public void sendNetworkData(byte[] data)
    public InputStream receiveNetworkData()
    public String getSerialNumber()
    public double getPrice()
}

Obviously, all of these methods are related to the smartphone entity. However, are they related to each other? I can imagine three points of view which define the separate responsibilities of a smartphone.

  1. User
    • public void dialUp(String number)
    • public void saveContact(AddressBook record)
  2. Telecom’s networking services
    • public void sendNetworkData(byte[] data)
    • public InputStream receiveNetworkData()
  3. Marketing and sales
    • public String getSerialNumber()
    • public double getPrice()

There are different techniques to mitigate this problem. We can split this class in two using abstraction. One base class which is responsible for ‘phone-activities’ and a smartphone class inheriting it.

public class Phone {
    public void dialUp(String number)
    public void saveContact(AddressBook record)
}

public class Smartphone extends Phone {
    public void sendNetworkData(byte[] data)
    public InputStream receiveNetworkData()
    public String getSerialNumber()
    public double getPrice()
}

Now we still have a single entity which embeds all the functionality – the smartphone object, but all the User related software may depend on Phone and not on its specific implementation – Smartphone. This is a step forward. Another approach is to define separate interfaces for the distinct concerns. For example:

public interface NetworkDevice {
    public void sendNetworkData(byte[] data)
    public InputStream receiveNetworkData(byte[] data)
}

public class Smartphone extends Phone implements NetworkDevice {
    @Override
    public void sendNetworkData(byte[] data) {...}
    @Override
    public InputStream receiveNetworkData()

    public String getSerialNumber()
    public double getPrice()
}

Now the telecom’s network services team and the specific software module they use can rely only on the NetworkDevice interface. It is a good idea to provide a separate interface to fulfil the needs defined by each team. At the end of the day, this could have been a Modem or TV set-top-box. The network services software doesn’t care. Another good way of refactoring is to extract the marketing related info into an entirely separate class.

public class MarketingInfo {
    public String getSerialNumber()
    public double getPrice()
}

public class Smartphone extends Phone implements NetworkDevice {
    @Override
    public void sendNetworkData(byte[] data) {...}
    @Override
    public InputStream receiveNetworkData()
}

public class Product {
    private MarketingInfo marketingInfo;
}

If there is a use case (this responsibility is required) we can combine the MarketingInfo and the Smartphone using composition.

public interface SellableItem {}

public class Smartphone extends Phone implements NetworkDevice, SellableItem {
    @Override
    public void sendNetworkData(byte[] data) {...}
    @Override
    public InputStream receiveNetworkData()
}

public class Product {
    private MarketingInfo marketingIno;
    private SellableItem item; // initialized with a Smartphone object
}

But think of each responsibility as an axis of change. An axis of change only matters if the changes actually occur. Otherwise, any unnecessary application of the ‘Single responsibility principle’ may come for additional complexity which does not bring any value. Do we need to separate dialUp and saveContact methods? They look like different responsibilities. Do we expect a situation when these two won’t be required by the same phone entity? I do not think so. Phones have been able to dialUp and saveContact for decades. So, do we need the complexity of additional types and relations? This is one of the decisions we have to take when applying the ‘Single responsibility principle’. We should try to think about how the requirements may change in the future and design our software based on these assumptions.

It is worth mentioning that when we defined the responsibilities above we based them on the requirements that different groups of people may provide: users, network services team and sales team. In 2014 Bob Martin reexamined SRP and concluded that: “This principle is about people.” He asks: “What defines a reason to change?” Usually, the reason for the change is people. People are those who request changes. Different groups of people interpret the same problem in a different way. Each group have their justification based on their expertise and goals. Each request stands for an individual aspect of the problem which holds its own responsibility. We should provide separate mechanisms for each group to solve their problems and those mechanisms should be independent of each other. They should not be coupled inside a single entity of execution.

Conclusion

After all the application of the ‘single responsibility principle’ depends on the knowledge the programmer has on the domain. Clear requirements and the ability to foresee possible feature requests are crucial to the software design. Building complex software in an enterprise environment makes it difficult to apply SRP on every single entity. Single responsibility per class or function is hard to achieve. But in practice, we can strive for ‘fewer responsibilities’ per entity. This will make our design cleaner and our software more robust, error-prone and easier to test.

Leave a Reply

Your email address will not be published. Required fields are marked *