Обучение единый принцип ответственности с 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. За пределами простого вещи, у вас также есть поведение, принадлежащих вещи. Несколько примеров поведения:
- сообщение может быть отправлено
- канал может принять пользователя (или вы можете сказать, что пользователь может присоединиться к канал)
- канал может пнуть пользователя
- и так далее...
обратите внимание, что это только один способ смотреть на него. Вы можете абстрагировать любое из этих действий, пока абстракция не будет означать ничего и все! Но слой абстракции обычно не вредит.
отсюда, есть две общие школы мысли в ООП: полная инкапсуляция и единая ответственность. Первый приведет вас к инкапсуляции всего связанного поведения внутри свой иметь объект (приводящ к в непреклонном дизайне), тогда как последнее посоветовало бы против его (приводящ в свободных соединении и гибкости).
Я бы пошел, но уже поздно и мне нужно поспать... Я делаю это сообщение сообщества, чтобы кто-то мог закончить то, что я начал, и улучшить то, что у меня есть до сих пор...
счастливого обучения!