J'ai récemment appris que les instructions de commutation sont mauvaises dans la POO, notamment dans "Clean Code" (p37-39) de Robert Martin.
Mais considérez cette scène: j'écris un serveur de jeu, je reçois des messages des clients, qui contiennent un entier qui indique l'action du joueur, comme déplacer, attaquer, choisir un objet ... etc., il y aura plus de 30 actions différentes. Lorsque j'écris du code pour gérer ces messages, aucune mesure à laquelle je pense, il devra utiliser switch quelque part. Quel modèle dois-je utiliser si ce n'est pas une instruction switch?
Un commutateur est comme toute autre structure de contrôle. Il y a des endroits où c'est la meilleure solution/la plus propre, et beaucoup d'autres endroits où c'est complètement inapproprié. C'est juste abusé bien plus que d'autres structures de contrôle.
Dans la conception OO, il est généralement considéré comme préférable dans une situation comme la vôtre d'utiliser différents types/classes de messages héritant d'une classe de messages commune, puis d'utiliser des méthodes surchargées pour différencier "automatiquement" les différents types .
Dans un cas comme le vôtre, vous pouvez utiliser une énumération qui correspond à vos codes d'action, puis attacher un attribut à chaque valeur énumérée qui vous permettra d'utiliser des génériques ou de créer des types pour créer différents objets de sous-classe Action afin que la méthode de surcharge soit travail.
Mais c'est une vraie douleur.
Évaluez s'il existe une option de conception telle que l'énumération réalisable dans votre solution. Sinon, utilisez simplement l'interrupteur.
Les "mauvaises" instructions de commutation sont souvent celles qui activent le type d'objet (ou quelque chose qui pourrait être un type d'objet dans une autre conception). En d'autres termes, coder en dur quelque chose qui pourrait être mieux géré par le polymorphisme. D'autres types d'instructions switch pourraient bien être OK
Vous aurez besoin d'une instruction switch, mais d'une seule. Lorsque vous recevez le message, appelez un objet Factory pour renvoyer un objet de la sous-classe Message appropriée (Move, Attack, etc.), puis appelez une méthode message-> doit () pour effectuer le travail.
Cela signifie que si vous ajoutez d'autres types de messages, seul l'objet d'usine doit changer.
Le motif Strategy
vient à l'esprit.
Le modèle de stratégie vise à fournir un moyen de définir une famille d'algorithmes, d'encapsuler chacun comme un objet et de les rendre interchangeables. Le modèle de stratégie permet aux algorithmes de varier indépendamment des clients qui les utilisent.
Dans ce cas, la "famille d'algorithmes" sont vos différentes actions.
En ce qui concerne les instructions switch - dans "Clean Code", Robert Martin dit qu'il essaie de se limiter à une instruction switch par type. Ne les éliminez pas complètement.
La raison en est que les instructions switch n'adhèrent pas à OCP .
Je mettrais les messages dans un tableau, puis ferais correspondre l'élément à la clé de solution pour afficher le message.
Du point de vue des modèles de conception, vous pouvez utiliser le modèle de commande pour votre scénario donné. (Voir http://en.wikipedia.org/wiki/Command_pattern ).
Si vous vous retrouvez à plusieurs reprises à l'aide d'instructions switch dans le paradigme OOP, cela indique que vos classes peuvent ne pas être bien conçues. Supposons que vous ayez une bonne conception des super et sous-classes et une bonne quantité of Polymorphism. La logique derrière les instructions switch doit être gérée par les sous-classes.
Pour plus d'informations sur la façon dont vous supprimez ces instructions switch et introduisez les sous-classes appropriées, je vous recommande de lire le premier chapitre de Refactoring par Martin Fowler. Ou vous pouvez trouver des diapositives similaires ici http://www1.informatik.uni-wuerzburg.de/database/courses/pi2_ss03_dir/RefactoringExampleSlides.pdf . (Diapo 44)
Les instructions IMO switch
ne sont pas mauvaises, mais doivent être évitées si possible. Une solution consisterait à utiliser un Map
où les clés sont les commandes, et les valeurs Command
objets avec une méthode execute()
. Ou un List
si vos commandes sont numériques et n'ont pas d'espaces.
Cependant, généralement, vous utiliseriez des instructions switch
lors de l'implémentation de modèles de conception; un exemple serait d'utiliser un modèle Chaîne de responsabilité pour gérer les commandes données à n'importe quelle commande "id" ou "valeur". (Le modèle Stratégie a également été mentionné.) Cependant, dans votre cas, vous pouvez également examiner le modèle Command .
Fondamentalement, dans la POO, vous essayerez d'utiliser d'autres solutions que de compter sur des blocs switch
, qui utilisent un paradigme de programmation procédurale. Cependant, quand et comment utiliser l'un ou l'autre est quelque peu votre décision. Personnellement, j'utilise souvent des blocs switch
lorsque j'utilise le modèle Factory , etc.
Une définition de l'organisation du code est:
Collection
API dans de nombreux frameworks)Math
...add
s'appuiera sur d'autres méthodes pour le faire et n'exécutera pas cette opération elle-même, car ce n'est pas son contrat. )Par conséquent, si votre instruction switch
effectue différents types d'opérations, vous "" violez "cette définition; alors que l'utilisation d'un modèle de conception ne fonctionne pas car chaque opération est définie dans sa propre classe (c'est son propre ensemble de fonctionnalités).
Utilisez des commandes. Enveloppez l'action dans un objet et laissez le polymorphisme faire le changement pour vous. En C++ (shared_ptr
est simplement un pointeur ou une référence en Java. Il permet une répartition dynamique):
void GameServer::perform_action(shared_ptr<Action> op) {
op->execute();
}
Les clients choisissent une action à effectuer, et une fois qu'ils l'ont fait, ils envoient cette action elle-même au serveur afin que le serveur n'ait pas besoin d'analyser:
void BlueClient::play() {
shared_ptr<Action> a;
if( should_move() ) a = new Move(this, NORTHWEST);
else if( should_attack() ) a = new Attack(this, EAST);
else a = Wait(this);
server.perform_action(a);
}
Je ne l'achète pas. Ces OOP fanatiques semblent avoir des machines qui ont une infinité RAM et des performances incroyables. Évidemment avec inifinite RAM vous n'avez pas avoir à vous soucier de RAM et les impacts sur les performances que vous avez lorsque vous créez et détruisez continuellement de petites classes d'assistance. Pour paraphraser une citation pour le livre "Beautiful Code" - "Chaque problème en informatique peut être résolu avec un autre niveau d'abstraction "
Utilisez un interrupteur si vous en avez besoin. Les compilateurs sont assez bons pour générer du code pour eux.