Programing

C #으로 단일 책임 원칙 학습

lottogame 2021. 1. 8. 07:45
반응형

C #으로 단일 책임 원칙 학습


SRP (Single Responsibility Principle)를 배우려고하지만 한 클래스에서 언제 무엇을 제거해야하며 어디에 배치 / 구성해야하는지 파악하기가 매우 어렵 기 때문에 상당히 어렵습니다.

몇 가지 자료와 코드 예제를 검색했지만 내가 찾은 대부분의 자료는 이해하기 쉽게 만드는 대신 이해하기 어렵게 만들었습니다.

예를 들어 사용자 목록이 있고 해당 목록에서 사용자가 들어 오거나 나갈 때 인사 및 작별 인사 보내기, 사용자가 입력 할 수있는 날씨 확인과 같은 많은 작업을 수행하는 Called Control 클래스가있는 경우 그를 차고, 사용자 명령과 메시지 등을받습니다.

예제에서 내가 이미 하나의 클래스로 너무 많은 일을하고 있다는 것을 이해할 필요가 없지만 나중에 분할하고 재구성하는 방법에 대해서는 충분히 명확하지 않습니다.

SRP를 이해하면 채널에 참여하기위한 수업, 인사와 작별, 사용자 확인 수업, 명령어 읽기 수업을 듣 겠죠?

그러나 예를 들어 킥을 어디서 어떻게 사용합니까?

나는 확인 클래스가 있으므로 날씨를 포함하여 모든 종류의 사용자 확인이 있거나 사용자를 쫓아 내지 않아야한다고 확신합니다.

그래서 kick 함수는 채널 조인 클래스 내부에 있고 확인이 실패하면 호출됩니까?

예를 들면 :

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

온라인에서 무료로 제공되는 이해하기 쉬운 C # 자료로 여기에 도움을 주시거나 인용 된 예제를 분할하는 방법과 가능한 경우 샘플 코드, 조언 등을 보여 주시면 감사하겠습니다.


SRP ( Single Responsibility Principle )가 실제로 의미 하는 바부터 시작하겠습니다 .

클래스는 변경해야하는 이유가 하나만 있어야합니다.

이것은 효과적으로 모든 객체 (클래스)가 하나의 책임을 가져야한다는 것을 의미합니다. 만약 클래스가 하나 이상의 책임을 가지고 있다면 이러한 책임은 결합되어 독립적으로 실행될 수 없습니다. 즉, 하나의 변경이 특정 구현에서 다른 하나에 영향을 미치거나 심지어 깨뜨릴 수 있습니다.

이를 위해 반드시 읽어야 할 것은 소스 자체입니다 ( "Agile Software Development, Principles, Patterns and Practices"의 pdf 장 ) : The Single Responsibility Principle

그래도 수업은 이상적으로는 한 가지만하고 한 가지만 잘하도록 설계해야합니다.

먼저 "개체"가 무엇인지 생각해보십시오. 귀하의 예에서 내가 볼 수있는 User것과 Channel이들이 통신하는 매개체 ( "메시지") 가 무엇인지 생각해보십시오 . 이러한 개체는 서로 특정 관계를 가지고 있습니다.

  • 사용자가 가입 한 여러 채널이 있습니다.
  • 채널에는 여러 사용자가 있습니다.

이것은 또한 자연스럽게 다음 기능 목록을 수행합니다.

  • 사용자는 채널 가입을 요청할 수 있습니다.
  • 사용자는 자신이 가입 한 채널에 메시지를 보낼 수 있습니다.
  • 사용자는 채널을 떠날 수 있습니다.
  • 채널은 사용자의 가입 요청을 거부하거나 허용 할 수 있습니다.
  • 채널은 사용자를 추방 할 수 있습니다.
  • 채널은 채널의 모든 사용자에게 메시지를 브로드 캐스트 할 수 있습니다.
  • 채널은 채널의 개별 사용자에게 인사말 메시지를 보낼 수 있습니다.

SRP는 중요한 개념이지만 그 자체로서는 안됩니다. 설계에 똑같이 중요한 것은 DIP ( Dependency Inversion Principle )입니다. 이를 디자인에 통합하려면 User, MessageChannel엔티티 의 특정 구현이 특정 구체적인 구현이 아닌 추상화 또는 인터페이스 에 의존해야한다는 점을 기억하십시오 . 이런 이유로 우리는 구체적인 클래스가 아닌 인터페이스 디자인부터 시작합니다.

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);
}

이 목록이 알려주지 않는 것은 이러한 기능이 실행되는 이유 입니다. "이유"(사용자 관리 및 제어)의 책임을 별도의 개체에 두는 것이 좋습니다. 이렇게하면 "이유"가 변경 되더라도 UserChannel개체가 변경 될 필요가 없습니다. 여기에서 전략 패턴과 DI를 활용할 수 있으며 "이유"를 제공 IChannel하는 IUserControl엔터티 따라 구체적으로 구현할 수 있습니다 .

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;
        //..
    }
    //..
}

위의 디자인에서 SRP는 완벽에 가깝지 않습니다. 즉, an IChannel은 여전히 ​​추상화 IUserIMessage.

In the end one should strive for a flexible, loosely coupled design but there are always tradeoffs to be made and grey areas also depending on where you expect your application to change.

SRP taken to the extreme in my opinion leads to very flexible but also fragmented and complex code that might not be as readily understandable as simpler but somewhat more tightly coupled code.

In fact if two responsibilities are always expected to change at the same time you arguably should not separate them into different classes as this would lead, to quote Martin, to a "smell of Needless Complexity". The same is the case for responsibilities that never change - the behavior is invariant, and there is no need to split it.

The main idea here is that you should make a judgment call where you see responsibilities/behavior possibly change independently in the future, which behavior is co-dependent on each other and will always change at the same time ("tied at the hip") and which behavior will never change in the first place.


I had a very easy time learning this principle. It was presented to me in three small, bite-sized parts:

  • Do one thing
  • Do that thing only
  • Do that thing well

Code that fulfills those criteria fulfills the Single-Responsibility Principle.

In your above code,

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

UserJoin does not fulfill the SRP; it is doing two things namely, Greeting the user if they can join, or rejecting 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 code originally posted. However, this code is more maintainable; what if a new business rule came down that, because of recent cybersecurity attacks, you want to record the rejected user's IP address? You would simply modify method RejectUser. What if you wanted to show additional messages upon user login? Just update method GreetUser.

SRP in my experience makes for maintainable code. And maintainable code tends to go a long ways toward fulfilling the other parts of SOLID.


My recommendation is to start with the basics: what things do you have? You mentioned multiple things like Message, User, Channel, etc. Beyond the simple things, you also have behaviors that belong to your things. A few examples of behaviors:

  • a message can be sent
  • a channel can accept a user (or you might say a user can join a channel)
  • a channel can kick a user
  • and so on...

Note that this is just one way to look at it. You can abstract any one of these behaviors until abstraction means nothing and everything! But, a layer of abstraction usually doesn't hurt.

From here, there are two common schools of thought in OOP: complete encapsulation and single responsibility. The former would lead you to encapsulate all related behavior within its owning object (resulting in inflexible design), whereas the latter would advise against it (resulting in loose coupling and flexibility).

I would go on but it's late and I need to get some sleep... I'm making this a community post, so someone can finish what I've started and improve what I've got so far...

Happy learning!

ReferenceURL : https://stackoverflow.com/questions/7542051/learning-single-responsibility-principle-with-c-sharp

반응형