SRP in Ruby
The Single Responsibility Principle (SRP) says that a class should have one, and only one, reason to change. To say this a different way, the methods of a class should change for the same reasons, they should not be affected by different forces that change at different rates.As an example, imagine the following class in Java:
class Employee
{
public Money calculatePay() {...}
public void save() {...}
public String reportHours() {...}
}
This class violates the SRP because it has three reasons to change. The first is the business rules having to do with calculating pay. The second is the database schema. The third is the format of the string that reports hours. We don't want a single class to be impacted by these three completely different forces. We don't want to modify the Employee class every time the accounts decide to change the format of the hourly report, or every time the DBAs make a change to the database schema, as well as every time the managers change the payroll calculation. Rather, we want to separate these functions out into different classes so that they can change independently of each other.
Of course this seems to fly in the face of OO concepts since a good object should contain all the methods that manipulate it. However, decoupling tends to trump ideals like this. We don't want business rules coupled to report formats, and so we need to put these functions in different classes.
However, in Ruby the situation is a little different. Consider the following three files:
employeeBusinessRules.rb
class Employee
def calculatePay ... end
end
employeeDatabaseSave.rb
class Employee
def save ... end
end
employeeHourlyReport.rb
class Employee
def reportHours ... end
end
Since classes in ruby are constructed at run time (i.e. The code above is part of the program execution) classes may be opened up at any time during program execution and more methods and variable may be added to them. In the main program we miight see something like:
require 'employeeBusinessRules.rb'
require 'employeeDatabaseSave.rb'
require 'employeeHourlyReport.rb'
Thus, the class will be complete before the rest of the program begins to execute. And yet no single source file contains the three different functions. Indeed, the three source files know nothing about each other at all. This means that the concepts in these three source files are not coupled.
Clearly one has to be careful with variables. We could couple these source files being clumsy with access to variables. But with a little care and abstraction we can keep the source files quite nicely decoupled. And yet we can also keep the methods in the same class, and therefore in the same object!
So, in Ruby, there is a way to put the methods in a class that belong in that class, without having to violate the SRP. You simply put the methods that change for different reasons into different source files. This gives us the best of both worlds!
!commentForm -r
In Bob's example, each class file might look like it tells the whole story. In that sense it can be confusing and misleading. I think another approach might help to resolve that confusion. Of course, it will only help if you're comfortable w/ ruby's more meta concepts.
In this case, you would have to change employee.rb to add new modules to it, but this makes it clear to any developer looking at any of the three files that there is more to the story than what they are looking at.
#in employee_database.rb
module EmployeeDatabaseInstanceMethods
def save ... end
end
#in employee_reports.rb
module EmployeeReportsInstanceMethods
def report_hours ... end
end
#in employee.rb
class Employee
include EmployeeDatabaseInstanceMethods
include EmployeeReportsInstanceMethods
def calculate_pay ... end
end
In this case, you would have to change employee.rb to add new modules to it, but this makes it clear to any developer looking at any of the three files that there is more to the story than what they are looking at.
Nguyen - SRP is not just about compilation. It's also about maintenance. Look at the example Bob gives above. The idea is that you shouldn't have to change all your domain objects every time you decide to change database vendors, for example. This is important in any language, IMO.
Like you say, there is the issue of avoiding name clashing.
To me that seems to indicate that we have chosen to defer addressing the problem of separating concerns, and not codify that structure. It seems to me that separating behaviors into different classes *does* force us to codify the relationships, putting that knowledge into our code giving it higher fidelity.
To me that seems to indicate that we have chosen to defer addressing the problem of separating concerns, and not codify that structure. It seems to me that separating behaviors into different classes *does* force us to codify the relationships, putting that knowledge into our code giving it higher fidelity.
I want to question the SRP in Java and .NET, since in these platforms the client object won't have to be recompiled when there are changes to the server object at areas which the client doesn't use. For example, if Client C1 uses Server S1 method A(), but not B(), then even if B() is changed, then C1 doesn't have to be recompiled with the new S1.
In the Smalltalk-based software my company has worked on the last nine years, the home-brewed module framework works in the same way. A customer licenses the basic software and the modules A and C. The modules contain methods for existing classes like in your example, and/or new classes and subclasses. Over the years, this has become a maintenance horror due to hierarchical dependencies of the modules and bad method names. You would see all methods in the Smalltalk code browser belonging to one class, but you would not see easily which module they belong to. It is easy to make the mistake of using a method from module C in module A. As long as all modules are loaded, this is no problem. But when the new code ships to the customers who only have module A, it will crash at some time. And there are classes with some hundred methods now.
I have no idea what programming tools are available for Ruby to detect these errors (sorry, no Ruby experience) or how quickly someone in the team will have the idea to build such a "nice modular framework", but when reading this article some alarms go off in my head. I think working with clearly defined classes - and interfaces that won't change depending on the files you have linked - you can save yourself some big headaches.
I have no idea what programming tools are available for Ruby to detect these errors (sorry, no Ruby experience) or how quickly someone in the team will have the idea to build such a "nice modular framework", but when reading this article some alarms go off in my head. I think working with clearly defined classes - and interfaces that won't change depending on the files you have linked - you can save yourself some big headaches.
I'm not too concerned about the maintenance aspect of this; after all, people worried that OO progams would be hard to maintain because behavior was scattered through the inheritance hierarchies. This is not so different from that.
Michael, you are right there is a definite coupling between all these source files since they all live in the same namespace. This is equivalent to the coupling that exists at the global namespace in a C program. So some care would be necessary to avoid reusing a method or variable name.
Michael, you are right there is a definite coupling between all these source files since they all live in the same namespace. This is equivalent to the coupling that exists at the global namespace in a C program. So some care would be necessary to avoid reusing a method or variable name.
So we can remove the SRP from the "class" and reapply it to the "source file"? I think I like it, but as Wilkes said, it makes source file management a little more interesting.
A .NET 2 specific answer could include partial classes, with each interface implementation in a separate source file, with each source file named for both the class and the interface, using explicit interface implementations to avoid name collisions. Would that address all the concerns other than "being careful with variables"? I'm still thinking on that one. Maybe common data could be hidden in a base class that some consistent & shared data access methods?
A .NET 2 specific answer could include partial classes, with each interface implementation in a separate source file, with each source file named for both the class and the interface, using explicit interface implementations to avoid name collisions. Would that address all the concerns other than "being careful with variables"? I'm still thinking on that one. Maybe common data could be hidden in a base class that some consistent & shared data access methods?
I think the files are radically coupled. Low coupling means that one module does not have to be concerned with the internals of another, but in this case we can't add a method to one of these files without being aware of whether the other file declares a method of the same name.
I usually use Modules for this kind of decoupling.
But this is a nice alternative because each of the aspects of the class can be tested as a complete and independent object.
But this is a nice alternative because each of the aspects of the class can be tested as a complete and independent object.
Another way to do this in AspectJ[?] would be to have a core Employee class in Java and then to introduce different behaviors through aspects. AspectJ[?] lets you add fields and methods to classes. Of course, this can be abused, too.
In any of these approaches, I would be careful to find the orthogonal state and behaviors, which is part of what you're doing in the SRP, and pull those apart. No doubt there will be some overlap, which will require careful refactoring to make the shared state and behavior accessible to the different "responsibilities" (or "concerns", in AOP terms) in a transparent manner. The Open/Closed Principle comes into play here.
In any of these approaches, I would be careful to find the orthogonal state and behaviors, which is part of what you're doing in the SRP, and pull those apart. No doubt there will be some overlap, which will require careful refactoring to make the shared state and behavior accessible to the different "responsibilities" (or "concerns", in AOP terms) in a transparent manner. The Open/Closed Principle comes into play here.
I really don't want to be on a project where it is standard practice for a core domain class to be defined in multiple files. Without some heavy duty tools support, it seems like a maintenance nightmare.
Back to the Java case, wouldn't breaking the methods out violate the DIP, by making concrete implementers of those methods depend on a concrete Employee? That is, unless you use subclassing of Employee or you make Employee abstract somehow?
I think the problem still kinda continues. One of the benefits of SRP is to be able to find the corresponding functionality easier. And make a point-shot to fix/modify/enhance the functionality. In the case above the only way to pinpoint a functionality is to use the class name which can be misleading. I think seeing an interface or an abstract class inside a class definition gives more clue about the context since both of them contains behavior.
One other thing is can't you do the same thing in .net with partial classes?
Murat Uysal
One other thing is can't you do the same thing in .net with partial classes?
Murat Uysal
Add Child Page to SrpInRuby