web-dev-qa-db-fra.com

Les instructions de commutation sont mauvaises?

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?

36
Hongbo

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.

32
Toby

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.

18
The Archetypal Paul

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 .

17
Oded

Je mettrais les messages dans un tableau, puis ferais correspondre l'élément à la clé de solution pour afficher le message.

4

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)

4
TechTravelThink

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:

  • un package est un groupe de classes avec une API cohérente (ex: Collection API dans de nombreux frameworks)
  • une classe est un ensemble de fonctionnalités cohérentes (ex: une classe Math ...
  • une méthode est a fonctionnalité; il devrait faire une chose et une seule chose. (ex: ajouter un élément dans une liste peut nécessiter d'agrandir cette liste, auquel cas la méthode 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).

4
Yanick Rochon

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);
}
2
wilhelmtell

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.

0
James