【问题标题】:At what inheritance level to store a dependency pointer when both dependency and dependent are polymorphic?当依赖项和依赖项都是多态的时,在什么继承级别存储依赖项指针?
【发布时间】:2019-12-14 19:07:35
【问题描述】:

我目前正在处理多态对象具有注入依赖项的情况,该依赖项也是多态的。我的问题是关于第一个类家族具有该家族共有行为的最佳方式,需要调用首先在第二个家族的顶级基类中定义的虚拟方法。具体来说,关于将拥有的智能指针存储到依赖项的位置——在基类、派生类或这两个地方。 (这个问题是关于在任务中使用智能指针,但是在使用引用或原始指针进行 DI 时也存在类似的问题。)

我将使用一些示例基类 Worker 和 Job 来说明。每个 Worker 拥有一个 Job,构造函数注入。 DerivedWorkers 可能要求用户注入特定的 DerivedJob,并调用特定于该 DerivedJob 的方法。每个 Worker 都必须有公共方法 get_location(),该函数的逻辑对所有 Worker 都是一样的,它需要调用 Job 中定义的虚拟方法 get_hours() 并被其子级覆盖。这是我提出的三种策略,“基址和派生的指针”、“仅派生的指针”和“仅基址的指针”:

class Job
{
public:
    virtual ~Job();
    virtual Hours_t get_hours();
};

class DerivedJob : public Job
{
public:
    virtual Hours_t get_hours();
    void derived_specific_method();
};

策略 1:基指针和派生指针

class Worker
{
public:
    Worker(std::shared_ptr<Job> job) : my_job(job) {}
    virtual ~Worker();
    Location_t get_location()
    {
        return some_logic(my_job->get_hours());
    }
private:
    std::shared_ptr<Job> my_job; //cannot be unique_ptr
};

class DerivedWorker : public Worker
{
public:
    DerivedWorker(std::shared_ptr<DerivedJob> derivedJob) : Worker(derivedJob), my_derived_job(derivedJob) {}
    void derived_specific_duty()
    {
        my_derived_job->derived_specific_method();
    }
private:
    std::shared_ptr<DerivedJob> my_derived_job;
};

策略 2:仅派生指针

class Worker //abstract
{
public:
    virtual ~Worker();
    virtual Location_t get_location() = 0;
};

class DerivedWorker : public Worker
{
public:
    DerivedWorker(std::unique_ptr<DerivedJob> derivedJob) : my_derived_job(derivedJob) {}
    virtual Location_t get_location()
    {
        return some_logic(my_derived_job->get_hours());
    }
    void derived_specific_duty()
    {
        my_derived_job->derived_specific_method();
    }
private:
    std::unique_ptr<DerivedJob> my_derived_job;
};

策略 3:仅在基址中的指针

class Worker
{
public:
    Worker(std::unique_ptr<Job> job) : my_job(job) {}
    virtual ~Worker();
    Location_t get_location()
    {
        return some_logic(my_job->get_hours());
    }
protected:
    std::unique_ptr<Job> my_job;
};

class DerivedWorker : public Worker
{
public:
    DerivedWorker(std::unique_ptr<DerivedJob> derivedJob) : Worker(derivedJob) {}
    void derived_specific_duty()
    {
        dynamic_cast<DerivedJob*>(my_job.get())->derived_specific_method();
    }
};

每个都有缺点,我想弄清楚我是否缺少第四种方法,是否有惯用的或“最佳”方法,或者我是否缺少一些重构技巧依赖模式的类型已过时。

对于 1,“基数和派生指针”,缺点是即使每个 Job 仅由一个 Worker 拥有,您也不能使用 unique_ptr,因为从技术上讲,每个 Worker 可以拥有多个指向同一个 Job 的智能指针。如果频繁移动 Worker,或者由于移动 shared_ptrs 引入的缓存内聚减慢,这可能是一个问题。这是我目前倾向于的策略。

对于 2,“仅派生的指针”的缺点是大量的代码重复。 get_location() 必须是虚拟的,尽管对于所有工人来说几乎完全相同的代码。此外,现在 Worker 可能必须是抽象的。 (在这个特定的示例中,您可以通过为 Location_t 设置一个空值来避免这种情况,但这在此问题的实际应用中并不总是可行的。)

对于 3,“仅在基址中的指针”,缺点是必须使用 dynamic_cast,这是一个巨大的代码异味是有原因的。巨大的运行时成本,必须添加对失败演员表的检查等。

【问题讨论】:

  • 是否有一些特定的上下文需要如此压倒性地困难?
  • “每个工人都有一份工作”——那为什么要std::shared_ptr
  • 澄清你需要什么:添加一些用例,你的解决方案有什么问题和想要的结果?
  • 一个更具体的用例是一个依赖项,它为具有相同目的但电气接口不同的一系列硬件实现驱动程序,以及一个实现您希望使用硬件完成的任务的依赖项。我想使用 Worker/Job 的类比,因为它似乎更容易理解。
  • @SergeyKolesnik 这正是将指针两次存储在基数和派生中的策略的缺点,因为使用 unique_ptr 变得不可能。没有这个障碍,选择会很清楚,我不会发布问题。

标签: c++ dependency-injection dependencies polymorphism smart-pointers


【解决方案1】:

我会选择 2 的变体。:

最好有virtual Job&amp; get_job() = 0;而不是virtual Location_t get_location() = 0;,这样你就可以在派生类中使用协变返回类型,并且get_location()的实现不会重复。

class Worker // abstract
{
public:
    virtual ~Worker() = default;
    virtual Job& get_job() = 0;

    Location_t get_location() { return some_logic(get_job().get_hours()); }
};

class DerivedWorker : public Worker
{
public:
    explicit DerivedWorker(std::unique_ptr<DerivedJob> derivedJob) : my_job(std::move(derivedJob)) {}

    DerivedJob& get_job() override { return *my_job;}

    void derived_specific_duty() { my_job->derived_specific_method(); }
private:
    std::unique_ptr<DerivedJob> my_job;
};

【讨论】:

  • 谢谢,这确实解决了指针派生策略的最大问题!这是一个开放式问题,所以我将等待一天将其标记为已接受,但这肯定会解决手头的问题。
【解决方案2】:

方法 1 - 问题是数据重复,这既是开销又是错误的来源。

方法 3 - 问题在于动态转换 - 这是一个非常缓慢的转换。您可以只使用 static_cast 但不断制作演员表很麻烦。由于基类几乎没有实现,因此不值得将类型存储在其中,因为它只会限制其使用。

方法 2 - 是您提供的 3 种方法中唯一合理的方法。

除此之外,我还质疑这些虚拟课程的目的——它们是否有目的?仅仅拥有一个通用函数并不足以成为创建共享基类的理由。它实际上需要提供帮助。不要仅仅因为他们说“面向对象是好的”而创建基类。

另外,我也会考虑模板方法。如果您的对象众多且需要快速处理,则面向对象往往无法满足基于模板的方法。

【讨论】:

  • 您能否详细说明方法 1 中数据重复可能引发的问题?我假设除了失去使用 unique_ptr 的能力之外,在一个对象中复制智能指针的存储没有任何缺点。
  • @John 想象您的同事制作或修改了 DerivedWorker 类,却忘记在某个地方更新其中一个指针。
猜你喜欢
  • 2015-11-11
  • 2014-12-09
  • 1970-01-01
  • 2019-12-19
  • 1970-01-01
  • 2016-10-24
  • 1970-01-01
  • 2014-03-18
  • 1970-01-01
相关资源
最近更新 更多