Command Design Patterns -> mit User input

Sadakazu

Well-Known Member
Hallo....
Ich hoffe das ich jetzt endlich das richtige Thema gefunden habe xD
Also ich möchte das ein Benutzer mehrere Kommandos eingeben kann.

zb: foo, bar, foobar, barfoo, start, stop, start foo, start bar, start foobar, start barfoo usw usf....

Ergo möchte ich Kommandos mit einer Parameter Liste erstellen....
Denkbar einfach wären ja strings Arrays zu benutzen und das ganze mit if else zu bauen....

Bei zwei drei Kommandos kann man das ja durchaus noch realisieren.... Aber bei 10 Komandos und mehr?

Kann man das mit den Command Design Patterns regeln?

Ich hab auf linuxtopia.org dazu einen kurzen artikel gelesen...
Hier mal dazu der Quellcode:
Code:
//: C10:CommandPattern.cpp
#include <iostream>
#include <vector>
using namespace std;

class Command {
public:
virtual void execute() = 0;
};

class Hello : public Command {
public:
void execute() { cout << "Hello "; }
};

class World : public Command {
public:
void execute() { cout << "World! "; }
};

class IAm : public Command {
public:
void execute() { cout << "I'm the command pattern!"; }
};

// An object that holds commands:
class Macro {
vector<Command*> commands;
public:
void add(Command* c) { commands.push_back(c); }
void run() {
vector<Command*>::iterator it = commands.begin();
while(it != commands.end())
(*it++)->execute();
}
};

int main() {
Macro macro;
macro.add(new Hello);
macro.add(new World);
macro.add(new IAm);
macro.run();
} ///:~

Soweit ist mir das ganze Thema auch klar
Ich würd das ganze zwar nicht unbedingt in einer Datei schmeißen sondern Hello, World und IAm jeweils in eine eigene cpp und hpp datei schmeißen....

mit macro.add(new Hallo); wird ja die Hallo klasse aufgerufen...
wie könnte ich das aber bewerkstelligen das ein user etwas eingeben kann, und entsprechend die richtige Funktion aufgerufen wird...

ich kann mir vorstellen das ich irgendwo auch noch die Kommandos "verfügbar" machen muss... also irgendwo "registrieren" muss, damit das Programm weiß, ah ja... diese und jenen Kommandos hab ich in meiner liste... also gebe ich das und das aus.. wenn ein falsches Kommando ausgegeben wurde dann geb ich halt ne Fehlermeldung zurück....

Oder bin ich hier doch schon wieder an der falschen stelle?? -.-" ^^
 
wie könnte ich das aber bewerkstelligen das ein user etwas eingeben kann, und entsprechend die richtige Funktion aufgerufen wird...
Dann musst du eine kleine REPL (read eval print loop) und cmd interpreter schreiben. In etwa so (Pseudocode):
Code:
bool quit = false;

function repl(){
   str prompt = "> ";
   while ! quit {
      println(prompt);
      str s = readln();
      bool err = interpret(s);
      if err {
        println ("error message");
      }
   }
}

function interpret(str s) {
   if s == "quit" {
       quit = true;
       return false;
   }
   else if
       ....
       ....
   return true; // error
}
Das ist nur die Struktur, dir fällt bestimmt besserer code ein...
 
Nachtrag: Vermutlich ist es besser deine Kommandos als ENUM zu codieren. Dann kannst du in interpret(s) einen assoziativen Array verwenden um das Kommando zu holen. Dann einen einfachen switch oder ein Index in einen Array. Klar, kommt darauf an auf was du hinaus willst. Aber einfach ist immer gut: weniger Laufzeit, weniger Code, weniger Fehler! :cool:
 
Versuch doch erstmal die grundlegenden Ein- und Ausgabemechanismen zu verstehen.
Tja... wie mir scheint sind wohl doch nicht alle Grundlagen durchgearbeitet worden.....

Dann musst du eine kleine REPL (read eval print loop) und cmd interpreter schreiben. In etwa so (Pseudocode):
Und genau da ist wieder das Problem von der if else if verzweigung...

bei 3 4 commands kann man das ja noch machen....

