Proste Menu MVC - konstruktywne opinie

0

Witam,

Postawiłem sobie za cel napisanie ładnego kodu aplikacji konsolowej, posiadającego menu z trzema opcjami, z których dwie wchodzą do podmenu a trzecia wyłącza program. Każde z podmenu ma dwie opcje: przykładowa akcja i wstecz.
Chciałem zastosować wzorzec MVC jednak nie jestem pewien czy do końca wyszło to tak jak powinno pod względem ideowym, ponieważ nie wszystko zrobiłem dokładnie tak jak znalazłem w opisie wzorca. W internecie ciężko znaleźć jakikolwiek przykład w C++ pod konsolę. Kod, który wkleiłem kompiluje się i działa poprawnie (informacja dla tych co omijają wywody autora znajdujące się powyżej kodu).

Chciałbym, żeby ktoś w wolnej chwili skrytykował mi ten kod tj.:

  • np. napisał coś w stylu: to nie jest wzorzec MVC, powinieneś raczej... (tutaj opis co robię źle i jak to powinno wyglądać)
  • co mogłoby być lepiej/inaczej
  • odpowiedź na pytanie, czy poniższy kod dałoby się przerobić bez większych problemów (tj. dopisanie tylko odpowiedniej klasy widoku) na aplikację okienkową.

Za poświęcony czas z góry dziękuję :)

Kod jest w jednym pliku, jednak myślę, że jest dość czytelny

#include <vector>
#include <queue>
#include <string>
#include <iostream>
#include <sstream>

using namespace std;

class View;
class Controller;
class Model;

class View
{
public:
  virtual void show()=0;
  Controller* getController() { return controller; }
  virtual void setController(Controller* controller) { this->controller = controller; }
  virtual ~View() {}
private:
  Controller* controller;
};

typedef int ActionType;
class Controller
{
public:
  typedef queue<ActionType> Actions;
  Controller() {}
  void userAction(ActionType actionType) { actions.push(actionType); }
  void setModel(Model* model) { this->model = model; }
  Model* getModel() { return model; }
  bool processActions()
    {
      if (actions.empty())
        return false;
      ActionType action = actions.front();
      actions.pop();
      processAction(action);
      return true;
    }
  virtual void processAction(ActionType action)=0;
  virtual ~Controller() {}
private:
  Model* model;
  Actions actions;
};

class Model
{
public:
  virtual void setView(View* view)
    {
      this->view = view;
      view->setController(controller);
    }
  virtual void setController(Controller* controller)
    {
      this->controller = controller;
      controller->setModel(this);
      if (view)
        view->setController(controller);
    }
  Controller* getController() { return controller; }
  View* getView() { return view; }
  virtual void displayView() { view->show(); }
  Model(): view(NULL), controller(NULL) {}
  virtual ~Model() {}
private:
  View* view;
  Controller* controller;
};

class ConsoleMenuItem: public View
{
public:
  virtual void show()=0;
  virtual ActionType getActionType()=0;
  void notify() { getController()->userAction(getActionType()); }
};

class ConsoleMenuCaption: public ConsoleMenuItem
{
public:
  ConsoleMenuCaption(const string& caption, ActionType actionType): caption(caption), actionType(actionType) {}
  void show() { cout << caption; }
  ActionType getActionType() { return actionType; }
private:
  string caption;
  ActionType actionType;
};

class ConsoleMenuView: public View
{
public:
  typedef vector<ConsoleMenuItem*> Items;
  void addItem(ConsoleMenuItem* v)
    {
      items.push_back(v);
      v->setController(getController());
    }
  void setController(Controller* controller)
    {
      View::setController(controller);
      for (auto it : items)
        it->setController(getController());
    }
  ConsoleMenuItem* getItem(int index)   { return items[index]; }
  void removeItem(int index) { items.erase(items.begin()+index); }
  void show()
    {
      unsigned int n=1;
      for (int i=0; i<25; i++) cout << "\n";
      for(auto it : items)
        {
          cout << n++ << ". ";
          it->show();
          cout << "\n";
        }
      cout.flush();
      string line;
      do
        {
          getline(cin, line);
          istringstream ss(line);
          n = 0;
          ss >> n;
          n--;
        }
      while (n>=items.size());
      getItem(n)->notify();
    }
private:
  Items items;
};

/********************************************************/

class MainMenu
{
public:
  static const ActionType OPTIONQUIT,OPTION1,OPTION2;
  View* getView() { return view; }
  MainMenu()
    {
      view = new ConsoleMenuView;
      quit = new ConsoleMenuCaption("Quit",OPTIONQUIT);
      op1 = new ConsoleMenuCaption("Menu 1",OPTION1);
      op2 = new ConsoleMenuCaption("Menu 2",OPTION2);
      view->addItem(op1);
      view->addItem(op2);
      view->addItem(quit);
    }
  ~MainMenu()
    {
      delete view;
      delete op1;
      delete op2;
      delete quit;
    }
private:
  ConsoleMenuView *view;
  ConsoleMenuCaption *op1,*op2,*quit;
  const MainMenu& operator= (const MainMenu&) { return *this; }
  MainMenu(const MainMenu&) {}
};


