Подтвердить что ты не робот

Изучение принципа единой ответственности с помощью С#

Я пытаюсь изучить принцип единой ответственности (SRP), но это довольно сложно, поскольку мне сложно понять, когда и что я должен удалить из одного класса, и где я должен его поставить/организовать.

Я искал некоторые материалы и примеры кода, но большинство материалов, которые я нашел, вместо того, чтобы упростить понимание, затрудняли понимание.

Например, если у меня есть список пользователей и из этого списка, у меня есть класс Called Control, который выполняет множество функций, таких как "Отправить приветствие" и до свидания, когда пользователь приходит/выдает, проверяет погоду пользователя должен быть в состоянии войти или нет, пинать его, получать пользовательские команды и сообщения и т.д.

Из примера, который вам не нужно много понимать, я уже делаю слишком много в одном классе, но все же я не совсем понимаю, как его разделить и реорганизовать.

Если я понимаю SRP, у меня будет класс для присоединения к каналу, для приветствия и до свидания, класс для проверки пользователя, класс для чтения команд, правильно?

Но где и как я могу использовать удар, например?

У меня есть класс проверки, поэтому я уверен, что у меня будет всякая проверка пользователя там, в том числе погода или не пользователь должен быть ногами.

Итак, функция kick будет внутри класса соединения канала и вызывается, если проверка не выполняется?

Например:

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

Было бы хорошо, если бы вы, ребята, могли мне помочь здесь с легкостью понять материалы С#, которые находятся в сети и бесплатно, или показывая мне, как я буду раскалывать цитируемый пример и, если возможно, некоторые примеры кодов, советы и т.д.

4b9b3361

Ответ 1

Давайте начнем с того, что на самом деле означает принцип единой ответственности (SRP):

У класса должна быть только одна причина для изменения.

Это эффективно означает, что каждый объект (класс) должен иметь одну ответственность, если класс несет более одной ответственности, эти обязанности становятся связанными и не могут выполняться независимо, то есть изменения в них могут влиять или даже нарушать другие в конкретной реализации.

Определенный должен прочитать, что это сам источник (глава pdf из Разработка, принципы, шаблоны и практика Agile Software): Принцип единой ответственности

Сказав это, вы должны разработать свои классы, чтобы они в идеале делали только одно и делали одно.

Сначала подумайте о том, какие "сущности" у вас есть, в вашем примере я вижу User и Channel и среду между ними, через которую они обмениваются данными ( "сообщения" ). Эти объекты имеют определенные отношения друг с другом:

  • Пользователь имеет несколько каналов, к которым он присоединился
  • Канал имеет несколько пользователей

Это также приводит, естественно, к следующему списку функций:

  • Пользователь может запросить подключение к каналу.
  • Пользователь может отправить сообщение на канал, к которому он присоединился.
  • Пользователь может оставить канал
  • Канал может запретить или разрешить пользователям запрашивать соединение
  • Канал может ударить пользователя
  • Канал может транслировать сообщение всем пользователям в канале
  • Канал может отправлять приветственное сообщение отдельным пользователям в канал

SRP - это важная концепция, но вряд ли стоит стоять сама по себе. Не менее важным для вашего дизайна является Принцип инверсии зависимостей (DIP). Чтобы включить это в проект, помните, что ваши конкретные реализации объектов User, Message и Channel должны зависеть от абстракции или интерфейса, а не от конкретной конкретной реализации. По этой причине мы начинаем с проектирования интерфейсов не конкретных классов:

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

Что этот список не сообщает нам, по какой причине эти функции выполняются. Нам лучше нести ответственность за "почему" (управление пользователями и контроль) в отдельном объекте - таким образом, объекты User и Channel не должны меняться, если "почему" изменяется. Мы можем использовать шаблон стратегии и 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 даже не близок к совершенству, т.е. a IChannel все еще зависит от абстракций IUser и IMessage.

В конце концов, нужно стремиться к гибкому, слабо связанному дизайну, но всегда есть компромиссы, а серые области также зависят от того, где вы ожидаете изменения вашего приложения.

SRP, сделанный до крайности, по моему мнению, приводит к очень гибкому, но также фрагментированному и сложному коду, который может быть не столь понятным, как более простой, но несколько более тесно связанный код.

На самом деле, если две обязанности всегда ожидаются изменения в то же время, вы, возможно, не должны разделять их на разные классы, поскольку это могло бы привести, цитируя Мартина, "запах Needless Complexity", То же самое относится к обязанностям, которые никогда не меняются - поведение является инвариантным, и нет необходимости разбить его.

Основная идея здесь заключается в том, что вы должны вынести решение, в котором вы увидите, что обязанности/поведение могут измениться независимо в будущем, поведение которых зависит друг от друга и всегда будет меняться в одно и то же время ( "привязано к хип" ), и какое поведение никогда не изменится в первую очередь.

Ответ 2

У меня было очень простое изучение этого принципа. Это было представлено мне в трех небольших размерах прикуса:

  • Сделайте одно.
  • Сделайте эту вещь только
  • Сделайте эту вещь хорошо

Код, который соответствует этим критериям, соответствует принципу единой ответственности.

В приведенном выше коде

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

UserJoin не выполняет SRP; он делает две вещи, а именно: Приветствие пользователя, если они могут присоединиться, или отклонить их, если они не могут. Возможно, было бы лучше реорганизовать метод:

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

Функционально это ничем не отличается от первоначально опубликованного кода. Однако этот код более удобен в обслуживании; что, если появилось новое бизнес-правило, из-за недавних атак кибербезопасности вы хотите записать отклоненный IP-адрес пользователя? Вы просто изменили бы метод RejectUser. Что делать, если вы хотите показывать дополнительные сообщения при входе пользователя в систему? Просто обновите метод GreetUser.

SRP по моему опыту делает для поддерживаемого кода. И поддерживаемый код имеет тенденцию идти долгий путь к выполнению других частей SOLID.

Ответ 3

Моя рекомендация - начать с основ: что у вас есть? Вы упомянули несколько вещей, таких как Message, User, Channel и т.д. Помимо простых вещей, у вас также есть поведение, которое принадлежит вашим вещам. Несколько примеров поведения:

  • можно отправить сообщение
  • канал может принять пользователя (или вы можете сказать, что пользователь может присоединиться к каналу)
  • канал может ударить пользователя
  • и т.д.

Обратите внимание, что это всего лишь один из способов взглянуть на него. Вы можете абстрагировать любое из этих поведений, пока абстракция не означает ничего и всего! Но слой абстракции обычно не болит.

Отсюда в ООП есть две общие школы мысли: полная инкапсуляция и единая ответственность. Первый приведет вас к инкапсуляции всех связанных действий внутри своего объекта-владельца (что приведет к негибкому дизайну), в то время как последний будет советовать против него (что приведет к ослаблению связи и гибкости).

Я бы продолжил, но это поздно, и мне нужно немного поспать... Я делаю это сообществом, поэтому кто-то может закончить то, что я начал, и улучшить то, что у меня есть до сих пор...

Счастливое обучение!