Background

Attributes are a great resource that allows developers to put extra information on types and members.

They are great in the sense that we put all we need together and they are completely refactoring friendly, as when a member is renamed all its attributes stay there. But
sometimes (or should I say many times) that initial benefit ends up causing more
trouble than they really solve.

Single Responsibility Principle

In Wikipedia I found this definition:

In object-oriented programming, the single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

Even if the idea is great, it does not really tell me when a class (or better, a type as we have structs too) has a single responsibility or not.

Let's see the int (System.Int32) type.

An int variable is a simple value holder. But the struct has methods to parse a string and return an int, to convert that int to a string, to compare an int with another, to do math operations, and so on. It is OK to me, but I can already say that it is a little counter intuitive that some math operations are in the int type itself while others are in the Math class.
Shouldn't methods like Math.Max be in the original types (int.Max, double.Max, and so on)?

Well, maybe, but this article is not really to discuss that point. The point I want to discuss are attributes and how they violate the Single Responsibility Principle.

So, let's see some attribute usages:

[Serializable]
[DisplayName("Non-Empty String")]
[Description("This struct simple holds a string value, which must be null or have a non-empty content.")]
publicstruct NonEmptyString
{
public NonEmptyString(string value)
{
if (value.Length == 0)
thrownew ArgumentException("value must be null or should have a non-empty content.", "value");
_value = value;
}
privatereadonlystring _value;
[Description("This property should be null or have a non-empty content.")]
publicstring Value
{
get
{
return _value;
}
}
}

The struct was created with a single responsibility in mind (store a
non-empty string). You may not like the idea of creating a struct for such a simple validation, but that's not the point here. The point is its responsibility that was altered to:

Make it [Serializable], so it can be used in ASP.NET external sessions (or serialized for any other reason);

Put a better name and description, in case it is used by some kind of editor.

See the problem?

If not, I will tell you. The type should not be worried about the text description to be used by a possible editor. It should not care about a Serializable attribute. It is the responsibility of a serializer to know if it is serializable or not. It is the responsibility of an editor to know some way of finding a better name and description.

Also, I can say that there are other problems, like, how do we support non-English descriptions with this model?

To make the code conform to the Single Responsibility Principle, it should look like this:

That's OK from the Single Responsibility Principle idea. But then how do we solve the
[Serializable] thing? How can an editor find the right DisplayName and
Description?

Well... that can be a little tricky with the old code. Surely we can create yet another type, like
SerializableNonEmptyString that holds a NonEmptyString and adds serialization support. But if we want to serialize an object that already holds a
NonEmptyString we will end-up needing to copy the object to a similar one with serializable types. So, for those cases, maybe it is better to violate the Single Responsibility Principle. Or if we have the option, we can recreate the serialization process (or use an alternative one) that allows us to add specific serialization code for already existing types.

Effectively, the idea is: Every type will have a single responsibility. So the NonEmptyString has the single responsibility of holding a string that is either null or non-empty. Then, a serializer type will know how to serialize a NonEmptyString. It is enough to inform a serialization mechanism that we have the serializer for such
a type.

In fact, with this model, any type that already exists on a third-party DLL can be serialized if it is possible to access all needed properties to store them and reconstruct (or find) the instance later. So, the data type can exist in an assembly and the serializer for it can exist in another assembly. I can say that I use that to serialize WPF colors, for example.

What about the DisplayName and Description?

Well... I can say that a Dictionary<MemberInfo, string> will do the job. Surely
there should be some code available to load descriptions for MemberInfos, but with such
a dictionary we can
already bind a DisplayName or Description to any existing member (in fact, or we will have two different classes with a similar structure, or we should create another type to store the
DisplayName and the Description together).

The best thing with this approach is that we can, again, do it in different assemblies. So, if we are dealing with 3rd party classes, we can still add a display name or a description to the types found there. With a little more work we can support different languages, and everything without changing the original types.

Sample Code

The sample code in this article is a small Binary Serialization library that works using the Single Responsibility Principle.
ConfigurableBinarySerializer must be configured (that is, the serializer for each item type must be added by the user). Also, there is a very small sample with it that only shows how to register the serializers, how to serialize and deserialize. If everything works
OK, the data is serialized to and deserialized from a memory stream, and the console application finishes. So, only use it to see the code.

I don't expect that people simple use this serializer as a replacement for normal serialization techniques, but I hope it gives a better understanding on the Single Responsibility Principle.

One way versus the way

I already received two messages from people that say that attributes don't violate the Single Responsibility Principle. They in fact have valid points. But let's look from an outside view.

Someone (a user, a chef, another programmer) asks for a component that:

Has two properties.

Has two more properties that are calculated from the first ones (P1+P2 and P1*P2).

