【问题标题】:Call derived class method from base pointer list loop (OOD)从基指针列表循环 (OOD) 调用派生类方法
【发布时间】:2019-04-25 06:50:44
【问题描述】:

问题

我遇到了一个简单的问题,但我无法为它想出合适的 OOD。

我有什么:

  • 基类
  • 子类添加新方法foo()
  • 指向基类实例的指针列表

我需要什么:

我需要遍历这个列表并调用foo() 来获取支持此方法的对象,即上述子类的对象(或派生自)。或者一般来说,我需要通过指向基类的指针列表对子类进行“无异味”多态访问。

示例代码

class Entity {
    // ...
    // This class contains methods also needed by subclasses.
};

class SaveableEntity : public Entity {
public:
    virtual void save() = 0;
};

// SaveableEntity has multiple subclasses with specific save() implementations.

std::vector<Entity *> list;
for (Entity *entity : list) {
    // Here I need to save() the descendants of a SaveableEntity type.
}

我想出了一些想法,但我觉得它们都不对。以下是其中一些:

方法一:dynamic_cast

由于有些元素是可保存的,有些则不是,我看到的最明显的方式是动态转换:

std::vector<Entity *> list;
for (Entity *entity : list) {
    auto saveable = dynamic_cast<SaveableEntity *>(entity);
    if (saveable) {
        saveable->save();
    }
}

但是,在这种情况下使用 dynamic_cast 看起来很糟糕(如果我错了,请纠正我)。另外,这种做法很容易导致违反LSP

方法2:将save()移动到基类

我可以删除SaveableEntity 并将save() 方法移动到基础Entity。但是,这使我们实现了虚拟方法:

class Entity {
    virtual void save() {
        // Do nothing, override in subclasses
    }
};

这消除了dynamic_cast 的使用,但虚拟方法似乎仍然不正确:现在基类包含与它完全无关的信息(save() 方法)。

方法 3:应用设计模式

  • 策略模式:SaveStrategy 类及其子类,如NoSaveStrategySomeSaveStrategySomeOtherSaveStrategy 等。同样,NoSaveStrategy 的存在让我们回到了之前方法的缺陷: 基类必须知道其子类的具体细节,这似乎是一个糟糕的设计。
  • ProxyDecorator 模式可以轻松封装dynamic_cast,但这只会隐藏不需要的代码,并不能摆脱糟糕的设计本身。
  • 添加一些composition-over-inheritance 层,等等等等...

问题

也许我遗漏了一些明显的解决方案,或者所描述的方法(1 或 2)在这种特定情况下并不像我看到的那样糟糕和难闻。

那么什么样的设计方法适合这种情况呢?

【问题讨论】:

  • 你仍然可以实现一个纯虚函数。这或多或少会使其成为默认行为。不过我不确定是否有臭味:-/
  • 这个角度看你的问题:基类接口不支持foo。而且您想通过所述接口(通过基类指针访问真实对象)做一些它不支持的事情( foo )。

标签: c++ oop inheritance design-patterns polymorphism


【解决方案1】:

面向数据的编程鼓励解决方案 #4(在 youtube 上的 cppcon 2018 中有一个很好的讨论):有两个列表。一个列表用于所有SavableEntitys,另一个用于不可保存的Entitys。

现在,您遍历第一个列表并-&gt;save() 这些项目。

主要优点是您只迭代相关实体。通过一些(可能是主要的)重构,您可以拥有一组对象而不是指向某些对象的指针。这将增加数据局部性并大大减少缓存未命中的数量。

【讨论】:

    【解决方案2】:

    我的第一个想法是使用virtual bool trySave() 方法来扩充基类。默认可以是return false;,而SaveableEntity 提供了这个覆盖:

    class SaveableEntity : public Entity {
    public:
        virtual bool trySave() final override
        {
            save();
            return true;
        }
    
        // You could also make this protected.
        virtual void save() = 0;
    };
    

    这是否比@YSC 对您的特定情况的建议更合适是您必须自己决定的事情。这类似于将save() 移到基类中,但对用户的困惑要少得多。

    【讨论】:

    • 感谢您的回答,但是,尽管它改进了方法 #2,但它仍然没有解决(理想情况下)对派生类一无所知的“全知”基类。
    猜你喜欢
    • 2014-02-15
    • 2013-09-23
    • 1970-01-01
    • 1970-01-01
    • 2016-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多