5

I'm thinking specifically of the Strategy pattern (Design Patterns, GoF94), where it is suggested that the context passed to the strategy constructor can be the object which contains the strategy (as a member) itself. But the following won't work:

//analysis.h

class StrategyBase;
class Strategy1;
class Strategy2;
class Analysis
{
   ...
      void ChooseStrategy();
   private:
      StrategyBase* _s;
      ...
};

//analysis.cpp

void Analysis::ChooseStrategy()
{
   if (...) _s = new Strategy1(this);
   else if (...) _s = new Strategy2(this);
   ...
}

//strategy.h

#include analysis.h
...

and then StrategyBase and its subclasses then access the data members of Analysis.

This won't work because you can't instantiate Strategy* classes before they've been defined. But its definition depends on that of Analysis. So how are you supposed to do this? Replace ChooseStrategy with

void SetStrategy(StrategyBase* s) { _s = s; }

and do the instantiation in files which #include both analysis.h and strategy.h? What's best practice here?

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
Matt Phillips
  • 9,465
  • 8
  • 44
  • 75
  • I think you need to express you problem more clearly. What's wrong with `#include`ing both header files from any source file that needs the definitions of both classes? To be clear, you say "the following won't work" but it's not clear why you think it won't work. – CB Bailey May 09 '11 at 17:07
  • I don't follow; why can't you have ctor `Strategy1(Analysis *th)` in `strategy.hpp`? – David R Tribble May 09 '11 at 17:11
  • @Charles, new Strategy1(this) won't compile. What you suggest sounds like what I suggested at the end, but there are situations in which you'd like to choose the strategy within the Analysis itself. @LoadMaster You can, that's not what will cause the error. – Matt Phillips May 09 '11 at 18:41
  • 1
    @MattPhillips: I'm sorry, I still don't understand. What stops you from `#include`ing "strategy1.hpp" (or wherever `Startegy1` has a definition from "analysis.cpp" where you need the full definition? Or do you have a specific compile error that you need help with? – CB Bailey May 09 '11 at 19:32
  • @Charles, You're absolutely right, that solves the problem. However my aversion to that is that now you have a .cpp which only redundantly includes its corresponding .h file (since strategy.h includes analysis.h). It just made the dependencies confusing, esp. as the number of files increased. – Matt Phillips May 10 '11 at 11:17
  • @Matt Phillips Include guards ensure that no redundant code is actually only compiled once. Don't worry about redundant inclusion of headers. – Mark B May 10 '11 at 13:09

4 Answers4

6

You will always have circular dependencies in the State/Strategy Pattern, except for very general States/Strategies. But you can limit the in-size (Lakos) use of the respective other class such that it compiles, at least:

  1. Forward-declare Analysis (analysis.h or strategies.h)
  2. Define StrategyBase and subclasses (don't inline methods that use Analysis) (strategies.h)
  3. Define Analysis (may already use inline methods that use strategies) (analysis.h)
  4. Implement Analysis and the strategy classes' non-inline methods (analysis.cpp)
Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
  • Thank you. My issue with this though is re 4)--I think that defining methods for a class A anywhere but a.h makes code subsequently harder to find. I've tried it, didn't like it. – Matt Phillips May 10 '11 at 11:29
  • The assertion, "_You will always have circular dependencies..._" is too strong. The GoF book mentions three ways to, "_give a ConcreteStrategy efficient access to any data it needs from a context, and vice versa._" (page 319) Only one of these includes a circular dependency. – jaco0646 Oct 22 '18 at 20:48
3

analysis.cpp also needs to include strategy.h to pick up the full definitions of the strategies. Since it's a source file there's no circular dependency.

Mark B
  • 95,107
  • 10
  • 109
  • 188
1

You don't need to instantiate classes if you're passing pointers (or references): Use a Forward Declaration.

Community
  • 1
  • 1
James
  • 24,676
  • 13
  • 84
  • 130
0

One important feature of c++ that is sometimes quite difficult to figure out is that the classes need to be provided for compiler in the correct order. And dependencies between classes decides which order needs to be used. It does not make it easier that people want to put every class to separate .h file, so the order of classes need to be provided several times when deciding order of #include's. While people are learning header and class dependencies, it's recommended to put every class to the same .h file, as this will force you to decide the order just once. After learning the correct order, you can again start using the good conventions of placing all classes to separate header files.

tp1
  • 288
  • 1
  • 3
  • If all your header files include their dependencies before doing anything else, then you don't need to decide order *at all*. At least, not until you create a genuine circular dependency, at which point you need to break the cycle no matter what you're doing. – Steve Jessop May 09 '11 at 17:44
  • no, the problem with that approach is that it prevents some important uses of variables. The order should be decided, or you can't use A a; as data member and inheritance at the same time. Both require one side of the order and together they require that the order is correct. – tp1 May 09 '11 at 17:49
  • I don't know what you mean by "data member and inheritance at the same time". – Steve Jessop May 09 '11 at 17:51
  • data member and inheritance at the same time means basically class C : public B { A a; }; – tp1 May 09 '11 at 17:54
  • So where's the problem? `C.h` should contain `#include "A.h"` and `#include "B.h"`, in either order, before this class definition. – Steve Jessop May 09 '11 at 17:55
  • well, the problem is that if you split the code to each file separately, regognizing when your dependencies are going to form a graph is quite difficult. Obviously this will only work if it's a tree and you'll need forward declarations for every dependency which changes your tree into a graph. – tp1 May 09 '11 at 18:05
  • another comment on this: The c++ way of handling class dependencies are really a relation between 3 complex data structures: graph, tree, and order of classes as provided for the compiler. – tp1 May 09 '11 at 18:13