0

I have a class with some data, which I want to represent in different ways. There will always only be one representation active at a time, but I need to be able to change between them at runtime without losing internal state of any of the representations.

As a small example, let us use a chart. It can either be a pie-chart or a bar-chart, nothing else and only one at a time. I want to use the same data for both charts, but each of them represents the data differently. So both types of charts need their own rules for how to treat the data, which can change at runtime by the user.

I do not want to deal with each case in the chart class, but rather use something that handles every type of chart and calls the correct functions depending on the active one. All used variants of charts are known at compile time (two in this example).
A simple inheritance does not work, because the data is not shared between the charts. The strategy-pattern also does not quite work, because the state is lost upon switching charts and I need to preserve it. For the same reason std::variant does not work in this case.

My current solution is similar to the strategy-pattern, but keeping each strategy alive in a manager class. Each strategy has a pointer to the chart class and is a friend of it to access the data:

struct Chart;

struct Strat {
    explicit Strat(Chart* chart) : chart {chart} {}
    virtual void foo() = 0;
    Chart* chart;
};

struct Pie : public Strat {
    explicit Pie(Chart* chart) : Strat {chart} {}
    void foo() override { /* use chart->data */ }
};

struct Bar : public Strat {
    explicit Bar(Chart* chart) : Strat {chart} {}
    void foo() override { /* use chart->data */ }
};

struct Manager {
    explicit Manager(Chart* chart) : pie {chart}, bar {chart} { strat = &pie; }
    void changeToBar() { strat = &bar; }
    void foo() { strat->foo(); }
    Strat* strat;
    Pie pie;
    Bar bar;
};

struct Chart {
    Chart() : manager {this} { manager.foo(); }
    void changeToBar() { manager.changeToBar(); }
    void foo() { manager.foo(); }
    friend Pie;             // friends to make data accessible
    friend Bar;
private:
    Manager manager;
    int data = 42;          // private data, shared by all strats
};

int main() {
    Chart chart;            // inititally pie chart
    chart.foo();            // do pie stuff
    chart.changeToBar();    // now bar chart, but keep pie alive
    chart.foo();            // do bar stuff
}

To me it feels like there is a better solution to do the same thing, but I could not find it, so my question is: is this the correct way of dealing with multiple strategies, while preserving the state?

Julian
  • 41
  • 7

1 Answers1

1

I imagine you thought of this already, but you could create a shared pointer to your data structure, and pass it to all your chart structs. Any new chart type would then be easy to add, just pass the same pointer. If you pass the data structure pointer in the chart constructors then it sort of self documents as well for when your client requests a new chart type down the road.

psimpson
  • 406
  • 4
  • 11
  • Good point, however the thing with shared_ptr in this case is that it only adds one additional step for the chart class and does not save me anything else (except the friend declarations)? Because I would need to pack all my data inside some data structure to only have one shared_ptr, but then I need to access this struct in the chart class as well, instead of directly working with the data. I much prefer declaring 2-3 friends to make clear that those have access to my data, than using a shared_ptr which is not as clear to me which instances use it at the same time. Am I missing something? – Julian Sep 01 '19 at 09:43
  • 1
    @Julian I think you may be discovering the trouble with asking, "What is the best way to ..." type questions. Assuming all alternatives work, the best way is always whatever best matches your employer's style guide. Beyond that you quickly enter the realm of opinion or preference. – psimpson Sep 01 '19 at 11:23
  • True, just thought there might be alternatives, like the one you pointed out or even some "preferred way". There are many interesting ideas of doing things and I certainly do not know all of them, so I am grateful for any option to choose from, thanks. – Julian Sep 01 '19 at 12:26