重构建议:如何避免在此OO设计中的类型检查 [英] Refactoring advice: How to avoid type checking in this OO design

查看:122
本文介绍了重构建议:如何避免在此OO设计中的类型检查的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



我正在使用Command设计模式来构造一个菜单树。菜单中的项目可以是各种类型(例如,立即动作[如保存],根据其状态显示带有勾号/图标的切换开/关属性[如斜体]等)。至关重要的是,还有一些子菜单,用于替换屏幕上当前菜单的(而不是显示在旁边)。这些子菜单当然包含自己的菜单项列表,其可以具有更多嵌套的子菜单。



代码是类似的(为了简单起见, p>

  //抽象基类
struct MenuItem
{
virtual〜MenuItem(){}
virtual void Execute()= 0;
virtual bool IsMenu()const = 0;
}

//具体类
struct Action:MenuItem
{
void Execute(){/*...*/}
bool IsMenu const {return false; }
// ...
};

// ...其他菜单项

struct菜单:MenuItem
{
void Execute(){/ *显示菜单* /}
bool IsMenu()const {return true; }
// ...
std :: vector< MenuItem *> m_items;
typedef std :: vector< MenuItem *> :: iterator ItemIter;
}

主菜单只是菜单的一个实例,一个单独的类跟踪菜单位置,包括如何进入和退出子菜单:

  struct位置
{
位置)
:m_menu(menu)
{
//保存初始位置
m_pos.push_back(MenuPlusIter(m_menu,m_menu-> m_items.begin
}

//忽略错误条件简单
void OnUpPressed(){m_pos.back()。iter--; }
void OnDownPressed(){m_pos.back()。iter ++; }
void OnBackPressed(){m_pos.pop_back(); }

void OnEnterPressed()
{
MenuItem * item = * m_pos.back()。
//如果当前
//选择的项目是一个子菜单,需要在这里有不同的行为
if(item-> IsMenu())
{
// dynamic_cast不需要因为我们知道类型
Menu * submenu = static_cast< Menu *>(item);

//将新菜单和位置放到栈中
m_pos.push_back(MenuPlusIter(submenu,submenu-> m_items.begin()));

// Redraw
submenu-> Execute();
}
else
{
item-> Execute();
}
}

private:
struct MenuPlusIter
{
菜单*菜单;
Menu :: ItemIter iter;

MenuPlusIter(Menu * menu_,Menu :: ItemIter iter_)
:menu(menu_)
,iter(iter_)
{}
}

菜单* m_menu;
std :: vector< MenuPlusIter> m_pos;
}

关键函数是Position :: OnEnterPressed(),你在调用中看到一个显式类型检查到MenuItem :: IsMenu(),然后转换为派生类型。什么是一些选项来重构这个避免类型检查和转换?

解决方案

IMO,重构的起点将是这些语句:

  1. m_pos.push_back(MenuPlusIter(m_menu,m_menu-> m_items.begin())); 

2. m_pos.push_back(MenuPlusIter(submenu,submenu-> m_items.begin()));

同样的语句重复本身的事实,IMO,需要重构的符号



如果您可以在基类的方法中使用factor(1),然后在派生类中覆盖它以考虑特定行为(2) ,那么你可以把它放在执行



如果我错了,纠正我:



现在,当您选择的项目是一个子菜单时,会显示一个子菜单, Execute 操作的含义:激活子菜单(我在一般意义上使用activate)。当项目不是子菜单时, Execute 是不同的野兽。



理解你的菜单系统,但在我看来,你有一个层次菜单/子菜单(位置)和一些动作,根据节点的类型触发。



我想象的是,菜单/子菜单关系是一个层次结构,允许你定义叶节点(当你没有子菜单)和非叶节点(子菜单)。叶节点调用动作,非叶节点调用不同类型的动作,其涉及激活子菜单(该动作返回到菜单系统,因此您不会将关于菜单系统的知识封装在其中,将动作传递到菜单系统)。



不知道这对你有意义。


I'm looking for advice on refactoring to improve my class design and avoid type checking.

I am using the Command design pattern to construct a menu tree. An item in the menu could be of various types (e.g., an immediate action [like "Save"], a toggle on/off property which displays with check/icon depending on its state [like "italics"], etc.). Crucially, there are also submenus, which replace (rather than displaying off to the side of) the current menu on the screen. These submenus of course contain their own list of menu items, which could have more nested submenus.

The code is something like (all public for simplicity in presentation):

// Abstract base class
struct MenuItem
{
  virtual ~MenuItem() {}
  virtual void Execute()      = 0;
  virtual bool IsMenu() const = 0;
};

// Concrete classes
struct Action : MenuItem
{
  void Execute() { /*...*/ }
  bool IsMenu() const { return false; }
  // ...
};

// ... other menu items

struct Menu : MenuItem
{
  void Execute() { /* Display menu */ }
  bool IsMenu() const { return true; }
  // ...
  std::vector<MenuItem*> m_items;
  typedef std::vector<MenuItem*>::iterator ItemIter;
};

The main menu is just an instance of Menu, and a separate class keeps track of the menu position, including how to go into and out of submenus:

struct Position
{
  Position( Menu* menu ) 
    : m_menu( menu ) 
  {
    // Save initial position
    m_pos.push_back( MenuPlusIter( m_menu, m_menu->m_items.begin() ) );
  }

  // Ignore error conditions for simplicity
  void OnUpPressed()   { m_pos.back().iter--; }
  void OnDownPressed() { m_pos.back().iter++; }
  void OnBackPressed() { m_pos.pop_back();    }

  void OnEnterPressed()
  {
    MenuItem* item = *m_pos.back().iter;
    // Need to behave differently here if the currently 
    // selected item is a submenu
    if( item->IsMenu() )
    {
      // dynamic_cast not needed since we know the type
      Menu* submenu = static_cast<Menu*>( item );

      // Push new menu and position onto the stack
      m_pos.push_back( MenuPlusIter( submenu, submenu->m_items.begin() ) );

      // Redraw
      submenu->Execute();
    }
    else
    {
      item->Execute();
    }
  }

private:
  struct MenuPlusIter
  {
      Menu*          menu;
      Menu::ItemIter iter;

      MenuPlusIter( Menu* menu_, Menu::ItemIter iter_ )
          : menu( menu_ )
          , iter( iter_ )
      {}
  };

  Menu* m_menu;
  std::vector<MenuPlusIter> m_pos;
};

The key function is Position::OnEnterPressed(), where you see an explicit type check in the call to MenuItem::IsMenu() and then a cast to the derived type. What are some options to refactor this to avoid the type check and cast?

解决方案

IMO, the refactoring starting point would be these statements:

 1. m_pos.push_back( MenuPlusIter( m_menu, m_menu->m_items.begin() ) );

 2. m_pos.push_back( MenuPlusIter( submenu, submenu->m_items.begin() ) );

The fact that the same kind of statement repeat itself is, IMO, the sign for the need to refactor that.

If you could factor (1) in a method of your base class, and then override it in the derived class to take into account the specific behavior (2), then you could just put this in Execute.

Correct me if I am wrong: the idea is the a menu has items, and each item has got an action associated to it that is triggered when some event is detected.

Now, when the item you select is a submenu, the the Execute action has the meaning: activate the submenu (I am using activate in a generic sense). When the item is not a submenu, then Execute is a different beast.

I don't have a full comprehension of your menu system, but it seems to me you have a sort of hierarchy menu/submenu (the positions), and some actions that are triggered depending of the kind of node.

What I envision is that the menu/submenu relationship is a hierarchy that allows you to define leaf-nodes (when you don't have a submenu), and non-leaf-nodes (the submenu). A leaf node invoke an action, a non-leaf-node invoke a different kind of action which deals with activating a submenu (this action goes back to the menu system, so you do not encapsulate knowledge about the menu system in it, you simply relay the action to menu system).

Don't know if this makes sense to you.

这篇关于重构建议:如何避免在此OO设计中的类型检查的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