Consider the following GUI screen: enter image description here
When user selects a person from PersonListView
, EditPersonView
should show person's first name and last name and allow the user edit. So, I end up with the following UML class Diagram where each Java package represents the layer of the class.
My question is in SelectedPerson
and if this is an MVP "model" class. Should this be part of my application layer? Isn't a presentation concern? The reason I added there is so the 2 presenters can observe it. When user selected an element from the list widget, it gets updated and EditPersonViewPresenter
refreshes the 2 fields and "apply" button "Enability".
Class Persons
is another model, responsible for persons CRUD operations and it makes perfect sense to be part of the application layer (should it be in domain?). It is also responsible to notify its observers that a person had a CRUD operation on it. So, when "Apply" is pressed, PersonListViewPresenter
as an observer to Persons is capable to refresh the list and show the new first/last name in the list.
Long story short, the two presenters communicate through Persons
and SelectedPerson
models. Assume this is a "correct" approach.
Now, the devil comes.
Selection is available only when there are no unsaved changes in the EditPersonView
. If the person in record state is named "Jackie Chan" and user edits to "Hackie Chan", the selection is disabled until the user clicks apply or restores the fields to "Jackie" and "Chan".
How PersonListViewPresenter
can know whether there are unsaved changes in EditPersonView
?
According to the approach taken till now, a new model "EditPersonState" must be added in the application layer and follow the same approach. EditPersonViewPresenter
updates the EditPersonState
model and PersonListViewPresenter
observes it and operate accordingly (disable the selection in list). But, if I have X forms and multiple presenters are interesting in, I will have my application layer with X of such models (that exist only to synchronize presenters). Should it be that way?
On the other hand, the use case for example, is "User can delete the selected person" so having a SelectedPerson
model in application layer could allow me to put the Delete operation (of CRUD) there and make the use case(s) more "visible".
As an alternate solution I thought, I could let this state in the view. The view will be treated as "the view" and then keep a hierarchy of views and child views. Each presenter depend on "the view" (say MainView) and then observes any of the interesting (child)views. So SelectedPerson
model does not exist neither in application layer, neither in presenter layer. It exists in the child view. Persons
model remain (in app layer) to do the CRUD. Uml class Diagram of this approach:
But in this approach there is no one-to-one relationship between a view and a presenter. Not that is is a law, but maybe one day if I have 50 different views I won't be able to know which presenters touches which (proportion of a) view.
What am I missing?
Finally, one thought of mine stands in the following. Martin Fowler in the same page states the following:
Session State is data that the user is currently working on. Session state is seen by the user as somewhat temporary, they usually have the ability to save or discard their work.
So, based on that statement, the person selection is session state (?). According to Craig Larman in Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development there is the following figure:
If Larman's session state is the same thing as to Fowler's session state, then my current approach, SelectedPerson
in application layer agree with them.
2 Answers 2
I believe you are making your design more complicated than needed by having two Presenters. I would design a system like this with a single Presenter that manages 2 Views (or, if you want, 1 main View corresponding to the GUI window, which contains 2 sub-Views corresponding to each panel).
In addition, the Presenter would also have a Mediator that is connected to both (sub-)Views and makes sure that the current selection from the list view is communicated to the edit view and that the "unsaved changed" status of the edit view causes the selection in the list view to be frozen.
The Presenter then only has to deal with the actions that require an actual action from the application, like applying changes.
-
having two Presenters. Of course these views are not that small in real application. They are views with many fields or so. If I have only one Presenter, it will probably end up a God class with lack of cohesion (there are other context related to list view, not only persons). Presenter would also have a Mediator, So its not the presenters responsibility to manage the disabled selection? This mediator belongs to presentation layer or view layer? Could you also provide an example in pseudocode (or UML?). About your last sentence, a presenter acts only when model is going to be manipulated?George Z.– George Z.2021年05月21日 21:36:47 +00:00Commented May 21, 2021 at 21:36
-
Also note that I try to follow a Passive View approach. The reason for that is testability. I would like to test only my presenters and leave the view untested. In my current design, testing only the presenter with a Test Double as view gives me the good sign that "everything works".George Z.– George Z.2021年05月21日 21:38:40 +00:00Commented May 21, 2021 at 21:38
You should think of any MVP/MVC complex as a single "component" with its connection to the outside world through its Presenter/Controller. MVP/MVC is all about controlling dependencies, coupling and visibility, so each of these components should be considered a black box outside of anything declared in the public interface of its Controller/Presenter.
Strictly speaking, you shouldn't have anything in a Model that isn't used internally by the View or Controller. So in this example, if you had something like "Phone Number" in your main table, but not in your edit window, then you'd need a separate model for the edit window which was different from the table row model. Your Controller for the edit window could observe the currently selected table row model, but then it would need to translate it into an internal Model for the edit window.
It's really contextual whether or not the table row model could/should be used as a component of the edit window model in any case. If the two windows really are meant to always work together, then it's probably OK. Otherwise it's best to create a special purpose class to hold the fields that you need from the table row model and make that class part of the Model for the edit screen. Then the controller for the table window would maintain an observable object of that class that would be passed to the Controller for the edit window in its constructor.
The EditPersonState
property should NOT be part of the Model for the edit window. That property should be created by the Controller for the table window, and passed to the edit window Controller as a parameter in its constructor. Then it's that Controller's responsibility to ensure that this state property is kept synchronized with what's happening in the edit screen.
The constructor for the edit window would look like this:
public EditWindow(PersonEditObservableData personData, ObservableEditState editState) {}
Now it's up to the list view controller to keep it's reference to the personData
synchronized with the list selection, and it's up to the edit window controller to keep it synchronized with its screen data AS APPROPRIATE. It's up to the edit window controller to ensure that editState is synchonized with the current status of it's model.
BTW, keeping the personData
separate from the Model for the edit screen makes it somewhat easier to control editState
. Put a ChangeListener on personData
, and any time it's triggered, copy the fields into the Model. Then set the editState
so that it reflects whether any of the fields in the Model are different from those in personData
.
There's other questions here too. Is there a persistence layer? If there is, then which controller/presenter is responsible for calling it? In a real world application, it's highly likely that the list window row model is just enough data to identify a Person, and the edit window would probably connect to the database and pull a lot more detail and populate its model from that.
It's possible to have "Main" controller which handles all of the persistence layer activity. In that case, the PersonEditObservableData
would need to be expanded to hold all that information. Additionally, you'll need to provide some sort of Consumer to handle the save request from the edit screen.
The problem with this is that you end up with a monolithic architecture where your "Main" controller has all kinds of functionality to support dependent components. It's probably best to think of each screen as a complete db ---> gui mini-application that can stand by itself.
Explore related questions
See similar questions with these tags.
Persons
is a service. A presenter gets the selected person and passes it toPersons
service. Notice how persons stand. It ispersons.delete(p)
notpersons.delete()
with association to selected person. I like the factPersons
stays as a (notifier) service with more business logic. Say I keep onlyPersons
, and selected person stays in presentation, howEditPersonViewPresenter
finds it in order topersons.update(selPerson)
?