Aber wenn man 10 Commands benutzen möchte? und im laufe der entwicklung kommen dann noch 10 dazu? und später will mans noch weiter erweitern und dann kommen nochmal 20 dazu?

40 If else if verzweigungen sind da seeehr unübersichtlich :D
 
Und genau da ist wieder das Problem von der if else if verzweigung...
Ach, kein ernsthaftes Problem: Du wirst dem Benutzer kaum 40 oder mehr Kommandos zumuten, und falls doch dass vermutlich in Kommano/Subkommando/Options unterteilen. Ansonsten ist ein Dictionary (assoziativen Array) wie darktrym vorschlägt oder ein switch mit Konstanten/Enums dein Freund. Mach dich nicht vorzeitig verrückt, bring die Sachen doch immer erst einmal zum laufen damit du die Struktur verstehst, optimieren oder reorganisieren kannst du dann immer noch. No premature optimisation...

Ich habe den Eindruck du denkst zu sehr TOP-DOWN, und nicht wie es besser ist (gerade am Anfang) BOTTOM-UP, - kleine, testbare Komponenten die du dann zusammenfügst. Wenn du einen kleinen command interpreter hast (wie immer der aussieht), dann kanst du deine anderen Komponenten leicht test und dann siehst du was funtioniert und was nicht.
 
Du kannst die if else Struktur durch case ersetzen:
hm ich hatte das mit switch case so verstanden das nur integer werte in case funktionieren?
Gut switch case würde natürlich vieles vereinfachen (langfristig aber trotzdem nicht das gelbe vom ei)...
Aber geht das denn wenn ich schreibe:
Code:
switch(cmd) {
case 'foo' :
  foo();
break;
case 'bar' :
  bar();
break;
// 10 weitere cases später
default :
  fooBar();
}

Ich habe den Eindruck du denkst zu sehr TOP-DOWN, und nicht wie es besser ist (gerade am Anfang) BOTTOM-UP
Was genau meinst du damit?
 
Habs jetzt grad mal mit mehreren varianten versucht...
Code:
#include <iostream>
#include <string>
#include <sstream>
#include "MainUtils/MainUtils.hpp"

// Testfunktion als Command interpreter
void testfunk(std::vector<std::string> command) {

   // zu testzwecken mal alles ausgeben
   for (int i = 0; i < command.size(); i++) {  // Warnung: Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlausdrücken Warum? o0
       std::cout << command[i] << std::endl;
     }
   // cin bereinigen
   std::cin.ignore();

   // switch case ..... Compiler Error -> Swicht-Größe ist keine Ganzzahl
   switch (command[0]) {
   case 'test' :   // Compiler Warung: Zeichenkonstante mit mehreren Zeichen
     std::cout << "es wurde test eingegeben" << std::endl;
     break;
   case 'foo' :
     std::cout << "es wurde foo eingegeben" << std::endl;
     break;
   default:
     std::cout << "befehl nicht gefunden" << std::endl;
   }
}

int main() {

    // Eigene utils für pause und co.....
   MainUtils::MainUtils util;


   std::vector<std::string> command(0);
   
   std::cout  << "> ";
   std::string cmd[3];
   std::string line;
   getline(std::cin, line);  // get the entire line
   // parse each string from the line
   std::istringstream stream(line);
  for (int i=0; stream.good(); i++) {
     stream >> cmd[i];
     command.push_back(cmd[i]);
   }
   std::cin.clear();
   std::cin.sync();
   testfunk(command);

   util.pause();
   return 0;
}
Hab die fehler kommentiert....
Funktioniert so also nicht...
auch wenn ich mit google nach switch case string suche... werden die strings immer in einen integer convertiert....
Also die Lösung über ein Dictionary ist aufwändig aber elegant.
Ooooookkkaaayyyy o0 Werd ich mal nach googeln :D
 
Gut switch case würde natürlich vieles vereinfachen (langfristig aber trotzdem nicht das gelbe vom ei)...
Ich denke da liegst du falsch. Switches mit Enums sind sehr effizient da sie von heutigen Compilern gut optimiert werden können. Wenn du sie in einem zentralen Modul in zwei Funktionen packst (z.B. cmd_from_str(const char*), str_from_cmd(Cmd)) dann ist das sehr einfach, flexibel und effizient.

