【问题标题】:Implementation of a class which depends on a sister class依赖于姊妹类的类的实现
【发布时间】:2016-09-23 15:05:03
【问题描述】:

我有多个,比如 2 个,派生自抽象基类的类,比如 Ingredient。现在,CarrotPotato 通过公共继承实现了Ingredient 的纯虚函数(称为Taste())。现在,假设我想要一个类Salt,它是一个Ingredient(即它派生自它),但需要调用其姐妹类的Taste() 实现?

基本上,Salt 类稍微修改了其姐妹类的 Taste() 实现。 这可以实施吗?如果有,怎么做?

这是我想做的事情的草图:

class Ingredient {
    virtual void Taste() = 0;
};

class Carrot : public Ingredient {
    void Taste() { printf("Tastes like carrot");}
};

class Potato : public Ingredient {
    void Taste() { printf("Tastes like potato"); }
};

class Salt : public Ingredient {
    void Taste() { SisterClass->Taste(); printf(" but salty"); }
};

【问题讨论】:

  • 你想如何获取使用哪个SisterClass
  • @PcAF 我认为指针将是理想的,就像@Quentin 答案中的组合方法一样。我考虑过使用模板对Salt 中的Sister 类进行参数化,但我不确定这是否是最好的方法。

标签: c++ oop


【解决方案1】:

盐不能单独提供改变的味道:它需要一种真正的其他成分来加盐。但是还有哪些其他成分?事实上,具有“某种东西,但有咸味”的不是盐,而是一种咸味制剂,它包含另一种成分。这可以通过多种方式建模:

作曲

这种制剂实际上包含其他成分,并代理调用。

class SaltedPreparation : public Ingredient {
    Ingredient *baseIngredient;
    void Taste() { baseIngredient->Taste(); printf(" but salty"); }
};

Tomato tomato;
SaltedPreparation preparation;
preparation.baseIngredient = &tomato;
preparation.Taste();

继承

咸番茄还是番茄,不是吗?这取决于与成分相同的原理,但成分本身会加盐。我猜。

class SaltedTomato : public Tomato {
    void Taste() { Tomato::Taste(); printf(" but salty"); }
};

SaltedTomato tomato;
tomato.Taste();

混音

我并不热衷于每次需要调味时都编写新课程,所以让我们为此编写一个模板! Mixin 模式是对现有类进行此类通用修改的典型模式。

template <class BaseIngredient>
class Salted : public BaseIngredient {
    void Taste() { BaseIngredient::Taste(); printf(" but salty"); }
};

Salted<Tomato> tomato;
tomato.Taste();

复合

上述所有模型都忽略了盐本身也应该是一种成分的事实。这可能很好,但如果必须这样做呢?那么复合模式可能很有用。我们还要区分调料和主料,因为我们不喜欢咸咸的盐。

class Seasoning : public Ingredient { };

class Salt : public Seasoning {
    void Taste() { printf("salty"); }
};

class SeasonedPreparation : public Ingredient {
    Ingredient *ingredient;
    Seasoning *seasoning;
    void Taste() { ingredient->Taste(); printf(", but "); seasoning->Taste(); }
};

Tomato tomato;
Salt salt;
SeasonedPreparation preparation;
preparation.ingredient = &tomato;
preparation.seasoning = &salt;
preparation.Taste();

我现在有点饿了。

【讨论】:

  • 我喜欢你的 SeasonedPreparation 包装器,我应该想到这一点。如果需要多种调味料,您可以将seasoning 展开为一个列表。
  • @RemyLebeau 非常感谢您的编辑:)
  • 哇,感谢您的周到回答!我认为简单的组合可以满足我的需要,尽管我喜欢 Mixin 设计的想法。直到我读到你的答案,我才知道这件事。 Composite 类SeasonedPreparation 的问题在于它本身不是Ingredient。在我的用例中,我需要使用模板来同时使用Ingredients 和SeasonedIngredients,而不是简单地传递Ingredient 指针。 (盐类比是我能找到的最接近我的实际问题的东西。)@RemyLebeau 也谢谢你的回答!
  • @JoeyDumont 这是一个疏忽,SeasonedPreparation 现在是实际的Ingredient
【解决方案2】:

Salt 不能修改其他类的实现。但是Salt 可以被赋予指向另一个Ingredient 的指针以进行操作,例如:

class Salt : public Ingredient
{
private:
    Ingredient *OtherIngredient;
public:
    Salt(Ingredient *AOther) : OtherIngredient(AOther) {}
    virtual void Taste() { OtherIngredient->Taste(); printf(" but salty"); }
};

然后你可以这样做:

Potato potato;
Salt salt(&potato);
salt.Taste();

但这并没有真正的意义,不是吗?盐尝起来不像马铃薯,但马铃薯可以尝到咸味。所以,我可能会采取不同的方法:

#include <vector>

class Ingredient 
{
public:
    virtual void Taste() = 0;
};

class Seasoning : public Ingredient 
{
};

class SeasonableIngredient : public Ingredient 
{
protected:
    std::vector<Seasoning*> seasonings;
    virtual void TastesLike() = 0;

public:
    virtual void Taste()
    {
        printf("Tastes like ");
        TastesLike();
        if (!seasonings.empty())
        {
            std::vector<Seasoning*>::iterator iter = seasonings.begin();
            printf(" but ");
            iter->Taste();
            ++iter;
            while (iter != seasonings.end())
            {
                printf(" and ");
                iter->Taste();
                ++iter;
            }
        }
    }

    void AddSeasoning(Seasoning *seasoning) { seasonings.push_back(seasoning); }
};

class Carrot : public SeasonableIngredient
{
protected:
    virtual void TastesLike() { printf("carrot"); }
};

class Potato : public SeasonableIngredient
{
protected:
    virtual void TastesLike() { printf("potato"); }
};

class Salt : public Seasoning
{
public:
    void Taste() { printf("salty"); }
};

class Pepper : public Seasoning
{
public:
    void Taste() { printf("peppery"); }
};

Potato potato;
Salt salt;
Pepper pepper;
Potato.AddSeasoning(&salt);
Potato.AddSeasoning(&pepper);
potato.Taste();

【讨论】:

  • 你的SpicableIngredient 应该拥有香料,从而控制它的生命周期。但是你只保留一个指向香料的指针,这肯定会导致更大范围的崩溃或内存泄漏
  • 我的设计是有意的,我让调用者保留它添加的Spice 对象的所有权。否则,SpicableIngredient 将需要一个单独的方法来添加每种类型的SpiceAddSalt()AddPepper() 等),因此SpicableIngredient 创建并获得它们的所有权。