Ответ 1
Давайте начнем с того, что на самом деле означает принцип единой ответственности (SRP):
У класса должна быть только одна причина для изменения.
Это эффективно означает, что каждый объект (класс) должен иметь одну ответственность, если класс несет более одной ответственности, эти обязанности становятся связанными и не могут выполняться независимо, то есть изменения в них могут влиять или даже нарушать другие в конкретной реализации.
Определенный должен прочитать, что это сам источник (глава pdf из Разработка, принципы, шаблоны и практика Agile Software): Принцип единой ответственности
Сказав это, вы должны разработать свои классы, чтобы они в идеале делали только одно и делали одно.
Сначала подумайте о том, какие "сущности" у вас есть, в вашем примере я вижу 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 даже не близок к совершенству, т.е. a IChannel
все еще зависит от абстракций IUser
и IMessage
.
В конце концов, нужно стремиться к гибкому, слабо связанному дизайну, но всегда есть компромиссы, а серые области также зависят от того, где вы ожидаете изменения вашего приложения.
SRP, сделанный до крайности, по моему мнению, приводит к очень гибкому, но также фрагментированному и сложному коду, который может быть не столь понятным, как более простой, но несколько более тесно связанный код.
На самом деле, если две обязанности всегда ожидаются изменения в то же время, вы, возможно, не должны разделять их на разные классы, поскольку это могло бы привести, цитируя Мартина, "запах Needless Complexity", То же самое относится к обязанностям, которые никогда не меняются - поведение является инвариантным, и нет необходимости разбить его.
Основная идея здесь заключается в том, что вы должны вынести решение, в котором вы увидите, что обязанности/поведение могут измениться независимо в будущем, поведение которых зависит друг от друга и всегда будет меняться в одно и то же время ( "привязано к хип" ), и какое поведение никогда не изменится в первую очередь.