Monday, December 16, 2013

Single Responsibility Principle

Single Responsibility Principle ( SRP ), according to Robert C. Martin, states:

A class should have one and only one reason to change

This principle highlights the criterion and factors governing the ways to define the responsibilities of a class.
This principle has to be understood, together with Open Closed Principle and Interface Segregation Principle, both of which are concerned with factors governing high cohesion, low coupling and ease of readability and maintenance.

What does a program achieve on conformation to SRP

High cohesion between the elements of a class and better segregation of concerns, which promotes readability and maintainability. 

High Cohesion is promoted because a class's responsibilities are precisely defined in a way that the class changes exactly for one reason. This, of course, does not mean that a class should have only one public method ( corresponding to one behavior ). It just means that the different public methods must be closely aligned to each other in behavior. 'Closely aligned' is a qualitative term and various behavioral and structural concerns must be analysed to figure out it's workable meaning under a given context.

SRP also promotes Segregation of concerns, factors governing segregation of concerns are similar to the the factors governing Interface Segregation Principle, because a class is not allowed to expose any undue public methods to the clients. It might be possible, under a given design structure, that the methods exposed by a class are not required in totality by a client. Some clients may be solely concerned with a subset of the methods while some other clients concerned with a totally different subset. With the classes design to have a single responsibility , such exposure of undue methods to clients tend to a minimum

Encapsulation and Responsibility

A class is data and behavior combined into one unit. In order that changes to a class do cascade down to the other interacting classes in an undesirable way, it is necessary that a class exposes only a minimum limited information about itself to other classes. The more information a class keeps to itself, lesser will be the impact of the change on other interacting classes. So class's public member variables and methods come under strict scrutiny. As a rule, public member variables are almost always avoided. Public methods, which are a reflection of the responsibilities that a class exposes to other interacting classes, should take care of some design considerations, which I shall take in the sections below.

How To Find If A Given Object Structure Violates SRP

This section analyses few design considerations for building classes with single responsibility and also explains design structures that are optimal candidates for SRP violation.

Split Responsibility ( Cluttered Responsibility  )

Lets consider a class whose responsibilities are defined by N number of public methods. It is possible to group these methods into K behavioral groups, where groups represent disjoint sets of methods.
This is illustrated in the diagram, below.

figure:1

For a class violating SRP it is possible to arrange the relationship-dependencies of the behavioral sets as:
  1. Relationship between methods of a given behavior are 'closely' related. For  'closely' related methods it is highly likely that a client using one of the methods of a given behavioral set will also require the use of other methods of the same set. The methods of a given behavioral set are complementary to each other. For example, think of methods like start(), code() , test(), stop(). They are very likely to be used together and are complementary to each other. They constitute methods of one behavior. Methods like attendMeeting(), takeNotes() are also complementary and highly likely to be used together, and hence, constitute methods of different behavioral set.  But methods attendMeeting() is not closely related to code() or stop(). A class whose responsibilities are defined by all the methods, namely start(), code(), test(), stop(), attendMeeting(), takeNotes() , may be a recipient of not obeying single responsibility. 
  2. Inter-Relationship between methods of different behavioral set are 'not closely' related, i.e it is highly unlikely that a client would use methods of different behavioral set in one class. This simply means that a class's different behavioral set has a tendency to get coupled with other interacting classes in a non-unitary and differential way. This means changes to a given class has a tendency to propagate in different directions. For example, a change of requirement 1 will propagate to module 1 , whereas a change of requirement 2 will propagate to module 2 and so on.

Classes obeying the above 1) and 2) can be said to have a 'split responsibility'. As can be seen in figure:1, class Split_Class has differential responsibilities assigned. This class change for a variety of reasons, either change in Behavior_12 or Behavior_AB or Behavior_XY, each change propagating to a different module. Add to this, these behaviors themselves depend upon other modules. It's maintainability, re-usability and extensibility are highly questionable. It is an ideal recipient to be re-designed with SRP.

Unit Responsibility 

Classes with a single responsibility which present high cohesion and are, thus, easier to use and maintain

figure:2

Figure:2 illustrates a class with single responsibility.

Code Examples

Example # 1

This Example consists of Bank Account system , illustrated, below, by figure:3

figure:3

The class BankAccount handles varying responsibilities:
  1. One responsibility consists of withdrawal and fetch account balance.
  2. Other responsibility consists of fetch Transaction History and,
  3. Yet another responsibility, unrelated with the first two, consists of registering complaints and fetching complaint status
Other modules of the system depend upon Bank Account class but use only a selected behaviors of Bank Account. For example, Bank Call Centre module uses only the methods for registering complaints and fetching complaint status, whereas ATM modules uses only the methods for withdrawal and fetching account balance.
BankAccount class can not only change for multiple reasons but also has a insidious coupling with other modules. This class, indeed, violates SRP.

Class BankAccount cluttered with too many differential responsibilites.
/**
 * This class handles various unrelated responsibilities, has poor cohesion
 * and violates SRP
 */
public class  BankAccount {
 
 
 public Status withdraw( long amount ) {
  // withdraws the given amount and returns the transation success/failure status
 }
 
 public long getAccountBalance() {
  // returns Account balance
 }
 
 public TransactionHistory getTransactionHistory( int lastXDays ) {
  // returns a transaction history of last 'X' days
 }
 
 public int registserComplaint( ComplaintDetails complaint ) {
  // returns complaintId
 }
 
 public Status getComplaintProgressStatus( long complaintId ) {
  // Returns the Status the progress of the given Complain ID
 }
}

How To Avoid SRP Violation

The class with split responsibility needs to separated into multiple classes, each adhering to a single responsibility. Coupling of the separated classes with other interacting classes should be achieved via a layer of abstraction.



figure:4

Let's redesign the BankAccount class of Example # 1. The redesigned structure is illustrated, above, by figure:4. 

Responsibilities of BankAccount are broken into three separate responsibilities, each defined by a separate interface.

Interface to handle responsibilities of Bank Account Transaction
interface BankAccount {

 Status withdraw(long amount);

 long getAccountBalance();

}
class BankAccountImpl implements BankAccount {

 public Status withdraw(long amount) {
  // withdraws the given amount and returns the transation success/failure
  // status
 }

 public long getAccountBalance() {
  // returns Account balance
 }
}

Interface to handle Transaction History
interface BankTransactionHistory {

 TransactionHistory getTransactionHistory(int lastXDays);
}
class BankTransactionHistoryImpl implements BankTransactionHistory {

 public TransactionHistory getTransactionHistory(int lastXDays) {
  // returns a transaction history of last 'X' days
 }
}

Interface to handle Call centre related activities
interface BankHelpCentre {

 int registserComplaint(Complaint complaint);

 Status getComplaintProgressStatus(long complaintId);
}
class BankHelpCentreImpl implements BankHelpCentre {

 public int registserComplaint(Complaint complaint) {
  // returns complaintId
 }

 public Status getComplaintProgressStatus(long complaintId) {
  // Returns the Status the progress of the given Complain ID
 }
}


S          O          L             I           D


No comments:

Post a Comment