Изучение принципа единой ответственности с C # - PullRequest
54 голосов
/ 25 сентября 2011

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

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

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

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

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

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

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

Таким образом, функция удара будет внутри класса соединения канала и будет вызываться в случае сбоя проверки?

Например:

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

Буду признателен, если вы, ребята, поможете мне здесь с простыми для понимания материалами на C #, которые доступны онлайн и бесплатны, или продемонстрируете, как я буду разбивать приведенный пример и, если возможно,некоторые примеры кодов, советы и т. д.

Ответы [ 3 ]

59 голосов
/ 29 сентября 2011

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

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

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

Определенное обязательное прочтение для этого является самим источником (глава в формате pdf от «Гибкая разработка программного обеспечения, принципы, шаблоны и практики» ): Принцип единой ответственности

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

Сначала подумайте о том, какие «сущности» у вас есть, в вашем примере я вижу 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 даже не близок к идеальному, то есть IChannel все еще зависит от абстракций IUser и IMessage.

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

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

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

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

21 голосов
/ 19 мая 2012

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

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

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

В указанном выше коде

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 голосов
/ 27 сентября 2011

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...