Learning the principle of single responsibility with C #

I am trying to learn the principle of single responsibility (SRP), but it is quite difficult, because it is difficult for me to understand when and what should I remove from one class, and where should I put / organize it.

I searched for some materials and code examples, but most of the materials I found, instead of simplifying the understanding, made it difficult to understand.

For example, if I have a list of users from this list as well, I have a Called Control class that performs many functions, such as "Send a greeting" and goodbye, when a user arrives / issues, checks the user's weather should be able to log in or no, kick it, receive user commands and messages, etc.

From an example that you don’t need to understand much, I already do too much in one class, but still I don’t quite understand how to divide and reorganize it.

If I understand SRP, I will have a class for joining the channel, for greeting and goodbye, a class for checking the user, a class for reading commands, right?

But where and how can I use punch, for example?

I have a validation class, so I’m sure that I will have any user verification there, including weather or not the user should be kicked.

So, the kick function will be inside the channel connection class and called if the check fails?

For example:

public void UserJoin(User user) { if (verify.CanJoin(user)) { messages.Greeting(user); } else { this.kick(user); } } 

It would be nice if you guys could help me here with ease to understand C # materials that are online and free, or showing me how I will split the quoted example and, if possible, some code examples, tips, etc. .d.

+43
c # solid-principles srp
Sep 24 '11 at 21:28
source share
3 answers

Let's start with what the principle of single responsibility (SRP) really means:

A class should have only one reason to change.

This effectively means that each object (class) must have one responsibility, if the class has more than one responsibility, these responsibilities become related and cannot be performed independently, that is, changes in them can affect or even violate others in a particular implementation.

A certain person should read that this is the source itself (pdf chapter of Agile Software Development, Principles, Patterns and Practice ): The principle of shared responsibility

Having said that, you should develop your classes so that they ideally do just one thing and do one thing.

First, think about what “entities” you have, in your example I see User and Channel and the environment between them through which they exchange data (“messages”). These objects have certain relationships with each other:

  • The user has several channels that he has joined.
  • Channel has multiple users

This also leads, naturally, to the following list of functions:

  • The user can request a connection to the channel.
  • The user can send a message to the channel to which he has joined.
  • User can leave channel
  • A channel may prohibit or allow users to request a connection
  • Channel can hit user
  • A channel can broadcast a message to all users in a channel
  • A channel can send a greeting message to individual users in a channel

SRP is an important concept, but it is hardly worth standing by itself. Equally important for your design is the Dependency Inversion Principle (DIP). To include this in your project, remember that your specific implementations of the User , Message and Channel objects must depend on an abstraction or interface, and not on a specific implementation. For this reason, we start by designing interfaces for non-specific classes:

 public interface ICredentials {} public interface IMessage { //properties string Text {get;set;} DateTime TimeStamp { get; set; } IChannel Channel { get; set; } } public interface IChannel { //properties ReadOnlyCollection<IUser> Users {get;} ReadOnlyCollection<IMessage> MessageHistory { get; } //abilities bool Add(IUser user); void Remove(IUser user); void BroadcastMessage(IMessage message); void UnicastMessage(IMessage message); } public interface IUser { string Name {get;} ICredentials Credentials { get; } bool Add(IChannel channel); void Remove(IChannel channel); void ReceiveMessage(IMessage message); void SendMessage(IMessage message); } 

That this list does not tell us why these functions are performed. We'd better be responsible for the "why" (user management and control) in a separate object - thus, the User and Channel objects should not change if the "why" changes. We can use the strategy template and DI here and can have any specific implementation of IChannel , depending on the IUserControl object, which gives us the why.

 public interface IUserControl { bool ShouldUserBeKicked(IUser user, IChannel channel); bool MayUserJoin(IUser user, IChannel channel); } public class Channel : IChannel { private IUserControl _userControl; public Channel(IUserControl userControl) { _userControl = userControl; } public bool Add(IUser user) { if (!_userControl.MayUserJoin(user, this)) return false; //.. } //.. } 

You see that in the above project, SRP is not even close to perfection, i.e. a IChannel is still dependent on the abstractions of IUser and IMessage .

In the end, you need to strive for a flexible, loosely coupled design, but there are always trade-offs, and the gray areas also depend on where you expect your application to change.

SRP done to the extreme, in my opinion, leads to very flexible, but also fragmented and complex code, which may not be as clear as simpler, but somewhat more closely related code.

In fact, if two responsibilities are always expected to change at the same time, you might not need to separate them into different classes, as this could lead, quoting Martin, to “smell of Needless Complexity.” The same applies to duties that never change - the behavior is invariant, and there is no need to break it.

The main idea here is that you have to make a decision in which you will see that responsibilities / behavior can change independently in the future, the behavior of which depends on each other and will always change at the same time ("tied to hip" ), and what behavior will never change in the first place.

+48
Sep 29 '11 at 2:40
source share

I had a very simple study of this principle. This was presented to me in three small bite sizes:

  • Do one thing.
  • Do this thing only
  • Do this thing well

A code that meets these criteria complies with the principle of single responsibility.

In the code above

 public void UserJoin(User user) { if (verify.CanJoin(user)) { messages.Greeting(user); } else { this.kick(user); } } 

UserJoin does not perform SRP; he does two things, namely: Greeting the user if they can join, or reject them if they cannot. It might be better to reorganize the method:

 public void UserJoin(User user) { user.CanJoin ? GreetUser(user) : RejectUser(user); } public void Greetuser(User user) { messages.Greeting(user); } public void RejectUser(User user) { messages.Reject(user); this.kick(user); } 

Functionally, this is no different from the originally published code. However, this code is more convenient to maintain; what if a new business rule has appeared, due to recent cybersecurity attacks, do you want to record the rejected user IP address? You would just change the RejectUser method. What if you want to show additional messages when a user logs in? Just update the GreetUser method.

SRP in my experience does for supported code. And supported code tends to go a long way toward executing other parts of SOLID.

+14
May 18 '12 at 20:35
source share

My recommendation is to start with the basics: what do you have? You mentioned a few things like Message , User , Channel , etc. Besides simple things, you also have behavior that belongs to your things. Some examples of behavior:

  • can send a message
  • the channel can accept the user (or you can say that the user can join the channel)
  • the channel can hit the user
  • etc.

Please note that this is just one way to look at it. You can abstract any of these behaviors as long as abstraction does not mean anything and everything! But the layer of abstraction usually does not hurt.

Hence, in the PLO there are two general schools of thought: full encapsulation and single responsibility. The former will lead you to the encapsulation of all related activities within its owner object (which will lead to an inflexible design), while the latter will advise against it (which will lead to a weakening of communication and flexibility).

I would continue, but it's late, and I need to get some sleep ... I am doing this as a community, so someone can finish what I started and improve what I still have ...

Happy learning!

+2
Sep 27 '11 at 4:25
source share



All Articles