0

I have multiple, say 2, classes that are derived from an abstract base class, say Ingredient. Now, Carrot and Potato implement the pure virtual function (call it Taste()) of Ingredient via public inheritance. Now, say I want to have a class Salt that is an Ingredient (that is, it is derived from it), but needs to call the Taste() implementation of its sister classes?

Basically, the Salt class slightly modifies the Taste() implementation of its sister classes. Can this be implemented? If so, how?

Here's a sketch of what I want to do:

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"); }
};
Barry
  • 286,269
  • 29
  • 621
  • 977
Joey Dumont
  • 898
  • 2
  • 7
  • 25
  • 1
    And how do you want to obtain which `SisterClass` to use? – PcAF May 25 '16 at 20:21
  • 1
    http://stackoverflow.com/questions/7085265/what-is-c-mixin-style – strangeqargo May 25 '16 at 20:25
  • @PcAF A pointer would be ideal, I think, like the composition method in @Quentin's answer. I thought about using templates to parametrize the `Sister` classes inside `Salt`, but I'm not sure if it's the best way. – Joey Dumont May 26 '16 at 12:16

2 Answers2

5

Salt cannot sensibly offer the modified taste on its own : it needs an actual other ingredient to salt. But which of the other ingredients ? In fact, what has a "something, but salty" taste is not the Salt, but a salty preparation, which contains another ingredient. This can be modeled in several ways :

Composition

This preparation literally contains the other ingredient, and proxies the call.

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

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

Inheritance

A salted tomato is still a tomato, isn't it ? This hinges on the same principle as composition, but the ingredient salts itself. I guess.

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

SaltedTomato tomato;
tomato.Taste();

Mixin

I'm not keen on writing new classes everytime I need some seasoning, so let's write a template for that ! The Mixin pattern is typical for such generic modifications to existing classes.

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

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

Composite

All of the above models lose the fact that salt is also supposed to be an ingredient on its own. That may be fine, but what if it has to ? Then the Composite pattern may be useful. Let's also distinguish seasonings from main ingredients, because we don't fancy salty salted salt.

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();

I'm a bit hungry now.

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • I like your `SeasonedPreparation` wrapper, I should have thought of that. And if multiple seasonings are needed, you can just expand the `seasoning` to be a list. – Remy Lebeau May 25 '16 at 22:30
  • @RemyLebeau thank you very much, for the edit also :) – Quentin May 26 '16 at 07:42
  • Wow, thank you for the thoughtful answer! I think the simple composition will do what I need, although I like the idea of a Mixin design. I didn't know about it until I read your answer. The issue with the Composite class `SeasonedPreparation` is that it's not an `Ingredient` in itself. In my use case, I would need to use templates to use both `Ingredient`s and `SeasonedIngredient`s instead of simply passing an `Ingredient` pointer. (The salt analogy was the closest thing I could find to my actual problem.) @RemyLebeau Thank you also for your answer! – Joey Dumont May 26 '16 at 12:20
  • 1
    @JoeyDumont That was an oversight, `SeasonedPreparation` is an actual `Ingredient` now. – Quentin May 26 '16 at 12:27
1

Salt cannot modify the implementation of the other classes. But Salt can be given a pointer to another Ingredient to act on, eg:

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

Then you can do things like this:

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

But that doesn't really make sense, does it? Salt does not taste like a Potato, but a Potato can taste salty. So, I would probably take a different approach for that:

#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();
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Your `SpicableIngredient` should own the spices thus controlling lifetime of it. But you're keeping a bare pointer to spices, this will definitely cause crashes or memleaks in a bit larger scale – Teivaz May 25 '16 at 22:09
  • My design was intentional, I'm letting the caller keep ownership of the `Spice` objects it adds. Otherwise, `SpicableIngredient` would need a separate method for adding each type of `Spice` (`AddSalt()`, `AddPepper()`, etc) so `SpicableIngredient` creates and takes ownership of them. – Remy Lebeau May 25 '16 at 22:20