In fact, the code works. But then the developer receives a "warning" that his code is not right. The fact is:

He must put the [Serializable] attribute in his class, or else it will not work

He must put a Description on the calculated properties.

But to him, that was not what was asked. You may consider him lazy, but he did what he was asked to do.

Is he really wrong?

My answer is no. Not because someone forgot to say that the class was serializable, but because the purpose of the class is to store data. Being serializable is another "trait" of such
a class... but it is better to put that trait somewhere else and let the one responsible for seeing what's serializable to say: Hey, that's a class that can be serialized without problems!

Don't put the need on the programmer that did the class. And, consequently, don't put that need in the class itself. If someone else, at a later time, can figure out that it's possible, allow that. That means: Allow that to be put at run-time, instead of only allowing that at compile-time. And that's where the real question is:

Are you using attributes as hints or are you using it as needs? As
hints, that may be a small violation. But as needs, that's a problem.

Share

About the Author

I started to program computers when I was 11 years old, as a hobbist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do they work easier, faster and with less errors.

Now I just started working as a Senior Software Engineer at Microsoft.

Comments and Discussions

I've read through the thread here and it seems like you haven't written enough software. I'm not talking about dragging and dropping boxes onto a web page, I'm talking slogging through hundreds of classes and their code, with nothing visual going on. I'm also amused by the comments that some of these comments are "academic" and therefore not important. Well, that "academic" stuff was developed to prevent real problems in software development. So, if you think it's "academic" that's because the concept works and you've never encountered the problem it was designed to solve.

You are misunderstanding the single responsibility principle. That is a reason for concern. There is nothing in the principle itself which defines the scope of an object's "responsibility" and gives us clues about where to draw the line, but common sense and experience can provide that.

For example, you're confused why some things are members of the numeric type classes, and other things are shuffled into utility classes like "Math" - common sense is the primary reason, but the other reason is because we can define our single responsibility any way we want. The designers chose to define that responsibility in the case of the Integer class to be "anything having to do with Integers" and they didn't put trig functions in that group because trig functions are not about Integers - but basic addition and subtraction ARE. If I choose to define it that way, I can do that.

You would also do yourself a favor by looking up the history of problems behind serializing objects - it has almost always been understood that the common sense way of doing that is to have objects responsible for their own serialization. You should just be happy you don't have to write your own serializers and unserializers - cuz that was fun, dude, lol

So to boil that down... I think you're missing the point because you lack experience and historical perspective.

It seems that this message was lost here for some time.
But, I did write lots of software. If my examples maybe gave you the wrong idea that doesn't mean I don't understand SRP, Serialization or the responsibility of base types like int. I was using examples that fit my description, but I may forgot to explain every detail.

So, about the Math situation. In the first .Net version we had the primitive types (some other numeric types) and the Math static class, with methods like Abs, Min, Max etc.
But now there is the BigInteger type, and for such type, methods like Abs, Min, Max etc are inside the type itself. The Math class doesn't have methods to work with the BigInteger and, in fact, the creation of the BigInteger type should not affect the Math type.
So, do we have a problem with the single responsibility here?

Well, we have a kind of problem. The BigInteger works differently from the other types. It was created later, but that's no excuse. If all numeric types considered that it was their own responsibility to have Abs, Min, Max etc, that wouldn't happen. But we could also have many different classes (Int32Math, Int64Math) only to avoid putting all of the methods into the same class.
The problem with where starts and where ends the responsibility here is: What will happen if new types are added?

If we consider that methods like Min and Max aren't the responsibility of the primitive type, then it's OK, but at the same type, why is the responsibility of the Math type to deal with Int32, Int64 and others and not with BigInteger?
This problem is even worse if we consider that Min and Max can simple be generic using IComparable<T>. So, Math having Min and Max will be valid, but as a single generic method. The problem of Math having methods for many types is that, someday, a new type will appear and Math can't deal with it. So, Math has too many responsibilities.

If Min and Max were responsibilities of the numeric types, we will have a better rule... but maybe it will be still too many methods to implement to make a numeric-type work.

So, even if I can agree that simple math is the responsibility of the int type and Abs, Min and Max it not, I can't agree that Math has to deal with many types, but not all. Something is broken. If we had many "Math" types it could look ugly, but it will conform better to the principle... so, there is the right and wrong even when we define a responsibility.

About serialization, I never said that it should not exist a default serialization. But if the default does not work, because you have an external type that is not serializable (and it is inside a big graph), what do you do?
If the framework is smart enough, it could find external serializers. If it is not, you have to implement the ISerializable in a type that contains such item, or you need to create an wrapper object... but you are changing the serialized item itself, not the way the serializer finds the serialization algorithm. But if you don't agree with me, look at Silverlight, which simple does not have a [Serializable] attribute.