Обучение единый принцип ответственности с C#

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

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

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

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

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

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

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

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

для пример:

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

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

3 ответов


давайте начнем с того, что делает Принцип Единой Ответственности (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 принято в экстрим на мой взгляд, приводит к очень гибкому, но также фрагментированному и сложному коду, который может быть не так легко понятен, как более простой, но несколько более тесно связанный код.

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

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


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

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

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

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

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.


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

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

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

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

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

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