Изучение принципа единой ответственности с помощью C#
Я пытаюсь изучить принцип единой ответственности (SRP), но это довольно сложно, поскольку мне очень сложно понять, когда и что я должен удалить из одного класса и где я должен его поместить/организовать.
Я искал в гугле некоторые материалы и примеры кода, но большинство материалов, которые я нашел, вместо того, чтобы сделать его легче понять, было трудно понять.
например, если у меня есть список пользователей и из этого списка я иметь
класс называется Control, который делает много вещей, таких как отправить приветствие и
прощальное сообщение когда пользователь входит/выходит, проверьте погоду пользователя
должен иметь возможность входить или нет и пинать его, получать команды и сообщения пользователя и т. д.
из примера вам не нужно много, чтобы понять, что я уже делаю слишком много в одном классе, но все же я недостаточно ясно понимаю, как разделить и реорганизовать его впоследствии.
Если я понимаю SRP, у меня был бы класс для присоединение к каналу, для приветствия и прощания, класс для проверки пользователя, класс для чтения команд, верно ?
но где и как я мог бы использовать удар, например ?
У меня есть класс проверки, поэтому я уверен, что у меня будет всякая проверка пользователя там, включая погоду или нет, пользователь должен быть выгнан.
таким образом, функция kick будет находиться внутри класса Channel join и вызываться, если проверка не выполняется ?
для пример:
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.
ПСП по моему опыту делает для кода. И поддерживаемый код, как правило, проходит долгий путь к выполнению других частей SOLID.
моя рекомендация, чтобы начать с основ: что вещи у вас есть? Вы упомянули несколько вещи как
Message
,User
,Channel
и т. д. За пределами простого вещи, у вас также есть поведение, принадлежащих вещи. Несколько примеров поведения:
- сообщение может быть отправлено
- канал может принять пользователя (или вы можете сказать, что пользователь может присоединиться канал)
- канал может пнуть пользователя
- и так далее...
обратите внимание, что это только один способ смотреть на него. Вы можете абстрагировать любое из этих поведений, пока абстракция ничего не значит и все! Но, уровень абстракции, как правило, не больно.
отсюда, есть две общие школы мышления в ООП: полная инкапсуляция и единая ответственность. Первый приведет вас к инкапсуляции всего связанного поведения внутри его собственный объект (что приводит к негибкому дизайну), тогда как последний будет советовать против него (что приводит к свободному соединению и гибкости).
Я бы пошел, но уже поздно и мне нужно поспать... Я делаю это сообщение сообщества, чтобы кто-то мог закончить то, что я начал, и улучшить то, что у меня есть до сих пор...
счастливого обучения!
Comments