• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Interfejs klasy menu w C++

0 głosów
128 wizyt
pytanie zadane 23 lutego w C i C++ przez whiteman808 Gaduła (4,660 p.)
edycja 23 lutego przez whiteman808

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(); };
}

1 odpowiedź

0 głosów
odpowiedź 27 lutego przez mokrowski Mędrzec (158,580 p.)
Do budowania struktur drzewiastych (np. menu, hierarchicznych ustawień dla aplikacji, kontenerów zagnieżdżonych itp...), służy wzorzec projektowy Kompozytu (ang. Composite). To jeden z klasycznych 23 wzorców które warto znać, jeśli programujesz bardziej zaawansowane aplikacje. Tu masz przystępny opis jak działa: https://refactoring.guru/design-patterns/composite , tu inne jego użycie (bo to wzorzec) : https://www.geeksforgeeks.org/composite-pattern-cpp/ , tu codereview menu opartego na tym wzorcu: https://codereview.stackexchange.com/questions/166792/menu-that-utilizes-the-composite-design-pattern

To jest absolutny klasyk dotyczący wzorców: https://www.goodreads.com/book/show/85009.Design_Patterns?from_search=true&from_srp=true&qid=yBEB1cJJBB&rank=1

Możesz jeszcze być zainteresowany tym: https://www.goodreads.com/book/show/44292432-hands-on-design-patterns-with-c?from_search=true&from_srp=true&qid=9owh7aINGn&rank=1

Pamiętaj że to wzorzec to tylko "sznyt twojego kodu" i wymaga dostosowania do własnych potrzeb. Ale wzorce naprawdę warto znać. Pierwsza książka dumnie (w wersji papierowej) rezyduje w mojej biblioteczce :)

Podobne pytania

0 głosów
1 odpowiedź 291 wizyt
pytanie zadane 7 marca w Nasze projekty przez whiteman808 Gaduła (4,660 p.)
0 głosów
1 odpowiedź 420 wizyt
0 głosów
1 odpowiedź 589 wizyt

93,427 zapytań

142,421 odpowiedzi

322,649 komentarzy

62,787 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

VMware Cloud PRO - przenieś swoją infrastrukturę IT do chmury
...