Commençons par ce que fait Principe de responsabilité unique (SRP) signifie réellement :
Une classe ne devrait avoir qu'une seule raison de changer.
Cela signifie effectivement que chaque objet (classe) doit avoir une seule responsabilité, si une classe a plus d'une responsabilité, ces responsabilités deviennent couplées et ne peuvent pas être exécutées indépendamment, c'est-à-dire que les changements dans l'une peuvent affecter ou même casser l'autre dans une implémentation particulière.
Pour cela, il faut absolument lire la source elle-même (chapitre pdf de "Développement logiciel agile, principes, modèles et pratiques". ) : Le principe de la responsabilité unique
Cela dit, vous devez concevoir vos cours de manière à ce qu'ils ne fassent idéalement qu'une seule chose et qu'ils la fassent bien.
Tout d'abord, réfléchissez aux "entités" que vous avez, dans votre exemple je peux voir User
y Channel
et le support entre eux par lequel ils communiquent ("messages"). Ces entités ont certaines relations entre elles :
- Un utilisateur a un certain nombre de canaux qu'il a rejoint.
- Un canal a un certain nombre d'utilisateurs
Cela conduit naturellement à la liste suivante de fonctionnalités :
- Un utilisateur peut demander à rejoindre un canal.
- Un utilisateur peut envoyer un message à un canal qu'il a rejoint.
- Un utilisateur peut quitter un canal
- Un canal peut refuser ou autoriser la demande d'un utilisateur de se joindre à lui.
- Un canal peut renvoyer un utilisateur
- Un canal peut diffuser un message à tous les utilisateurs du canal.
- Un canal peut envoyer un message d'accueil aux utilisateurs individuels dans le canal
L'ASR est un concept important, mais il ne doit pas se suffire à lui-même - l'ASR est tout aussi important pour votre conception. Principe d'inversion des dépendances (DIP). Pour incorporer cela dans la conception, n'oubliez pas que vos implémentations particulières de l'algorithme de l'arbre à cames (DIP) ne peuvent être utilisées. User
, Message
y Channel
doivent dépendre d'une abstraction ou interface plutôt qu'une implémentation concrète particulière. C'est pourquoi nous commençons par concevoir des interfaces et non des classes concrètes :
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);
}
Ce que cette liste ne nous dit pas c'est pour quelle raison ces fonctionnalités sont exécutées. Il est préférable de confier la responsabilité du "pourquoi" (gestion et contrôle des utilisateurs) à une entité distincte - de cette façon, l User
y Channel
Les entités ne doivent pas changer si le "pourquoi" change. Nous pouvons tirer parti du modèle de stratégie et de DI ici et avoir n'importe quelle implémentation concrète de IChannel
dépendent d'un IUserControl
entité qui nous donne le "pourquoi".
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;
//..
}
//..
}
Vous voyez que dans la conception ci-dessus, l'ASR n'est même pas proche de la perfection, c'est-à-dire qu'une IChannel
dépend toujours des abstractions IUser
y IMessage
.
En fin de compte, il faut s'efforcer d'obtenir une conception flexible et faiblement couplée, mais il y a toujours des compromis à faire et des zones d'ombre, en fonction de l'endroit où l'on se trouve. s'attendre à votre application à changer.
L'ASR prise au extrême à mon avis, cela conduit à un code très flexible, mais aussi fragmenté et complexe, qui peut ne pas être aussi facile à comprendre qu'un code plus simple mais un peu plus étroitement couplé.
En fait, si deux responsabilités sont toujours sont censés changer en même temps, vous ne devriez pas les séparer en différentes classes car cela conduirait, pour citer Martin, à une "odeur de complexité inutile". Il en va de même pour les responsabilités qui ne changent jamais - le comportement est invariable, et il n'y a pas besoin de les séparer.
L'idée principale ici est que vous devez déterminer les responsabilités/comportements qui pourraient changer indépendamment à l'avenir, les comportements qui dépendent les uns des autres et qui changeront toujours en même temps ("liés par la hanche") et les comportements qui ne changeront jamais.