Code untested, from top of my head.
Code:
#include <unordered_map>
#include <stdexcept>

enum class Cmd {
   foo, bar, baz
}

typedef std::unordered_map<const char*, Cmd> CmdMap;

CmdMap map = {{"foo", Cmd::foo},{"bar",Cmd::bar},{"baz",Cmd::baz}};

try {
   // input cmd von argv oder wo immer holen.
   Cmd cmd = map.at("cmdstr");

   switch(cmd) {
      case Cmd::foo : ... break;
      case Cmd::bar : ... break;
      case Cmd::baz : ... break;
   };
}
catch (const std::out_of_range& err) {...}
Natürlich könntest du deine FuntionsPtr auch direkt in eine Map stecken, ich halte aber ein solches Design für flexibler. Wie immer: das kann man auch anders sehen.
Was genau meinst du damit?
Ich will da gar nicht argumentieren, ich habe einfach den Eindruck dass du zu weit vorausdenkst und zu schnell eine Lösung für dein spezielles Vorhaben haben willst. InOut ist ein notorisch schwieriges Thema und damit sollt man sich auseinandersetzen bevor man in die Tiefen geht. Die Lisp-Leute sagen das in jedem ernsthaften Programm ein kleiner Lisp Interpreter steckt, und da haben sie recht.:)
 
Ich muss mir enum glaub ich nochmal vorknöpfen....
So ergibt das ganze natürlich sinn....
Auch wenn ichs persönlich jetzt vermutlich ehr mit nem vector als string gelöst hätte o0
Aber ich probier das mal aus....

PS: die string2int variante hab ich grad umgesetzt...
das dumme daran ist... ich hab aktuell 9 Kommands (voraussichtlich folgen da noch 3 4 stück) ... das heißt ich muss mir erstmal den int wert von "foo" ausgeben lassen um den dann im case abzurufen... sieht extrem dumm aus und ohne kommentare ist man da echt aufgeschmissen.... sieht dann so aus:

Code:
unsigned int string2int(const char* str, int h = 0)
{
   return !str[h] ? 5381 : (string2int(str, h + 1) * 33) ^ str[h];
}

int main() {
// abgeschnitten
switch (string2int(command[0].c_str())) {
   case /* test */ 2087933171 :
     std::cout << "es wurde test eingegeben" << std::endl;
     break;
   case /* foo */ 193420387 :
     std::cout << "es wurde foo eingegeben" << std::endl;
     break;
   default:
     std::cout << "befehl nicht gefunden" << std::endl;
   }
}
 
Dein string2int ist wirklich unglücklich, da die Werte unverständlich sind, enums sind da weit besser. Der Vorzug von C++ ist u.A. dass du HL (Heigh Level, Klassen etc) gut mit LL mischen kanst und volle Kontrolle über das Laufzeitverhalten hast. Gerade am Anfang neigt man dazu alles HL zu machen, aber mit der Zeit wird das immer weniger da dann oft das Laufzeitverhalten schlecht ist. Wichtig ist z.B. ob du die Sachen statisch oder auf dem Stack allokieren kannst, oder new verwenden must. New und virtuelle Funktionen erfordern immer eine Redirektion was hinsichtlich Laufzeitverhalten (wg. Prozessor Catch) nicht so gut ist, Konstanten sind diesbzgl. immer gut. Und bei C++ geht es immer auch um gutes Laufzeitverhalten, sonst könnte man auch eine andere HL Sprache verwenden die produktiver ist.

Noch etwas: Verstänlichkeit, d.h. auch treffende Namen (und nicht Macro wie oben), ist wichtig um den Überblick bei Veränderungen zu behalten, und die kommen sicherlich. Na ja, wird schon! :)
 
JUCHUUU ^^ Hab das mit den Enums und Switch Case ans laufen bekommen :D

So hab ichs jetzt mal testweise gelöst....
Code:
#include <iostream>
#include <string>
#include <sstream>
#include <unordered_map>
#include <exception>

// Soll nur testweise rest des cmd Arrays ausgeben das der Benutzer eingegeben hat... zb start foo bar ausgabe foo bar....
class DoSomth {
public:
    // Soll nur testweise rest des cmd Arrays ausgeben das der Benutzer eingegeben hat... zb start foo bar ausgabe foo bar....
   void make_dummtuech(std::string args[]){
     std::cout << args[1] << " " << args[2] << std::endl;
   }
};

