Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

[Note: I'm going to be talking about objects here. Objects is what object-oriented programming is about, after all, not classes.]

What the responsibility of an object is depends mostly on your domain model. There are usually many ways to model the same domain, and you will choose one way or the other based on how the system is going to be used.

As we all know, the "verb/noun" methodology that is often taught in introductory courses is ridiculous, because it depends so much on how you formulate a sentence. You can express almost anything as either a noun or a verb, in either an active or a passive voice. Having your domain model depend on that is wrong, it should be a conscious design choice whether or not something is an object or a method, not just an accidental consequence of how you formulate the requirements.

However, it does show that, just like you have many possibilities of expressing the same thing in English, you have many possibilities of expressing the same thing in your domain model … and none of those possibilities is inherently more correct than the others. It depends on the context.

Here is an example which is also very popular in introductory OO courses: a bank account. Which is typically modeled as an object with a balance field and a transfer method. In other words: the account balance is data, and a transfer is an operation. Which is a reasonable way to model a bank account.

Except, that's not how bank accounts are modeled in real-world banking software (and in fact, it's not how banks work in the real world). Instead, you have a transaction slip, and the account balance is computed by adding (and subtracting) up all the transaction slips for an account. In other words: the transfer is data and the balance is an operation! (Interestingly, this also makes your system purely functional, since you never need to modify the balance, both your account objects and the transaction objects never need to change, they are immutable.)

As to your specific question about toString, I agree. I very much prefer the Haskell solution of a Showable type class. (Scala teaches us that type classes fit beautifully with OO.) Same with equality. Equality is very often not a property of an object but of the context in which the object is used. Just think about floating point numbers: what should the epsilon be? Again, Haskell has the Eq type class.

All too often the Single Responsibility Principle becomes a Zero Responsibility Principle, and you end up with Anemic Classes that do nothing (except setters and getters), which leads to a Kingdom of the Nouns disaster.

You'll never get it perfect, but better for a class to do a bit too much than too little.

In you example with Encoding, IMO, it definitely should be able to encode. What do you thing it should do instead? Just having a name "utf" is zero responsibility. Now, maybe the name should be Encoder. But,as Konrad said, data (which encoding) and behavior (doing it) belong together.

An instance of a class is a closure. That's it. If you think that way all well designed software you look at will look right, and all poorly thought out software will not. Let me expand.

If you want to write something to write to a file, think of all the things you need to specify (to the OS): filename, access method (read, write, append), the string you want to write.

So you construct a File object with the filename (and access method). The File object is now closed over the filename (in this case it will probably have taken it in as a readonly/const value). The File instance is now ready to take calls to the "write" method defined in the class. This just takes a string argument, but in the body of the write method, the implementation, there is also access to the filename (or file handle that was created from it).

An instance of File class therefore boxes up some data into some kind of composite blob so that later use of that instance is simpler. When you have a File object, you don't need to worry about what the filename is, you are only concerned about what string you put as argument into the write call. To reiterate - the File object encapsulates all that you don't need to know about where the string you want to write is actually going.

Most problems that you want to solve are layered in this way - things created up front, things created at the start of each iteration of some kind of process loop, then different things declared in the two halves of an if else, then things created at the start of some kind of sub loop, then things declared as some part of algorithm within that loop etc. As you add more and more function calls to the stack, you are doing ever finer-grained code, but each time you'll have boxed up the data in the layers lower in the stack into some kind of nice abstraction which makes it easy to access. See what you are doing is using "OO" as a kind of closure management framework for a functional programming approach.

The shortcut solution to some problems involves mutable state of objects, which is not so functional - you're manipulating the closures from the outside. You're making some of the things in your class "global", at least to the scope above. Every time you write a setter - try to avoid those. I'd argue this is where you got the responsibility of your class, or the other classes in which it operates, wrong. But don't worry too much - it's pragmatic sometimes to stir in a bit of mutable state to solve certain kinds of problems quickly, without too much cognitive load, and actually get anything done.

So in summary, to answer to your question - what is the real responsibility of a class? It's to present kind of a platform, based on some underlying data, to achieve a more detailed kind of operation. It's to encapsulate half the data involved in an operation that changes less frequently than the other half of the data (Think currying...). E.g. filename vs string to write.

Often these encapsulations looks superficially like real world object/an object in the problem domain but don't be fooled. When you create a Car class, it's not actually a car, it's a collection of data that forms a platform on which to achieve certain kinds of things you might consider wanting to do on a car. Forming a string representation (toString) is one of those things - spitting out all that internal data. Remember that sometimes a Car class might not be the right class even if the problem domain is all about cars. In contrast to Kingdom of nouns, it's the operations, the verbs that a class should be based around.

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.

While it does, it might not necessarily make for good code. Beyond the simple fact that any sort of blindly followed rule leads to bad code, you're starting off an invalid premise: (programming) objects are not (physical) objects.

Objects are a set of cohesive bundles of data and operations (and sometimes only one of the two). While these often model real world objects, the difference between a computer model of something and that thing itself necessitates differences.

