0

I am trying to abstract some functionality from my desktop application (JavaFX) for changing views.

So, what I am thinking is:

Navigator interface with methods like goToAccountsOverview(), goToAccountDetails(Account account) etc. The implementation will depend on a ViewFactory to create the Views and a DisplayingViewChanger to change the view that is being displayed.

There will be multiple navigator interfaces depending on the state of the application, starting with:

  • NotConnectedStateNavigator (goToConnectToDatabase(), goToCreateDatabase() etc)

  • ConnectedStateNavigator (goToAccountsOverview(), goToAccountDetails(Account account) etc)

So each state will have its own navigator and view factory and a common view changer. The view factories and navigators will not share a common interface (createView(ViewEnum v) and goToView(View view) respectively).

While implementing this I run into a problem. An example of the problem is this:

In the AccountsOverviewView there is a table with all the accounts. Each account has a button to view the details. Pressing that button should navigate to the AccountDetailsView with the ViewModel of the account (I am using some sort of MVVM).

To do that it would call connectedStateNavigator.goToAccountDetails(accountVM) which would call viewChanger.changeView(connectedStateViewFactory.createAccountDetailsView(accountVM)).

So the AccountsOverviewView needs the ConnectedStateNavigator, which needs the ConnectedStateViewFactory, which needs to be able to construct the original AccountsOverviewView, which needs the ConnectedStateNavigator etc.

Some code to explain it better:

public class AccountsOverviewView {
    public AccountsOverviewView(ConnectedStateNavigator navigator, ...) {
        (...)
        accountDetailsButton.onClick(e -> navigator.goToAccountDetails(account));
        (...)
    }
}

public class ConnectedStateNavigator {
    public ConnectedStateNavigator(ConnectedStateViewFactory viewFactory, ...) { ... }  

    public void goToAccountDetails(Account account) {
        viewChanger.changeView(viewFactory.createAccountDetailsView(account));
    }
}

public class ConnectedStateViewFactory {
    public ConnectedStateViewFactory(ConnectedStateNavigator navigator, ...) { ... }

    public View createAccountsOverviewView() {
        return new AccountsOverviewView(new AccountsOverviewViewModel(navigator, ...));
    }

    public View createAccountDetailsView(Account account) { ... }
}

How could I design this better to avoid this cyclic reference? Is using a setter or an asynchronous event system the only solution?

3
  • While I feel that it is probably possible to simplify this design somehow, I don't know enough about the system you are building, so I won't go into that; regarding the specific issue you asked about, you could modify the constructor of ConnectedStateNavigator and replace the viewFactory parameter with a lambda that takes in a navigator, calls the constructor, and returns an instance of the factory (Function<ConnectedStateNavigator,ConnectedStateViewFactory> factoryCreator). You'd then just do viewFactory = factoryCreator.apply(this) in the body. Commented Jul 19, 2019 at 5:14
  • @FilipMilovanović Hmm, I understand what you mean but isn't this just a fancier version of a setter? I have seen it used in other projects but I am not sure of what this acomplishes
    – alexk745
    Commented Jul 19, 2019 at 5:28
  • With a setter, you remove the dependency from the constructor of the view factory (so it becomes less explicit); then, when the object is instantiated, it's not in a usable state until you pass the dependency to the setter. A lambda here essentially acts as a single-method factory for the viewFactory, and returns a ready-to-use, complete object; it's essentially breaking the circular dependency of the constructors (in the context of object creation). But again, maybe a better way to go about it is to reexamine the problem, find some new point of view, and redesign things. Commented Jul 19, 2019 at 6:12

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.