class CommandBuilder {
public:
    //Ein paar beispiel "commands"
   enum Command {
     ON,
     OFF,
     RESET,
     START
   };
   // unordered map mit strings nicht chars
   typedef std::unordered_map<std::string, Command> CommandMap;

   //Die map mit den commands füllen
   CommandMap cmdmap = {
         {"off", Command::OFF},
         {"on", Command::ON},
         {"reset", Command::RESET},
         {"start", Command::START}
     };

// der "command listener" erwartet die eingabe des benutzers....
void listen() {

   std::cout  << "> ";
   std::string cmd[3];
   std::string line;
   getline(std::cin, line);
   std::istringstream stream(line);
  for (int i=0; stream.good(); i++) {
  stream >> cmd[i];
  }
  std::cin.clear();
  std::cin.sync();
   execute(cmd);
}

private:
//Führt das Command aus 
void execute(std::string command[]) {
   try {
       Command cmd = cmdmap.at(command[0]);
       switch (cmd) {
       //Fall through
       case Command::ON :
       case Command::START:
         std::cout << "es wurde on oder start eingegeben" << std::endl;
         DoSomth a;
         a.make_dummtuech(command);
         break;
       case Command::RESET:
         std::cout << "Es wurde reset eingegeben" << std::endl;
         break;
       case Command::OFF:
         std::cout << "Es wurde off eingegeben" << std::endl;
         break;
       default: /* wird gar nicht ausgeführt da er in eine exception läuft siehe Catch */
         std::cout << "command nicht gefunden" << std::endl;
         break;
       };
     } catch (const std::exception& e) {
        // Liefert in der regel _Map_Base::at zurück irgend wie logisch da zb foobarblub nicht in der map vorhanden ist
       std::cout << "Der Befehl wurde leider nicht Gefunden! Errorcode: " << e.what() << std::endl;
     }
     //bereinigen des input streams
     std::cin.ignore();
     
    // zurück in den listener springen
    listen();
}
};



int main(void) {

   CommandBuilder cmd;

   cmd.listen();

   return 0;
}

Ist vielleicht immer noch nicht die sauberste lösung, aber immer noch sauberer als 10 if else if blöcke zu verwenden.....
Da lob ich mir schon irgendwie Java mit seinen Interfaces xD
 
Warum so kompliziert? Ich denke das kommt von dem Java-Unsinn jede Bagatelle als eine Klasse zu implementieren. Außerdem, woher weißt du dass du die Funktionalität nur in Main brauchst? Was ist z.b, mit Strings die du irgendwo anders her bekommst (TCP/IP, Konfiguration, ...) ? C++ ist eben nicht (!) Java.

Bei mir sähe das etwa folgendermaßen aus:
Code:
///// cmd.hpp /////

enum class Cmd { Error,  ... };

const char * str_from_cmd(Cmd cmd);
Cmd  cmd_from_str(const char*);
// evtl. weitere Funktionen/Klassen die mit Kommandos zusammenhängen.

///// cmd.cpp /////

#include <unordered_map> // steht nicht im Header!
#include <exception>

typedef std::unordered_map<const char*, Cmd> CommandMap;

static  CommandMap cmdmap = { ... }; // static!

const char * str_from_cmd(Cmd cmd){....}
Cmd  cmd_from_str(const char*) {...}

Dann kannst du es in Main oder wo auch immer verwenden, z.B. auch in irgendeiner einer Klasse wenn das angezeigt ist. Natürlich kann man das auch noch anders machen, z.B. inline verwenden, die Exception auswerfen und einiges mehr. KISS: keep ist stupid simple, in C++ kann man das! :)
 
Ignoriert mich besser, aber: Ein assoziativer Container ist lediglich für simple Implementierungen geeignet. Sobald (performante) Autovervollständig von Haupt-, Unterkommando und Optionen ins Spiel kommt, landet man schnell bei den guten Rot-Schwarz-Bäumen. :)
 
