构建控制台菜单类层次结构的最佳方法是什么?

6
我已经在C++和Python中创建了一个控制台菜单,但我认为语言并不太重要,因为我正在询问类的结构。
所以我尝试实现的是一个类似于MS-DOS的菜单,其中我可以有父菜单(文件夹)和动作菜单(文件)。这是在控制台中打印出来的样子:
[-] Root directory
        Open snakegame
    [-] First sub directory
            Print out stupid messages
        [+] Closed directory in open directory
            Shutdown computer
    [+] These directories are closed
    [+] You can't see the content inside them
        Quit menu

所以你可以看到,我这里有两种类型的菜单;
  1. 目录(MS-DOS文件夹)菜单,其中包含其他菜单。当激活时,它们会打开/关闭。例如:现在“根目录”已经打开,您可以看到其中所有的菜单。如果关闭,则“[-]”变为“[+]”,您将无法看到任何其他菜单。
  2. 动作(MS-DOS文件)菜单,与一个函数相关联。当激活时,它们调用它们所链接的函数。例如:“打开贪吃蛇游戏”可以链接到一个名为“startSnakeGame()”的函数,该函数将关闭菜单并启动贪吃蛇游戏。

我已经编写了两个可工作的实现来获得想要的结果,我只是想知道,我应该使用哪一个?第一种方式是,我只有一个称为“Menu”的类,并且它具有所有成员变量和方法编码到一个类中。另一种方式是,我将这两种不同类型的菜单分别分为两个类,并具有共同的基类。


这里有一些成员变量,我现在将它们分为三个部分(基类、目录类和动作类),但它们可以合并成一个类。

基本菜单:

  • parent = 一个菜单(目录),将this/self作为子项存储在列表/向量中。
  • label = 显示菜单时显示的标签。
  • selected = 布尔值,指示菜单当前是否被选中(例如由鼠标指向)。

目录菜单:

  • subMenus = 一个列表或向量(在C++中),其中包含其他菜单。
  • open = 布尔值,指示菜单是打开还是关闭。

动作菜单:

  • action = 指针,指向激活此菜单时调用的函数。

正如您所看到的,只有少数变量与其他类不同,如果设置action == 0(没有动作),则菜单会自动更改open以依赖于其当前值的false/true。这样,动作菜单将被终止,唯一的缺点是动作菜单将保留subMenusclosed而无用。


这可能只是一个人的观点,但我已经思考了一段时间,无法找到一种方法比另一种优越,它们都有其优势和缺点,并且都能很好地工作。因此,我想问一下您的意见,如果有人选择其中一种方式,请告诉我原因。基本上,我正在寻求原因,不关心您的意见。
除文件夹和文件之外,将没有其他菜单类型,因此基类不能用于其他任何内容。
编辑:如何使用菜单的简单Python和C++示例:
只有一个类的Python:
# Using default param. here to set "action = None" or "action = toggleOpen()"
root = Menu(None, "Root directory")
snake = Menu(root, "Open snakegame", startSnakeGame)
sub1 = Menu(root, "First sub directory")
printMsg = Menu(sub1, "Print out stupid messages")
...

Python多类使用:

# With multiple classes, action parameter no longer exists
root = DirectoryMenu(None, "Root directory")
snake = ActionMenu(root, "Open snakegame", startSnakeGame)
...

一类C++:

Menu* root = new Menu(0, "Root directory");
Menu* snake = new Menu(&root, "Open snakegame", &startSnakeGame);
...

使用多个类的C++:

DirectoryMenu* root = new DirectoryMenu(0, "Root directory");
ActionMenu* snake = new ActionMenu(&root, "Open snakegame", &startSnakeGame);
...

第二次编辑: 我只在Python中实现了两种方式,而在C++中只实现了一种。因此,我开始在C++中编写多类方法,只是为了好玩和练习,但是我遇到了一个问题;由于有一个基类,我不能将this添加到父类的subMenus向量中,因为基类不拥有subMenus,并且基类无法知道DirectoryMenu

因此,我将不得不通过hack的方式解决这个问题,这是一个很大的缺点。除非有人能想出一个好的实现方法?

BaseMenu::BaseMenu(BaseMenu* parent)
: m_parent(parent) // works
{
    m_parent->addSubMenuk(this); // BaseMenu doesn't have Directory's addSubMenu()
}    
2个回答

