Projektowanie biblioteki w C++ - interfejs menu

Projektowanie biblioteki w C++ - interfejs menu
whiteman808
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 201
0

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

Kopiuj
~/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(); };
}
RE
  • Rejestracja: dni
  • Ostatnio: dni
1

Zacznijmy od organizacji projetku, są różne ale to co opisujesz to większy kawałek całości.
Dzielisz projekt na domeny np. GUI, Network, IO itp.
Każdy z nich budujemy jako bibliotekę. A później linkujemy do głównej aplikacji. Dzięki tem masz podział aplikacji, krótszy czas budowania przy zmianach.
Jak ma wyglądać api takiego modułu? no cóż to zależy po prostu co potrzebujesz.

  1. Najprostszy przypadek gdzie struktura jest predefiniowana a ja wywołuje tylko display z modułu gui i przekazuje mu później z cin wartości.
  2. Domena która na podstawie przekazanych danych konstruktorze(lub innych metodach) buduje GUI.
  3. Poczytaj o evnet loop lub też program loop
    Generalnie poczytaj o tym i o cmake
    Przykład podziąłu apki z chata
    project_root/
    │── CMakeLists.txt
    │── src/
    │ │── main.cpp
    │ │── CMakeLists.txt
    │ │── gui/
    │ │ │── gui.h
    │ │ │── gui.cpp
    │ │ │── CMakeLists.txt
    │ │── network/
    │ │ │── network.h
    │ │ │── network.cpp
    │ │ │── CMakeLists.txt
    │ │── io/
    │ │── io.h
    │ │── io.cpp
    │ │── CMakeLists.txt
    │── build/ (katalog dla plików build - tworzony przez CMake)
    │── README.md
K1
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 55
0
whiteman808 napisał(a):

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?

Trochę zależy od gustu, jak chcesz tego używać. Ręczne dodawanie poszczególnych elementów daje trochę większą swobodę i możliwości, chociaż może wymagać więcej pisania.
Są jeszcze inne opcje, np. wczytywanie z pliku, albo tworzenie funkcji inicjalizacyjnych (chociaż to w zasadzie byłby dodatek do któregoś z powyższych)

MS
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 33
0

spoko wyglada

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.