Ist vielleicht immer noch nicht die sauberste lösung, aber immer noch sauberer als 10 if else if blöcke zu verwenden
Wenn du den Code ordentlich formatierst, ist das if-else sogar übersichtlicher. Letztendlich gibst du den Kommandos nur eine numerische Identität, die du dann im case-Block auswertest. Was genau hast du denn davon? Du denkst viel zu kompliziert.

Rob
 
Was genau hast du denn davon?
Uff...
Ich versuchs mal so zu erklären das es Sinn ergibt...
Vorweg... ich weiß... VIIIEEEL zu hoch gesteckt das Ziel.... jedenfalls für nen Anfänger...

Also ich will mir ein Programm schreiben, das "dynamisch" Minecraft Server in einer Screensession startet.
Dazu will ich im Programm unterschiedliche "Gruppen" erstellen.... Diese sollen vom Benutzer auch definiert werden können...
Das Ganze Landet dann in einer sqlite Datenbank (später aktuell bin ich noch am grundkonstruckt)

Das Programm soll dann mittels Kommandos die einzelnen screensessions steuern....
z.b. stop proxy -> stopt erst den proxy server und beendet dann die screen session
stop lobby 1 -> stoppt erst Lobby-1 und beendet dann die screen session (deswegen übrigens auch das Array mit einer größe von 3)
start lobby 5 -> startet nach einander 5 screensessions und 5 Lobby-Server
stop GRUPPEN_NAME 1 -> stoppt GRUPPEN_NAME-1

Usw...
Dazu benötige ich halt folgende Kommandos....
start -> startet einen prozess
stop -> stopt den prozess
create group -> erstellt eine neue Gruppe
modify group -> bearbeitet eine vorhandene gruppe
delete group -> löscht die gruppe
compile spigot -> kompiliert eine neue (andere) Minecraft server version (zb compile spigot 1.11.2)
exit || quit || shutdown || end || stop all -> beendet alle laufenden Prozesse und beendet das programm
install -> legt sqlite datenbank an und füllt sie mit default werten
help -> zeigt kurze hilfe übersicht an

Es kann natürlich auch sein das mir im laufe der zeit noch weitere Funktionen einfallen die man mittels Commands steuern soll/kann...
Daher war die idee eines Design Patterns halt, damit ich jedes Kommando in einer eigenen cpp Datei auslagern kann....
zb start.cpp, stop.cpp create_group.cpp (oder allgemein group.cpp?) usw usf....

Ich weiß das das Ziel extrem hoch gesteckt ist für einen Anfänger....
Die meisten Sachen Funktionieren aber auch schon ;) nur die Command Geschichte und sqlite fehlt noch...
wobei sqlite als Entwurf auch schon bereit liegt....

Später soll das ganze noch weiter ausgebaut werden (aber erst viiiieeeellllll später) als Client <-> Server Anwendung
Sprich:
Server Managed Proxy und verwaltet die Clients... (start stop create group "signale" an client senden)
Clients verwalten die Gruppen sollen aber auf sqlite Inhalte von Server zugreifen können bzw Server schickt sqlite Inhalte an Client
Warum?
Weil man evtl sein Netzwerk ausweiten möchte und dazu einen zweiten dirtten vierten usw Dedizierten Server anmietet...
Dort braucht man sowas wie den Proxy Server aber nicht.... Außerdem wärs kompfortabler sich nur auf einer konsole anzumelden und von dort aus das gesamte Netzwerk steuern zu können....

Und im nächsten Schritt war die Überlegung ne QT Anwendung zu basteln die dann auf Server zugreift um von dort aus halt das ganze zu "verwalten"

Aber..... da bin ich definitiv noch weit weit weit und noch viel weiter von weg.....
landet man schnell bei den guten Rot-Schwarz-Bäumen.
? Was meinst du?
Außerdem, woher weißt du dass du die Funktionalität nur in Main brauchst?
War nur ein beispiel bzw entwurf... das ganze würd ich natürlich in header und cpp auslagern und dann über die main bzw die teile des codes aufrufen, wo ich sie brauche..
Problem war bis heute morgen halt noch, das es gar nicht erst funktioniert hat... ^^
Das tuts jetzt aber ... muss es nur noch in meinem aktuellen Programm "implementieren".....

