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

Interfejs klasy menu w C++

Konkurs Mistrz Programowania
0 głosów
341 wizyt
pytanie zadane 23 lutego 2025 w C i C++ przez whiteman808 Mądrala (5,470 p.)
edycja 23 lutego 2025 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 2025 przez mokrowski Mędrzec (158,940 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ź 746 wizyt
pytanie zadane 7 marca 2025 w Nasze projekty przez whiteman808 Mądrala (5,470 p.)
0 głosów
1 odpowiedź 580 wizyt
0 głosów
1 odpowiedź 719 wizyt

93,652 zapytań

142,573 odpowiedzi

323,088 komentarzy

63,165 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

Kursy INF.02 i INF.03
...