2022 06 10_controller_and_view_ownership - DmitrySemikin/dsemikin_notes GitHub Wiki

Problem of view - controller ownership

Copied from here:

Statement 1: creating circular references is not good. It should normally be avoided. The need to create circular reference means, that

  • either one (or more) of the classes contains multiple functions, which could be separated to different classes.
  • or the two (or more) classes just represent single entity, which is not really separable and should be present by single class.

Now to our situation with the GUI:

When we implement our GUI classes (no matter, if it is a application window, some dialog or a panel, which groups several standard gui elements we always have this situation: Normally we want to split the code of the gui element into two parts (especially in swing, where we implement graphical layout directly in Java code and not with help of e.g. XML or so). First part is "view", which is responsible for graphical part. Second part is "view-controller", which is basically the logical part of the GUI - it's interface to the rest of the code.

Now, GUI components are special (compared to e.g. with "business-logic components") in that it has multiple triggers/entry points (or even better to say: entry directions): first, some activity can be triggered by the user. Normally this kind of trigger activates then some "activity" in some "business logic component", i.e. it should call some method in controller. To make this possible gui need to have to be able to access controller. On the other hand, some business component may need to change the state of the GUI (e.g. triggering some other GUI function make change of state of the applicaiton, which needs to disable certain GUI elements). For this to happen business logic component needs to be able to access the GUI.

Easy solution for this is "circular dependency". Then we have bi-directional communication channel. But at the beginning we told, that it is not good to have it.

So what would be the alternative:

We could instead build the tree of all gui elements and make each element available from the root. And then make the root available either to all controllers via e.g. injection (but in this case we would still have circular dependency - just the circle would be much larger and it would be less obvious). Alternatively we could make root gui element available globally (e.g. as Singleton). This would make "circulr reference problem" go away. But it has another significant problem: to use this solution we would need each controller, which needs to access some GUI element to know exact path to this GUI element. This make the structure of the GUI hardcoded to the controller code, which makes it very non-flexible.

After giving this whole thing some thought, I came to the conclusion, that having circular reference in this case is "less evil", then hardcoding the GUI structure.

Of course, the reference form GUI to controller should be done in the form of registering listeners. And then, if we think about it, when we register some listener, we don't need to "keep it alive", so we may just keep the weak references to the listeners and only call the callbacks of the objects, which are still alive.

This is the way, I am going to continue with this project.

Probably I could create some helper object, which would do the housekeeping of working with weak references.

More layers - more problems

Now, if we think about View and View-Model (or View-Controller) as "graphical" and "logical" interfaces of certain GUI elements... First, the frameworks, which provide XML (or some other declarative) way of representing GUI layout, then off-load "View" part to this representation, so usually you don't need to have explicitly corresponding class (or it is being generated by the framework).

Second, such composite GUI elements can be either specific (e.g. some particular dialog or panel) or generic - some panel, which may be reused in different circumstances.

Note, that in both cases above we don't want view-controller to modify the state of the application (either persistent or not persistent). Alternatively we could say, that we don't use this layer to modify data of our application (or model of our application).

At this point it gets tricky again. E.g. is the state of GUI are "data" or "application state", which needs its own controller, or is it part of GUI, so it can be controlled from the GUI-levels (view or view-controller)?

Now if we go deeper and consider the actual data, which we process in our application, then it may be the case, that particular data does not have particular GUI (or the only one GUI). For example, if we work with online shop and have an "Order" as a piece of data there, then most likely we will have multiple GUI pieces, which work with this data. So most likely we want to have the controller, which works with this data: order-controller.

But then is the question, what provides the connection between "data-controller" (order-controller in our example) and the view controller for particular gui element (e.g. panel to manage orders). So we would need one more layer here. So we end up with the following layers:

  • View (code or XML/declarative style)
  • View-Controller - code, which provides API (and correspondingly logical interface) of this piece of GUI.
  • Feature-Controller (or GUI connector) - provides connection between particular piece of GUI and controllers, which actually modify the data. This controller implements actual logic of reactions to particular actions of the user with GUI. It uses capabilities exposed by data-controller (see next level) as building blocks to implement the logic. In fact probably I look at this layer wrongly. It is not layer of the GUI, but rather, it is the core, which actually implements the appliciton logic. For this it may have one or more GUI pieces and also access data-controllers.
  • Data-controllers - implement actual work with data. Basically those are an API for work with application data, i.e. it provides the building blocks to implement the application logic.
  • Further layers are lower level particular mechanics of working with data (e.g. persistency in DB, or in File or management of objects in memory if needed etc...).

Now, if we talk about the ownership, then my convention would be like this:

  • feature-controllers are the main thing. They should own each other and the GUI.
  • If callbacks are needed, it is OK to have circular references, but the callback references should be weak.
  • feature-controllers should own view-controllers, which in turn own views (probably with reversed weak callbacks).
  • also the feature controllers should own the data-controllers.

Note, that even though we discussed the ownership, but it rather defines, who controls, when the objects can be deleted. It does not imply though, that owner should create the owned object. Owned objects may be provided to the owner using dependency injection or with some factories (which in turn can be injected).

Generally it is also fine, if the whole infrastructure of the objects of all levels form a static network, which basically never changes: all the objects are created and interconnected on application start and then those connections are never changed. This does not contradict to the notion of ownership (and conventions, which we chose above). But if the occasion comes, when some piece of the GUI or some data-connection classes need to be created in runtime, then our conventions bring some order into how it should be done.

One more thing: in different types of GUI (e.g. Desktop and Web) the connection and callback mechanisms between the two can differ significantly, which should be taken into account.

By the way, this model should be usable not only for GUI, but also for other interaction mechanisms (e.g. REST API etc.).

view-controllers

View-controllers should be an interface between piece of GUI and the rest of the application. This interface is provided in the form of:

  • list of methods available for the rest of application to influence the GUI (update it's model and state) or to query it's state.
  • list of methods to register callbacks to the events, which may be triggered by this piece of the GUI.

Root-Object (or Root Controller)

There should be root-object, which has reference to the rest of the application objects (feature-controllers).

Most likely this object should hang on the "main window" in normal desktop application, but there can be also different possibilities. Question: how would it be in Android app?

It is questionable, if this object should be called "controller", because it will most likely not implement any logic.

Passing messages (events) between different controllers

How to pass some message from one controller to parallel controller? E.g. if we updated data and want other GUI components to be updated.

It is possible to register listeners directly (one controller to another), but I think, this way it is too hard to trace.

So my solution will be to pass events to parants and from parents to children (even though it may require additional ceremony).

In this constellation the root controller will at least provide the function of conductor of events.

Requirements are specified for GUI representation

Interestingly, even though we try to keep GUI secondary and to make the application logic abstract, but the user-functionality, which is described in the requirements is usually specified in terms of GUI, which is natural. So design of feature-controllers and further data-controllers is to certain extent the "reverse engineering" for those GUI-requirements.

⚠️ **GitHub.com Fallback** ⚠️