【问题标题】:Class needs to handle a new type - duplicate or extend?类需要处理一种新类型——重复还是扩展?
【发布时间】:2010-07-20 20:58:53
【问题描述】:

假设我有一个用于宠物商店的 C++ 应用程序,该应用程序刚开始使用 Cats。该应用程序有一个类可以对 Cat 对话框进行一些数据处理:

CatDlg.cpp:

CatDlg::CatDlg() {//ctor stuff}

CatDlg::onInitDialog() {
   DBAccess db;
   db.open();
   CatRec cat;
   cat = db.findRec(m_catID);
   CString name = cat.getName();

   NameField.SetWindowText(name);
   // other catty dialogy stuff
}

现在店主想要将 Dogs 添加到应用程序中。狗数据本质上与猫数据相同,只是数据类型不同。我可以复制 CatDlg 类,更改一些类型并以这种方式处理它,但这会导致维护问题和 99% 重复代码的代码膨胀:

DogDlg.cpp:

DogDlg::onInitDialog() {
   DBAccess db;
   db.open();
   DogRec dog;
   dog = db.findRec(m_dogID);
   CString name = dog.getName();

   NameField.SetWindowText(name);
   // other doggy dialogy stuff
}

或者,我可以更改对话框类以处理多个动物,方法是传入一个指示动物类型的标志,然后相应地处理它。随着所有者添加新动物,这也变得更加丑陋,因为方法变得更长,有很多重复的代码块:

AnimalDlg.cpp:

AnimalDlg::onInitDialog(AnimalType type) {
    DBAccess db;
    db.open();
    if(type == CAT)
    {
        CatRec cat;
        cat = db.findRec(m_catID);
        CString name = cat.getName();
    }
    else if(type == DOG)
    {
        DogRec dog;
        dog = db.findRec(m_dogID);
        CString name = dog.getName();
    }

   NameField.SetWindowText(name);
}

重构类以使其更通用的最佳方法是什么?有我可以应用的设计模式吗?有没有办法将通用代码拉入基类,然后制作特定于动物的子类?我快速浏览了 Fowler 关于重构的书,但没有任何反应。

假设类中有更多代码,目前特定于 Cat 类型,但适用于任何类型的动物。

感谢您的帮助!

菲利普

【问题讨论】:

  • 多态性?虚函数?
  • 您使用的是什么 GUI 工具包,还是有待决定?

标签: c++ oop


【解决方案1】:

这要求继承。为动物定义一个基类。然后派生出猫狗和条纹海狸以及商店想要出售的任何其他东西。

【讨论】:

    【解决方案2】:

    假设 CatRec 和 DogRec 是不相关的类型,不能成为继承结构的一部分,我认为您需要使用模板。

    AnimalDlg 将是 CatDlgDogDlg 的父级。 AnimalDlg 将有一个新的受保护模板 findRec 函数,其返回类型将是模板类型(因此您可以返回 CatRec 或 DogRec)。 onInitDialog 将仅存在于 AnimalDlg 中,并调用虚函数 getName 而不是特定于 db 的代码。 getName 的子版本然后将使用findRec 的适当版本(例如findRec<CatDlg>)来获取记录并将类型返回给onInitDialog

    如果有机会,我稍后会尝试绘制代码。

    编辑(代码草图):

    class AnimalDlg
    {
    public:
        void onInitDialog()
        {
            NameField.SetWindowText(getName());
        }
    
    protected:
        template <class T>
        T findRec(IdType id) const
        {
            DBAccess db;
            db.open();
            return T(db.findRec(id));
        }
    
        virtual std::string getName() const = 0;
    };
    
    class CatDlg : public AnimalDlg
    {
    protected:
        virtual std::string getName() const
        {
            return findRec<CatRec>(m_catID).getName();
        }
    };
    

    【讨论】:

      【解决方案3】:

      将您的数据库和数据直接绑定到 GUI 并不是那么好。如果您想要的只是 CRUD(创建读取更新删除),那么已经有很多工具可以使用数据库来实现这一点。

      围绕应用的目的而不是如何存储数据来驱动 GUI。

      【讨论】:

      • 这很好——虽然这个例子是对实际问题的简化,但它可能确实滥用了 MFC 的 MVC 架构......
      【解决方案4】:

      我会沿着模板专业化的路线走:

      template <class AnimalType>
      AnimalDlg::onInitDialog(AnimalType type, int m_animalId) {
         DBAccess db;
         db.open();
      
         name = db.findRec<AnimalType>(m_animalId).getName();
      
         NameField.SetWindowText(name);
      }
      

      【讨论】:

        猜你喜欢
        • 2012-10-22
        • 2023-03-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-01
        • 2020-12-30
        • 1970-01-01
        相关资源
        最近更新 更多