By taking that hard line between a "noun" that represents a thing and other things that consume them, you're going against a key benefit of object oriented programming (having function and state together so that the function can protect invariants of the state). Worse, you're trying to represent the physical world in code, which as history has shown, will not work well.

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.

In theory, yes, but I think that C# makes a justified compromise for the sake of simplicity (as opposed to Java's more strict approach).

If Encoding only represented (or identified) certain encoding - say, UTF-8 - then you'd obviously need an Encoder class, too, so that you can implement GetBytes on it - but then you need to manage the relation between Encoders and Encodings, so we end up with our good ol'

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

so brilliantly lampooned in that article you linked to (great read, by the way).

If I understand you well, you're asking where the line is.

Well, on the opposite side of spectrum is the procedural approach, not hard to implement in OOP languages with the use of static classes:

HelpfulStuff.GetUTF8Bytes(myString)

The big problem about it is that when the author of HelpfulStuff leaves and I'm sat in front of the monitor, I have no way of knowing where GetUTF8Bytes is implemented.

Do I look for it in HelpfulStuff, Utils, StringHelper?

Lord help me if the method is actually implemented independently in all three of those... which happens a lot, all it takes is that the guy before me did not know where to look for that function either, and believed there wasn't any yet so they snapped out another one and now we have them in abundance.

To me, proper design is not about satisfying abstract criteria, but about my ease to carry on after I sit in front of your code. Now how does

EncodersFactory.getDefaultFactory().createEncoder(new UTF8Encoding()).getBytes(myString)

rate in this aspect? Poorly as well. It's not an answer I want to hear when I shout to my workmate "how do I encode a string into a byte sequence?" :)

So I'd go with moderate approach and be liberal about single responsibility. I'd rather have the Encoding class both identifying a type of encoding and doing the actual encoding: data unseparated from behaviour.

I think it passes as a facade pattern, which helps to grease the gears. Does this legitimate pattern violate SOLID? A related question: Does the Facade pattern violate SRP?

Note that this might be how getting encoded bytes are implemented internally (in .NET libraries). The classes are just not publicly visible and only this "facadeous" API is exposed outside. It's not that hard to verify if that's true, I'm just bit too lazy to do it right now :) It probably isn't, but that doesn't invalidate my point.

You may choose to simplify the publicly visible implementation for the sake of friendliness while maintaing a more severe, canonical implementation internally.

In Java the abstraction used to represent files is a stream, some streams are unidirectional (read only or write only) others are bidirectional. For Ruby the File class is the abstraction that represents OS files. So the question becomes what is its single responsibility? In Java, FileWriter's responsibility is to provide a unidirectional stream of bytes that is connected to an OS file. In Ruby, File's responsibility is to provide bidirectional access to a system file. They both fulfill the SRP they just have different responsibilities.

It's not a Car's or a Dog's responsibility to convert itself to a string, now is it?

Sure why not? I expect the Car class to fully represent a Car abstraction. If it makes sense for the car abstraction to be used where a string is necessary then I fully expect the Car class to support conversion to a string.

To answer the larger question directly a class's responsibility is to act as an abstraction. That job may be complex, but that is kind of the point, an abstraction should hide complex things behind an easier to use, less complex interface. The SRP is a design guideline so you focus on the question "what does this abstraction represent?". If multiple different behaviors are necessary to fully abstract the concept then so be it.

Class can have multiple responsibilities. At least in classic data-centric OOP.

If you are applying Single responsibility principle to its full extent, you get responsibility-centric design where basically every non-helper method belongs to its own class.

I think there is nothing wrong with extracting a piece of complexity from a base class as in case of File and FileWriter. The advantage is that you get clear separation of code that is responsible for a certain operation and also that you can override (subclass) just that piece of code instead of overriding the whole base class (e.g. File). Nevertheless, applying that systematically seems to be an overkill to me. You simply get much more classes to handle and for every operation you need to invoke its respective class. It is flexibility that comes at some cost.

These extracted classes should have very descriptive names based on the operation they contain, e.g. <X>Writer, <X>Reader, <X>Builder. Names like <X>Manager, <X>Handler does not describe the operation at all and if they are called like that because they contain more than one operation, then it is not clear what you have even achieved. You have separated the functionality from its data, you still break single responsibility principle even in that extracted class, and if you are looking for a certain method you might not know where to look for it (if in <X> or <X>Manager).

Now, your case with UserDataHandler is different because there is no base class (the class that contains actual data). The data for this class are stored in an external resource (database) and you are using an api to access them and manipulate them. Your User class represents a run-time user, which is really something different than a persistent user and the separation of responsibilities (concerns) might come handy here especially if you have got lots of run-time-user related logic.

You probably named UserDataHandler like that because there is basically just functionality in that class and no real data. However, you might also abstract from that fact and consider the data in database to belong to the class. With this abstraction you can name the class based on what it is and not on what it does. You can give it a name like UserRepository, which is pretty slick and also suggests usage of repository pattern.

Licensed under: CC-BY-SA with attribution
scroll top