Doesn't Sam's solution go against Dependency Inversion?

From the image below, we see that GameInstance and MainMenu should have includes from MainMenuInterface and NOT each other.

Yet, to do his dependency injection, he includes MainMenu.h in GameInstance.cpp.


Furthermore, he alters the code in GameInstance.cpp to use UMainMenu.

As a reminder, Dependency Inversion says

High-level modules should not depend on low-level modules. Both should depend on abstractions [interfaces].

1 Like

You are of course correct but only partially so. What is being decoupled is the functionality between menu and instance (i.e. join and quit) but the instance still needs to be able to create the menu. The only way around this would be to create a factory to create the menu object as a base widget or a type inherited from that that implements further interface methods.

The game interface still needs a reference to a menu somehow and this shows how the interface could be used for this purpose.

Admittedly more work is needed here.

If you’ve ever worked on solutions where code runs into 100’s of thousands of lines of code or more, you should also understand that what a textbook or indeed a website says something should be and what actually is done in implementation are two completely different things. Working on small projects you can often be idealistic and good architecture does help large projects but nothing is perfect.

Also, what is interesting and I see this repeatedly is the use of the term Interface in C++ - C++ does not have interfaces. This is just a label which means class without function, or to use the correct C++ term, a pure abstract.

What I would do here in this instance to handle further separation is to create a static factory method within the “Interface” which returns an instance of the menu as an abstract type. It may involve adding a few more methods but it is possible to create further abstraction. In this case, Abstraction is a better term than Dependency Inversion.

I’d like to share my solution. I’ve hidden all the lines not relevant to my interface setup. Would love to hear feedback from anyone if it’s not too much to ask.

I will be showing the contents of 4 of my files:
MyMainMenuInterface.h
MyGameInstance.h
MyGameInstance.cpp
MainMenu.cpp

MyMainMenuInterface.h

// other code that should be here

class CUSTOMUI_API IMainMenuInterface
{
	// other code...
public:

	UFUNCTION(BlueprintNativeEvent)
	void Host();
};

// NOTICE that I've elected to use the BlueprintNativeEvent specifier
// this is important bc otherwise I wouldn't be able to use "Execute_FnName" in MainMenu.cpp
// it also affects "Host" declaration in MyGameInstance.h

MyGameInstance.h

#include "CustomUI/MainMenuInterface.h"

UCLASS()
class GDTV_API UMyGameInstance : public UGameInstance, public IMainMenuInterface
{
	// other code that should be here

	void LoadMenu();

	virtual void Host_Implementation();

private:

	UPROPERTY(EditDefaultsOnly, SimpleDisplay, Category="Custom", meta=(AllowPrivateAccess))
	TSubclassOf<class UUserWidget> MainMenuClass;

MyGameInstance.cpp

// other code that goes here

void UPuzzlePlatformGameInstance::LoadMenu()
{
    UUserWidget* MainMenu = CreateWidget<UUserWidget>(this, MainMenuClass);
    if(!ensure(MainMenu != nullptr)) return;
    MainMenu->AddToViewport();
    // all other management of MainMenu is done within MainMenu
    // it seems to have worked out for me so far

}

void UPuzzlePlatformGameInstance::Host_Implementation()
{
    // implementation
}

MainMenu.cpp

// other code
#include "MainMenuInterface.h"

void UMainMenu::HostGame()
{
    UGameInstance* MyGameInstance = GetGameInstance();
    if(!ensure(MyGameInstance != nullptr)) return;
    if(MyGameInstance->Implements<UMainMenuInterface>())
    {
        // other code

        RemoveFromParent();
        IMainMenuInterface::Execute_Host(MyGameInstance);
    }
}

With this code, MyGameInstance and MainMenu do not include each other and MainMenu calls Host through the interface. Of course for this to work, the end user’s game instance must be of class UMyGameInstance (or a subclass of it) AND the end-user’s main menu widget blueprint must inherit from UMainMenu.

EDIT: I added some code to address the latest contention that the model I’m going with (having MainMenu and MyGameInstance not have includes of each other) doesn’t work. As far as I can tell, it works.

In theory this will work. It doesn’t address how the menu is created in the first place which is why the class is referred to in the game instance. This would still have to be abstracted away and the abstract class could be the place to add a static which creates the menu. This again involves adding include which breaks the model you’re trying to go with.

Privacy & Terms