Object-Oriented Design – Real Responsibility of a Class

object-orientedsingle-responsibilitysolid

I keep wondering if it is legitimate to use verbs that are based on nouns in OOP.
I came across this brilliant article, though I still disagree with the point it makes.

To explain the problem a bit more, the article states that there shouldn't be, for instance, a FileWriter class, but since writing is an action it should be a method of the class File. You'll get to realize that it's often language dependent since a Ruby programmer would likely be against the use of a FileWriter class (Ruby uses method File.open to access a file), whereas a Java programmer wouldn't.

My personal (and yes, very humble) point of view is that doing so would break the Single Responsibility principle. When I programmed in PHP (because PHP is obviously the best language for OOP, right?), I would often use this kind of framework:

<?php

// This is just an example that I just made on the fly, may contain errors

class User extends Record {

    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

}

class UserDataHandler extends DataHandler /* knows the pdo object */ {

    public function find($id) {
         $query = $this->db->prepare('SELECT ' . $this->getFields . ' FROM users WHERE id = :id');
         $query->bindParam(':id', $id, PDO::PARAM_INT);
         $query->setFetchMode( PDO::FETCH_CLASS, 'user');
         $query->execute();
         return $query->fetch( PDO::FETCH_CLASS );
    }


}

?>

It is my understanding that the suffix DataHandler doesn't add anything relevant; but the point is that the single responsibility principle dictates us that an object used as a model containing data (may it be called a Record) shouldn't also have the responsibility of doing SQL queries and DataBase access. This somehow invalidates the ActionRecord pattern used for instance by Ruby on Rails.

I came across this C# code (yay, fourth object language used in this post) just the other day:

byte[] bytes = Encoding.Default.GetBytes(myString);
myString = Encoding.UTF8.GetString(bytes);

And I gotta say that it doesn't make much sense to me that an Encoding or Charset class actually encodes strings. It should merely be a representation of what an encoding really is.

Thus, I would tend to think that:

  • It is not a File class responsibility to open, read or save files.
  • It is not a Xml class responsibility to serialize itself.
  • It is not a User class responsibility to query a database.
  • etc.

However, if we extrapolate these ideas, why would Object have a toString class? It's not a Car's or a Dog's responsibility to convert itself to a string, now is it?

I understand that from a pragmatic point of view, getting rid of the toString method for the beauty of following a strict SOLID form, that makes code more maintainable by making it useless, is not an acceptable option.

I also understand that there may not be an exact answer (which would more be an essay than a serious answer) to this, or that it may be opinion-based. Nevertheless I would still like to know if my approach actually follows what the single-responsibility principle really is.

What's a class's responsibility?

Best Answer

Given some divergences between languages, this can be a tricky topic. Thus, I'm formulating the following commentaries in a way that tries to be as comprehensive as I can inside the realm of OO.

First of all, the so called "Single Responsibility Principle" is a reflex -- explicitly declared -- of the concept cohesion. Reading the literature of the time (around '70), people were (and still are) struggling to define what a module is, and how to construct them in a way that would preserve nice properties. So, they would say "here is a bunch of structures and procedures, I'll make a module out of them", but with no criteria as to why this set of arbitrary things are packaged together, the organization might end up making little sense -- little "cohesion". Hence, discussions on criteria emerged.

So, the first thing to note here is that, so far, the debate is around organization and related effects on maintenance and understandability (for little matter to a computer if a module "makes sense").

Then, someone else (mr. Martin) came in and applied the same thinking to the unit of a class as a criteria to use when thinking about what should or should not belong to it, promoting this criteria to a principle, the one being discussed here. The point he made was that "A class should have only one reason to change".

Well, we know from experience that many objects (and many classes) that appear to do "many things" have a very good reason for doing so. The undesirable case would be the classes that are bloated with functionality to the point of being impenetrable to maintenance, etc. And to understand the latter is to see where mr. Martin was aiming at when he elaborated on the subject.

Of course, after reading what mr. Martin wrote, it should be clear these are criteria for direction and design to avoid problematic scenarios, not in any way to pursue any kind of compliance, let alone strong compliance, specially when "responsibility" is ill defined (and questions like "does this violates the principle?" are perfect examples of the widespread confusion). Thus, I find it unfortunate it is called a principle, misleading people into try to take it to the last consequences, where it would do no good. Mr. Martin himself discussed designs that "do more than one thing" that should probably be kept that way, since separating would yield worse results. Also, there are many known challenges regarding modularity (and this subject is a case of it), we are not at a point of having good answers even for some simple questions about it.

However, if we extrapolate these ideas, why would Object have a toString class? It's not a Car's or a Dog's responsibility to convert itself to a string, now is it?

Now, let me pause to say something here about toString: there is a fundamental thing commonly neglected when one makes that transition of thought from modules to classes and reflect on what methods should belong to a class. And the thing is dynamic dispatch (aka, late binding, "polymorphism").

In a world with no "overriding methods", choosing between "obj.toString()" or "toString(obj)" is a matter of syntax preference alone. However, in a world where programmers can change the behavior of a program by adding a subclass with distinct implementation of an existing/overridden method, this choice is no more of taste: making a procedure a method also can make it a candidate for overriding, and the same might not be true for "free procedures" (languages that support multi-methods have a way out of this dichotomy). Consequently, it is no more a discussion on organization only, but on semantics as well. Finally, to which class the method is bound, also becomes an impacting decision (and in many cases, so far, we have little more than guidelines to help us decide where things belong, as non-obvious trade-offs emerge from different choices).

Finally, we are faced with languages that carry terrible design decisions, for instance, forcing one to create a class for every little bit of thing. Thus, what once was the canonical reason and main criteria for having objects (and in the class-land, therefore, classes) at all, which is, to have these "objects" that are kind of "behaviors that also behave like data", but protect their concrete representation (if any) from direct manipulation at all costs (and that's the main hint for what should be the interface of an object, from the point of view of its clients), gets blurred and confused.

Related Topic