J'essaie de mettre en œuvre le modèle de conception de commande , mais je suis trébuchant Problème conceptuel. Disons que vous avez une classe de base et quelques sous-classes comme dans l'exemple ci-dessous: Le truc est, chaque utilisateur Je sais que lorsque vous construisez une sous-classe telle que Somecommand, le constructeur de classe de base s'appelle Automaticaly, afin que je puisse ajouter un appel à "enregistrer" là-bas. La chose que je ne veux pas appeler registre avant que l'opérateur () () soit appelée. P> Comment puis-je faire cela? Je suppose que mon design est un peu imparfait, mais je ne sais pas vraiment comment faire ce travail. P> p> () code> est appelé une instance Somecommand, J'aimerais ajouter * ceci à une pile (principalement à des fins d'annulation) en appelant la méthode du registre du commandement. J'aimerais éviter de faire appel à "registre" de Somecommand :: opérateur () (), mais de l'avoir appelé Automaticaly (quelque voie ;-)) P>
5 Réponses :
Il semble que vous puissiez bénéficier de l'idiome NVI (interface non virtuelle). Là l'interface de la commande code> code> n'aurait aucune méthode virtuelle, mais appellerait des points d'extension privés:
class command { public: void operator()() { do_command(); add_to_undo_stack(this); } void undo(); private: virtual void do_command(); virtual void do_undo(); };
Simplement brillant! Merci beaucoup de gars;)
C'est pourquoi les méthodes virtuelles pure ne doivent jamais entrer dans l'interface publique: vous devez toujours ajouter quelque chose (enregistrement, validation, pré ou post-traitement).
@Dinaiz: Brillant? Dites que Sutter, je viens de copier :) (BTW: Stackoverflow.com/users/297582/herb-sutter )
Savoir quand utiliser des modèles et des idiomes est beaucoup plus important que de les connaître, il s'agit donc d'une louange bien méritée.
C'est bien de voir cet excellent motif obtenir la reconnaissance qu'il mérite! C'est l'une de ces choses qui semble un peu en arrière au début (comme tirant des méthodes d'une classe dans des fonctions autonomes), mais lorsque vous y réfléchissez soigneusement, c'est clairement une grosse victoire.
Voir aussi Plus C ++ Idioms - Interface non virtuelle
diviser l'opérateur dans deux méthodes différentes, par exemple Exécuter et exécuterimpl (pour être honnête, je n'aime pas vraiment l'opérateur ()). Make Command :: Exécuter non-virtuel, et commande :: ExecuteImpl Pure virtual, puis autoriser la commande :: EXECUTE Effectuer l'enregistrement, puis appelez-le exécuteurImpl, comme celui-ci:
class Command { public: ResultType execute() { ... // do registration return executeImpl(); } protected: virtual ResultType executeImpl() = 0; }; class SomeCommand { protected: virtual ResultType executeImpl(); };
Gentil et intelligent. Voici mon +1.
Accepter de ne pas aimer opérateur () code>. Si vous avez besoin de transmettre la classe en quelque chose qui prend une fonction, vous pouvez toujours utiliser
Bind code> pour supprimer le nom de la méthode.
Quelle est la raison pour laquelle vous n'aimez pas l'opérateur ()?
@Dinaiz: 1: Il devient plus difficile de ce que la fonction est censée faire (documentation). 2: Plus gros changements d'affrontements de nom (en particulier avec des interfaces ayant des opérateurs virtuels ().
Ce que vous faites ici n'est pas une exception en sécurité! Si ExecuteImpl () jette une commande dans votre pile d'annulation qui n'a jamais été exécutée (entièrement). Dans la réponse de David Rodriguez au-dessus, il est fait correctement.
J'aimerais aussi aimer opérateur () () code> pour une méthode "principale" / "par défaut". @Patrick, je ne vois pas comment les choses sont bien meilleures avec les fonctions "régulièrement nommées" - toute méthode qui pourrait être sensiblement nommée
opérateur () () code> serait probablement nommé
exécuter () CODE> ou
EXECUTE () CODE> Si vous avez utilisé des noms "réguliers", les chances d'une collision de nom ne sont pas beaucoup plus basses à l'IMHO. (Avoir un contre-exemple?)
@j_random_hacker: J'ai déjà vu un exemple où quelqu'un a fait des interfaces d'observateur utilisant des opérateurs de parenthèses: un donethisobserver avec un opérateur de parenthèses virtuel pur, un donéthatobserver avec un opérateur pur de parenthèses virtuel et ainsi de suite. Le résultat était que les implémentations d'observateurs devaient être mises en œuvre comme différentes classes et ne pouvaient pas les mettre en œuvre en 1 classe. Enfin, quelqu'un a fabriqué des cours d'adaptateur qui calait l'opérateur () des méthodes d'Ondonethis, Ondonethat, puis cela a fonctionné, mais nous avons finalement été horrible.
@Patrick: Voyez ce que tu veux dire. Oui, si vous savez à l'avance, plusieurs méthodes «principales» distinctes devront peut-être coexister dans la même classe, ce qui leur donne différents noms simplifiera beaucoup les choses. Mais si vous ne I> savez-le à l'avance, par exemple Si vous concevez un observateur général d'un observateur à usage général, des signaux et des machines à sous Boost, alors par définition, vous avez besoin d'un nom de méthode "générique", puis opérateur () () code> est presque aussi bon que, disons ,
exécuter () code>.
@J_Random_hacker: J'ai trouvé un problème supplémentaire avec l'opérateur (). Certains (ou la plupart?) Les plugins d'IDE ou IDE ne peuvent pas manipuler correctement l'opérateur (). Par exemple. Vous ne pouvez pas exécuter une "Rechercher toutes les références" dans Visual Assist X sur une méthode opérateur (). Cependant, je ne sais pas si ceci est un problème spécifiquement dans Visual Assist X ou un problème général dans tous les environnements de développement.
@Patrick: Je vais totalement vous donner cela :) Cela se sent parfois que C ++ a été conçu spécifiquement pour obtenir le plus près possible de la non-peine que possible ... :)
En supposant que c'est une application «normale» avec annulation et refaire, je n'essaierais pas de mélanger la gestion de la pile avec les actions effectuées par les éléments sur la pile. Il sera très compliqué si vous avez de multiples chaînes d'annulation (plus d'une seule onglet ouverte) ou lorsque vous le faites-endo-redo, où la commande doit savoir s'il faut s'ajouter à annuler ou à se déplacer de Refaire pour annuler, ou se déplacer d'annuler pour refaire. Cela signifie également que vous devez vous moquer de la pile annulation / redo pour tester les commandes. P>
Si vous voulez les mélanger, vous disposerez de trois méthodes de modèle, chacune de la prise des deux piles (ou que l'objet de commande doit avoir des références aux piles qu'il fonctionne lors de la création de personnes créées), et chacun effectuant le déplacement ou l'ajout. , puis appelant la fonction. Mais si vous avez ces trois méthodes, vous verrez qu'ils ne font rien d'autre que d'appeler des fonctions publiques sur la commande et ne sont utilisées par aucune autre partie de la commande, devenez donc candidats la prochaine fois que vous refactez votre code. pour la cohésion. p>
Au lieu de cela, je créerais une classe UNDOREDOSTACCACCACTACT qui a une fonction Execute_Command (Command * Commande) et laissez la commande aussi simple que possible. P>
La suggestion de Patrick est la même que celle de David qui est aussi la même chose que la mienne. Utilisez NVI (Idiome d'interface non virtuelle) à cette fin. Les interfaces virtuelles pures manquent de tout type de contrôle centralisé. Vous pouvez également créer une classe de base abstraite distincte que toutes les commandes hériter, mais pourquoi se déranger?
Pour une discussion détaillée sur la raison pour laquelle NVIS est souhaitable, voir C ++ Codage normes de Sutter. Là-bas, il va jusqu'à suggérer de faire de toutes les fonctions publiques non virtuelles pour obtenir une séparation stricte du code excédentaire du code d'interface publique (qui ne devrait pas être répréhensible afin que vous puissiez toujours avoir un contrôle centralisé et ajouter une instrumentation, pré / post- Vérification des conditions, et tout ce dont vous avez besoin). P>
class Command { public: void operator()() { do_command(); add_to_undo_stack(this); } void undo() { // This might seem pointless now to just call do_undo but // it could become beneficial later if you want to do some // error-checking, for instance, without having to do it // in every single command subclass's undo implementation. do_undo(); } private: virtual void do_command() = 0; virtual void do_undo() = 0; };
J'ai déjà eu un projet de création d'une application de modélisation 3D et que j'avais l'habitude d'avoir la même exigence. Autant que je sache, lorsque vous travaillez, c'était que peu importe ce que l'opération devrait toujours savoir ce qu'elle a fait et devrait donc savoir comment l'annuler. J'ai donc eu une classe de base créée pour chaque opération et son état de fonctionnement comme indiqué ci-dessous.
void OperationManager::undo() { if(!mUndoStack.empty()) { OperationState* state = mUndoStack.pop(); if(state->getParent().undo(state)) { mRedoStack.push(state); }else{ // Throw an exception or warn the user. } } }
Les membres de la commande
code> sont-ils supposés être publics?
Merci pour ton aide. Oui, ils sont censés être publics et j'ai oublié de le mettre dans le code. L'opérateur d'instance de Somecommand () est appelé, je voudrais l'ajouter à une pile. Vous pouvez le voir comme une sorte de "pile d'annulation". Un certain retard pourrait se produire entre le moment où l'objet Somecommand est construit et le moment où l'opérateur () est appelé. Par conséquent, je ne peux pas l'ajouter à la pile à la cause de la construction qui pourrait conduire au programme qui tente d'annuler quelque chose qui n'a pas encore été fait.
Registre est un mot-clé, vous ne pouvez pas nommer un registre de méthode.
Voir aussi Y a-t-il un moyen de numériser pour que les gens oublient d'appeler la version de la classe de base d'un virtuel? .