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



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



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




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




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



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



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



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



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



для пример:



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


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

394   3  

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.

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

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

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

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

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

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

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

Comments

    Ничего не найдено.