Robię klasę menu w C++ (kod poniżej). Jakie rozwiązanie jest lepsze, oczekiwanie od użytkownika pisanej przeze mnie biblioteki podania na starcie oczekiwanego interfejsu (np. używając list inicjalizacyjnych) w konstruktorze klasy menu czy dodanie do klasy menu metod add_item, remove_item?
Co sądzicie o stworzeniu osobnej klasy menu_item_list służącej do budowania interfejsu z metodami odpowiednio push_back, pop_back i używaniu jej w klasie menu do odwoływania się do elementów menu? Czy jak tworzę klasy będące kontenerami np. menu_item_list czy checkbox_group to dobrze jest tworzone w nich metody nazywać jak w klasach z STL push_back, pop_back, push_front, pop_front itd.?
Jakbym tworzył klasę menu_item_list to chętnie dodałbym metody typu add_submenu. Jak już tworzyć klasę menu_item_list to lepiej nazwać jej metody push_back, pop_back itd. czy lepsze będą nazwy add_item, remove_item, add_submenu?
Mój kod
~/programy_cpp/linked_list ❯ cat main.cpp 16:32:57
#include <iostream>
/*#include <string>*/
/*#include "linked_list.h"*/
#include "menu.h"
void display_sth() {
std::cout << "test\n";
}
int main() {
menu_items main_menu_items;
main_menu_items.push_back(menu_item{"test", menu_item::noop});
main_menu_items.push_back(menu_item{"display_sth", display_sth});
menu submenu{"Submenu", {{"test submenu", menu_item::noop}}};
main_menu_items.push_back(menu_item{"Submenu", invoke_menu_loop(submenu)});
menu main_menu{"Main menu", main_menu_items};
main_menu.loop();
return 0;
}
~/programy_cpp/linked_list ❯ cat menu.h 16:39:44
#ifndef MENU_H
#define MENU_H
#include <iostream>
#include <string>
#include <vector>
#include <functional>
using menu_item_func = std::function<void()>;
void print_title(const std::string& title);
struct menu_item {
menu_item(const std::string& title_val) : title{title_val}, action{noop} {}
menu_item(const std::string& title_val, menu_item_func action_val)
: title{title_val}, action{action_val} {}
menu_item(const menu_item& item) : title{item.title}, action{item.action} {}
friend std::ostream& operator<<(std::ostream& lhs, const menu_item& rhs);
static void noop() { std::cout << "No action defined!\n"; }
std::string title{};
menu_item_func action{};
};
using menu_items = std::vector<menu_item>;
class menu {
public:
menu(const std::string& title_val);
menu(const std::string& title_val, const std::vector<menu_item>& items_val);
menu(const menu& menu_val);
void loop();
private:
void print_choices() const;
char read_choice() const;
static int nesting_level;
bool active{false};
std::string title{};
std::vector<menu_item> items{};
};
std::function<void()> invoke_menu_loop(menu& the_menu);
#endif // MENU_H
~/programy_cpp/linked_list ❯ cat menu.cpp 16:39:48
#include "menu.h"
#include <limits>
int menu::nesting_level = 0;
void print_title(const std::string& title) {
std::size_t title_len = title.size();
std::size_t width = title_len + 4;
for (int i = 0; i < width; ++i) {
std::cout << "*";
}
std::cout << "\n* " << title << " *\n";
for (int i = 0; i < width; ++i) {
std::cout << "*";
}
std::cout << std::endl;
}
std::ostream& operator<<(std::ostream& lhs, const menu_item& rhs) {
return lhs << rhs.title;
}
menu::menu(const std::string& title_val) : title{title_val} {
}
menu::menu(const std::string& title_val, const std::vector<menu_item>& items_val)
: title{title_val}, items{items_val} {
}
menu::menu(const menu& menu_val) {
for (const menu_item& item : menu_val.items) {
items.push_back(item);
}
}
void menu::print_choices() const {
for (int i = 0; i < items.size(); ++i) {
std::cout << i << ") " << items[i] << std::endl;
}
std::cout << "q) ";
if (nesting_level > 1)
std::cout << "Back";
else
std::cout << "Quit";
std::cout << std::endl;
}
char menu::read_choice() const {
char choice;
bool valid{false};
std::cout << "What do you want to do?\n";
do {
print_choices();
std::cout << "Your choice: ";
std::cin >> choice;
if ((choice - '0') < 0 || ((choice - '0') >= items.size()) && (choice != 'q')) {
std::cout << "Invalid choice!\n";
} else {
valid = true;
}
} while (!valid);
return choice;
}
void menu::loop() {
active = true;
++nesting_level;
while (active) {
print_title(title);
char choice = read_choice();
if (choice == 'q') {
--nesting_level;
active = false;
} else {
// call function bound to menu item
items[choice - '0'].action();
// discard input and wait until user press the enter key
std::cout << "Press enter to continue...\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
}
}
}
std::function<void()> invoke_menu_loop(menu& the_menu) {
return [&]() { the_menu.loop(); };
}