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

时间:2012-11-29 20:29:19

标签: c++ python oop class

我已经在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文件夹)菜单,其中包含其他菜单。激活后,它们会打开/关闭。例如:Root directory现已打开,您可以看到其中的所有菜单。如果它关闭,[-]将变为[+],您将看不到任何其他菜单。
  2. 动作(MS-DOS文件)菜单,链接到一个功能。激活后,他们会调用他们链接的功能。例如:Open snakegame可以链接到函数startSnakeGame(),这将关闭菜单并启动蛇游戏。
  3. 我已经编写了两个工作实现来获得想要的结果,我只是想知道,我应该使用哪一个?第一种方式是,我只有一个名为Menu的类,它将所有成员变量和方法编码到一个类中。另一种方式是,我将这两种不同类型的菜单分成两个类,并带有一个共同的基类。


    以下是一些成员变量,我现在将它们分成三个部分(基类,目录类和动作类),但它们可以组合成一个类。

    基本菜单:

    • parent =一个菜单(目录一),将this/self作为孩子保存在列表/向量中(见下文)。
    • label =显然是打印菜单时显示的标签。
    • selected =布尔值,告诉我们当前选择菜单(用鼠标指示)。

    目录菜单:

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

    动作菜单:

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

    正如您所看到的,只有少数变量与其他类不同,并且可以设置为如果action == 0(无操作),则菜单会自动将open更改为{{ 1}}取决于它的当前值。这种方式操作菜单将被终止,并且只有行动菜单将保留false/truesubMenus而不使用。


    这可能完全取决于一个人的意见,但我一直在考虑这个问题,并且找不到一种优于其他方式的方法,它们都有自己的优点和缺点,而且两者都运作良好。所以我问你的意见,我很想知道是否有人有任何理由为什么他们选择一个而不是另一个。基本上我是在问原因,我不在乎你的意见。

    除了文件夹和文件之外,没有其他菜单类型,因此基类不能用于其他任何内容。


    编辑:关于如何使用菜单的简单Python和C ++示例:

    只有一个类的Python:

    closed

    具有多个类的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")
    ...
    

    带有一个类的C ++:

    # 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);
    ...
    

    第二编辑:我只在Python中实现了两种方式,而在C ++中只实现了一种方式。所以我开始用C ++编写多类方法,只是为了好玩和练习,我遇到了一个问题;有一个基类,我不能将DirectoryMenu* root = new DirectoryMenu(0, "Root directory"); ActionMenu* snake = new ActionMenu(&root, "Open snakegame", &startSnakeGame); ... 添加到父的this - 向量,因为基类不拥有subMenus,基类不能知道subMenus

    所以我将不得不破解我的方式,这是一个很大的减号。除非有人能想出一个好方法来实现它吗?

    DirectoryMenu

2 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:0)

这两种方式在性能等方面非常接近,所以有点难以弄清楚。 但是,有一个理由选择一个:OOP中的逻辑和规则。我必须将其拆分为3个类:BaseMenuActionMenuDirectoryMenu

在这里解决“两个班级不能互相认识”的问题可能就像iogane gamba puti fon gu建议的那样。但是,在addSubMenu()中定义抽象方法removeSubMenu()BaseMenu对于规则来说就像只有一个类一样,所以这不是一种选择。

我最终得到的是使用回调并重载指针(*)运算符。它现在返回一个指向另一个类的实例的指针,并根据类型调用它的方法。