2
第二种方法更可取(实际上它使用了 http://sourcemaking.com/design_patterns/composite)。实现被分离出来,易于添加新的菜单项。此外,这种结构将与访问者模式 http://sourcemaking.com/design_patterns/visitor 很好地配合。
您可以添加跟踪代码并查看输出:
#include <vector>
#include <boost/shared_ptr.hpp>

class ActionMenu;
class SubMenu;
class Menu;

typedef boost::shared_ptr<Menu> MenuPtr;
typedef boost::shared_ptr<SubMenu> SubMenuPtr;
typedef boost::shared_ptr<ActionMenu> ActionMenuPtr;

//visitor
class Action
{
public:
    virtual void visit(ActionMenu* actionMenu) = 0;
    virtual void visit(SubMenu* subMenu) = 0;
};

//element
class Menu
{
public:
    virtual void Accept(Action& action) = 0;
};

//menus
class SubMenu : public Menu
{
public:
    virtual ~SubMenu() 
    {
    }

    unsigned long GetMenuCount()
    {
        return m_menus.size();
    }

    MenuPtr GetMenyByIndex(unsigned long index)
    {
        return m_menus[index];
    }

    void AddMenu(const MenuPtr& menu)
    {
        m_menus.push_back(menu);
    }

    virtual void Accept(Action& action)
    {
        action.visit(this);
    }

    void ShowMenu()
    {
    }

    void ChangeStyle()
    {
    }
private:
    std::vector<MenuPtr> m_menus;
};

class ActionMenu : public Menu
{
public:
    virtual ~ActionMenu() 
    {
    }

    virtual void Accept(Action& action)
    {
        action.visit(this);
    }

    void DoCommand()
    {
    }

    void ChangeImage()
    {
    }
};

//visitors

class StyleAction : public Action
{
public:
    virtual ~StyleAction() 
    {
    }

    virtual void visit(ActionMenu* actionMenu)
    {
        actionMenu->ChangeImage();
    }

    virtual void visit(SubMenu* subMenu)
    {
        subMenu->ChangeStyle();
    }
};

class MenuAction : public Action
{
public:
    virtual ~MenuAction() 
    {
    }

    virtual void visit(ActionMenu*actionMenu)
    {
        actionMenu->DoCommand();
    }

    virtual void visit(SubMenu* subMenu)
    {
        subMenu->ShowMenu();
    }
};

int main(int argc, char* argv[])
{
    SubMenuPtr rootMenu(new SubMenu());
    SubMenuPtr fileMenu(new SubMenu());
    SubMenuPtr userMenu(new SubMenu());
    MenuPtr open(new ActionMenu());
    MenuPtr save(new ActionMenu());
    MenuPtr close(new ActionMenu());
    fileMenu->AddMenu(open);
    fileMenu->AddMenu(save);
    fileMenu->AddMenu(close);
    rootMenu->AddMenu(fileMenu);
    rootMenu->AddMenu(userMenu);

    StyleAction sa;
    MenuAction ma;

    //iterate over root menu
    //recursively can bypass all the menu items, the structure of the tree
    for (unsigned int i = 0; i < rootMenu->GetMenuCount(); ++i)
    {
        rootMenu->GetMenyByIndex(i)->Accept(sa);
        rootMenu->GetMenyByIndex(i)->Accept(ma);
    }

    return 0;
}

我现在要睡觉了,那些链接看起来很多,我明天再查看。与此同时,请看一下我的第二次编辑。 - user1632861
没错,你的链接甚至回答了我的第二个编辑,我应该在BaseMenu中定义addSubMenu()removeSubMenu(),并将它们都设为抽象方法。这样做是可行的,但我仍然认为这种方式不比其他方式更好。每个Menu对象中有一个无用变量似乎并不比有两个抽象方法更糟糕,而这两个方法在ActionMenu中也没有被使用。 - user1632861
你应该创建一个纯抽象类Menu。ActionMenu继承自Menu。BaseMenu继承自Menu类,并包含方法addSubMenu(Menu* addedMenu)和removeSubMenu(unsigned int index) - 不是抽象的!所有添加的菜单都存储在BaseMenu中的std::vector<Menu*>中(您可以在此处保留BaseMenu和ActionMenu以及从Menu继承的某些其他类型的新菜单)。这就是我第一个链接的全部内容。在第二步中,您可以添加各种事件来处理您的菜单,有关详细信息,请参见我的第二个链接(<<interface>> Element与您的Menu类相同)。 - fedorshishi
如果晚上时间很短,我会尝试给出一段代码示例。 - fedorshishi
我似乎找不到为什么要添加额外的“BaseMenu”的原因,这毫无意义。 - user1632861

0
这两种方法在性能等方面非常接近,所以很难确定。然而,有一个原因选择其中之一:面向对象编程中的逻辑和规则。我必须将其分成三个类:BaseMenuActionMenuDirectoryMenu
解决“两个类不能互相知道”的问题可以像建议的那样完成。然而,在BaseMenu中定义抽象方法addSubMenu()removeSubMenu()与只有一个类一样违反规则,因此这不是另一种选择。
最终我使用回调和重载指针(*)运算符。现在它返回另一个类的实例指针并根据类型调用它的方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接