Muss aber auch dazu sagen... so viel hocke ich da auch nicht vor ^^
Ich mach halt immer mal hier ne halbe stunde da mal 10 minuten.... ;)
Cmd cmd_from_str(const char*);
Ernstgemeinte frage.... warum eigentlich immer char? bin ich so sehr php und java verwönt? MUSS ich mir das umgewöhnen in chars zu denken?
Strings sind meiner meinung nach viel schöner :D
#include <unordered_map> // steht nicht im Header!
Warum den header nicht in cmd.hpp, und cmd.hpp in cmd.cpp?
 
Ich hätte meine Klappe halten sollen... Ich meinte, dass man mit assoziativen Containern zwar schnell eine funktionierende Eingabezeile implementieren kann, man aber für weitergehende Funktionen wie Vervollständigung der Kommandos, selektive History und so weiter eine andere, effizienter durchsuchbare Datenstruktur benötigt. Wenn du einen assoziativen Container hast, musst du (im einfachen Fall) die Nutzereingabe mit jedem Kommando vergleichen, bis du das passende Kommando gefunden hast. Das ist sehr teuer und entsprechend langsam, wird schon bei wenigen Kommandos zum Problem. Daher nimmt man für solche Zwecke oft Suchbäume, vor allem AVL-Bäume oder eben die vom Laufzeitverhalten recht ähnlichen Rot-Schwarz-Bäume.

Aber das sind beides keine Datenstrukturen, die man als Anfänger mal so nebenbei versteht, geschweige denn korrekt in effizienten Code gegossen bekommt. Sie dürften eher verwirren. Daher: Ignoriert mich einfach. :)
 
Vorab: Die Lösungen von KobRheTilla und Yamagi sind natürlich auch möglich und möglicherweise besser, es kommt eben immer darauf an wie man die jeweilige Funktionalität ins Gesammt-Konzept einordnet. Ich selbst mache mit der Cmd-Line meist nicht viel rum und benutze If-Then-Else, meist in getrennten Funktionen für Kommandos und Optionen. Gut formatiert natürlich. :) In deinem Falle war mir vor allem wichtig dich auf Enums aufmerksam zu machen. Class Enum ist ein echter Fortschritt von C++11, unterstützt die Optimierungsmöglichkeiten des Kompilers und ermöglicht bessere Fehlermeldungen.
Ernstgemeinte frage.... warum eigentlich immer char? bin ich so sehr php und java verwönt? MUSS ich mir das umgewöhnen in chars zu denken?
Strings sind meiner meinung nach viel schöner :D
Das kannst du machen wie du willst. Strings sind schöner, aber oft unnötiger Overhead. Von Strings kannst du leicht einen char* bekommen, umgekehrt koste Laufzeit. Strings sind (meist) eine vergleichsweise teuere Angelegenheit. Ich neige außerdem zu einem gewissen Minimalismus und hatte hier zunächst dein Main im Kopf.
Warum den header nicht in cmd.hpp, und cmd.hpp in cmd.cpp?
Sorry, mein Fehler. Ist natürlich richtig dass du cmd.hpp mit inkludieren musst. Was ich mit der Anmerkung deutlich machen wollte ist dass man in der Header-Datei nur das inkludieren sollte was man dort (!) auch braucht. Und in diesem Besispiel-Fall braucht man unordered_map und exception nur im Source-Code. Der Punkt ist dass die Kompilierzeiten und Unübersichtlichkeit dadurch minimiert werden und die Wiederverwendbarkeit in anderen Projekten verbessert wird.

PS: Von Java und PHP ist man nicht verwöhnt, man hat nur schlechte Angewohnheiten! ;)
 
PS: Von Java und PHP ist man nicht verwöhnt, man hat nur schlechte Angewohnheiten!
Sag ich doch... verwöhnt xD (verwöhnte göhre) ^^
Was ich mit der Anmerkung deutlich machen wollte ist dass man in der Header-Datei nur das inkludieren sollte was man dort (!) auch braucht.
Okaaayyyyy.... dann hab ich das etweder falsch verstanden gehabt oder man hats mir falsch erklärt xD
Also mal eben alle hpp's umstricken xD
 
Zurück
Oben