【问题标题】:Best way to construct a console menu class hierarchy? [closed]构建控制台菜单类层次结构的最佳方法? [关闭]
【发布时间】:2012-11-29 20:29:19
【问题描述】:

我在 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(),这将关闭菜单并启动贪吃蛇游戏。

我已经编写了两个工作实现来获得想要的结果,我只是想知道,我应该使用哪一个?第一种方法是,我只有一个名为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-vector,因为基类不拥有subMenus,并且基类无法知道DirectoryMenu

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

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

【问题讨论】:

    标签: c++ python oop class


    【解决方案1】:

    第二种方式更可取(实际上是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;
    }
    

    【讨论】:

    • 我要去睡觉了,那些链接看起来很长,我明天检查一下。同时看看我的第二次编辑。
    • 对,你的链接甚至回答了我的第二次编辑,我应该在BaseMenu 中定义addSubMenu()removeSubMenu(),并将它们都抽象化。会工作,但我仍然不认为这种方式比另一种更好。在每个 Menu-object 中拥有一个无用的变量似乎并不比拥有两个在 ActionMenu 中未使用的抽象方法更糟糕
    • 你应该做一个纯抽象类菜单。 ActionMenu 继承自 Menu。 BaseMenu 继承自 Menu 类并包含方法 addSubMenu(Menu* addedMenu) 和 removeSubMenu(unsigned int index) - 不是抽象的!所有添加的菜单都存储在 std::vector 的 BaseMenu 中(您可以在此处保留为 BaseMenu 和 Action Menu 以及从 Menu 继承的一些其他类型的新菜单)。这就是我的第一个链接。在第二步中,您可以添加将处理您的菜单的各种事件,有关详细信息,请参阅我的第二个链接(> 元素与您的菜单类相同)。
    • 如果晚上时间不多,我就试着举个代码例子。
    • 我似乎找不到任何理由要添加额外的BaseMenu,这没有任何意义。
    【解决方案2】:

    这两种方式在性能等方面非常接近,所以很难弄清楚。 但是,选择其中之一是有原因的:OOP 中的逻辑和规则。我不得不将其分为 3 个类:BaseMenuActionMenuDirectoryMenu

    这里可以像iogane gamba puti fon gu 建议的那样解决“两个类不能互相认识”的问题。但是,在 BaseMenu 中定义抽象方法 addSubMenu()removeSubMenu() 与只有一个类一样违反规则,因此这不是另一种选择。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-24
      • 1970-01-01
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多