class SubMenu1
{
public:
  static const ActionType OPTION1,OPTION2;
  View* getView() { return view; }
  SubMenu1()
    {
      view = new ConsoleMenuView;
      op1 = new ConsoleMenuCaption("Cos 1",OPTION1);
      op2 = new ConsoleMenuCaption("wstecz",OPTION2);
      view->addItem(op1);
      view->addItem(op2);
    }
  ~SubMenu1()
    {
      delete view;
      delete op1;
      delete op2;
    }
private:
  ConsoleMenuView *view;
  ConsoleMenuCaption *op1,*op2;
  const SubMenu1& operator= (const SubMenu1&) { return *this; }
  SubMenu1(const SubMenu1&) {}
};

class SubMenu2
{
public:
  static const ActionType OPTION1,OPTION2;
  View* getView() { return view; }
  SubMenu2()
    {
      view = new ConsoleMenuView;
      op1 = new ConsoleMenuCaption("Bla bla 1",OPTION1);
      op2 = new ConsoleMenuCaption("wstecz",OPTION2);
      view->addItem(op1);
      view->addItem(op2);
    }
  ~SubMenu2()
    {
      delete view;
      delete op1;
      delete op2;
    }
private:
  ConsoleMenuView *view;
  ConsoleMenuCaption *op1,*op2;
  const SubMenu2& operator= (const SubMenu2&) { return *this; }
  SubMenu2(const SubMenu2&) {}
};

const ActionType MainMenu::OPTIONQUIT = 0;
const ActionType MainMenu::OPTION1    = 1;
const ActionType MainMenu::OPTION2    = 2;
const ActionType SubMenu1::OPTION1    = 3;
const ActionType SubMenu1::OPTION2    = 4;
const ActionType SubMenu2::OPTION1    = 5;
const ActionType SubMenu2::OPTION2    = 6;

class Application;

class AppController: public Controller
{
public:
  AppController(Application* app): app(app) {}
  void processAction(ActionType action);
private:
  Application* app;
};

class Application
{
public:
  MainMenu mainMenu;
  SubMenu1 subMenu1;
  SubMenu2 subMenu2;
  
  void setView(View* view) { model->setView(view); }
  void display() { model->displayView(); }
  void run() { display(); }

  Application()
    {
      model = new Model;
      controller = new AppController(this);
      model->setController(controller);
      model->setView(mainMenu.getView());
    }
  ~Application()
    {
      delete controller;
      delete model;
    }
  bool running()
    {
      return controller->processActions();
    }
private:
  const Application& operator= (const Application&) { return *this; }
  Application(const Application&) {}

  Controller* controller;
  Model* model;
};

void AppController::processAction(ActionType action)
  {
    switch (action)
      {
        case MainMenu::OPTION1:
          {
            app->setView(app->subMenu1.getView());
            break;
          }
        case MainMenu::OPTION2:
          {
            app->setView(app->subMenu2.getView());
            break;
          }
        case SubMenu1::OPTION2:
        case SubMenu2::OPTION2:
          {
            app->setView(app->mainMenu.getView());
            break;
          }
        case SubMenu1::OPTION1:
          {
            cout << "Jakas akcja 1. Nacisnij ENTER..." << endl;
            string foo;
            getline(cin,foo);
            break;
          }
        case SubMenu2::OPTION1:
          {
            cout << "Jakas akcja 2. Nacisnij ENTER..." << endl;
            string foo;
            getline(cin,foo);
            break;
          }
      }
    if (action!=MainMenu::OPTIONQUIT)
      app->display();
  }

int main()
{
  Application app;
  app.run();
  while (app.running());
  return 0;
}
0

Nie jestem w tym specialista (bardziej siedze w konsoli niz w PHPie), dlatego moge sie mylic:

  1. W MVC nie masz sztywnego polaczenia miedzy warstwami (np. na pewno nie jest tak ze controller ma przypisany jakis konkretny model).
  2. Model na pewno do niczego sie nie odwoluje (to jest warstwa danych, ktora ew. moze cos sygnalizowac ale nie przez dowiazania typu setController) – to musza byc luzne powiazania (sygnaly lub inne).
  3. ActionType powinien byc zamieniony na ActionCode (type oznacza krotki zestaw typow, code – tyle ile dusza zapragnie)
  4. W ConsoleMenuView – jesli masz getItem(int) to musisz dodac tez getItemCount() lub cos podobnego
  5. Destructor MainMenu zawiera kod ktory powinien sie wykonywac automatycznie (np. poprzez rejestracje elementow menu w jakiejs kolekcji)

1 użytkowników online, w tym zalogowanych: 0, gości: 1