Question

I'm developing an UI application where I ran into an issue with a cyclic dependency. Here is the simplified code, to explain the problem.

#include <list>

class UiStyle;
UiStyle* CreateStyle();

class UiWindow {
public:
    virtual void Draw() = 0;
};

class UiMenu : public UiWindow {
public:
    void Draw() override {
        // Some UI drawing

        // How should I implement changing style from here?
    }
};

class UiApplication {
public:
    void Draw() {
        for (UiWindow* window : this->windows)
            window->Draw();
    }

protected:
    void SetStyle(UiStyle *style);

    void AddWindow(UiWindow* window) {
        this->windows.push_back(window);
    }

private:
    std::list<UiWindow*> windows;
};

class UiMainApplication : public UiApplication {
public:
    void Initialize() {
        this->SetStyle(CreateStyle());

        this->AddWindow(new UiMenu());
    }
};

Explanation:

  • UiStyle - application style class.
  • CreateStyle - method that creates some UI style.
  • UiWindow - interface of an UI window (not to be changed)
  • UiMenu - some implemented UI window from where I need to change a style.
  • UiApplication - base application class that provides managing windows, styles, etc. (not to be changed)
  • UiMainApplication - implemented application that adds real windows, sets real style.

The problem I face is about changing the application style from inside UiMenu. How can UiMenu call UiApplication::SetStyle?

Of course, I could pass UiMainApplication pointer to UiMenu, then create public method UiMainApplication::SetStyle that will allow to call UiApplication::SetStyle, and call UiMainApplication::SetStyle from UiMenu. But, as far as I know, it's bad practice, to have a bidirectional reference, like A -> B and B -> A. In my case, UiMainApplication would refer to UiMenu and UiMenu refer to UiMenuApplication.

Hence I think it's not the best way to implement it.

Hope for your help! I will be glad to any suggestions!

Was it helpful?

Solution

Cyclic references sometimes have their place, and before rejecting them as a solution, I think it is important to understand why and when they may become an issue, and if this issue really applies to your case.

For example, as you mentioned by yourself, one could solve this problem by introducing an ctor parameter UiMainApplication * to UiMenu. I would implement it this way:

class UIMenu
{
   UiMainApplication *_app;
public:
     UIMenu(UiMainApplication * app){
         _app=app;
     }
     void Draw() override {
          if(_app) {
              _app->SetStyle(...);
          }
          //...
    }
};


This would result in a cyclic dependency between UiMainApplication and UiMenu However, this is only a compile time dependency. At run time, you still can instantiate an UiMainApplication object without creating an UiMenu object first. It is also possible to create an UiMenu object without UiMainApplication, by passing a nullptr as parameter, if you need one for testing purposes.

You need to decide if this compile time dependency is acceptable for your case. It will forbid to place UiMenu in a reusable library module independend from UiMainApplication, but if you don't intend to do so, this may be perfectly fine.

Of course, it is possible to get rid of the compile time dependency, if necessary. For this, one can create a abstract new class SetStyleService with a virtual method SetStyle. This class definition can be part of the same library module where UiMenu is placed, or in a separate "interface" library. Then instead of expecting a pointer to UiMainApplication in the UiMenu ctor, you make it expecting a pointer to SetStyleService. UiMainApplication will have to be derived from SetStyleService and implement SetStyle accordingly. (Note the same behaviour can be accomplished by using function pointers and passing a pointer to SetStyle into UiMenu).

I would probably stick to the first approach as long as there is no real need for the second. Just because someone has written somewhere "bidrectional dependencies are bad practice" I would not follow such a recommendation thoughtlessly, but first check if the potential drawbacks of such a dependency really applies to my specific case.

Licensed under: CC-BY-SA with attribution
scroll top