Skip to content

Commit d01c8e2

Browse files
committed
Integrate mediator into composer
1 parent c163340 commit d01c8e2

18 files changed

+240
-70
lines changed

README.md

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ three layers in every element. In other words, a component does not violate MVVM
130130
communicate exclusively through data binding and observable properties.
131131

132132
In addition to the `ComponentViewModel` and `ComponentView`, a component always has a `ComponentDescriptor` (which
133-
is provided by the framework and normally does not require custom implementation) and may include two optional
134-
classes: `ComponentHistory` and `ComponentComposer`.
133+
is provided by the framework and normally does not require custom implementation) and may include three optional
134+
classes: `ComponentHistory`, `ComponentComposer`, `ComponentMediator`.
135135

136136
The `ComponentDescriptor` represents the internal metadata and platform-level state of a component. The descriptor
137137
acts as a technical identity card, containing all framework-related information while keeping it completely separate
@@ -144,8 +144,8 @@ exclusively between the `ComponentViewModel` and the `ComponentHistory`. When th
144144
transitions to `DEINITIALIZED`, data from the `ComponentViewModel` is saved back to the `ComponentHistory`. The volume
145145
of state information that is restored and persisted is defined by the `HistoryPolicy` enum.
146146

147-
The `ComponentComposer` is responsible for managing child components and their composition
148-
(see [Composite Component](#composite-component)).
147+
The `ComponentComposer` is responsible for managing child components and their composition, while the `ComponentMediator`
148+
allows `ComponentViewModel` to interact with the `ComponentComposer` (see [Composite Component](#composite-component)).
149149

150150
### Component Lifecycle <a name="component-lifecycle"></a>
151151

@@ -197,8 +197,9 @@ hierarchical and non-cyclic.
197197
### Composite Component <a name="composite-component"></a>
198198

199199
Components can be either simple or composite. A simple component has no child components. A composite component has
200-
one or more child components. Working with a composite component is one of the most challenging parts of using
201-
the platform for the following reasons:
200+
one or more child components. The use of `Composer` and `Mediator` is required only for components that manage children
201+
or dynamically create other components, such as dialogs, panels, or complex containers. Working with a composite
202+
component is one of the most challenging parts of using the platform for the following reasons:
202203

203204
1. MVVM Gap. MVVM does not specify how child components should be created, how their lifecycle should be managed, or
204205
how they should be composed.
@@ -212,58 +213,75 @@ since names like `SomeComponentViewComposer` and `SomeComponentViewModelComposer
212213
must be created: `ChildView` extends `ParentView`, `ChildViewModel` extends `ParentViewModel`, `ChildComposer` extends
213214
`ParentComposer` etc.
214215

215-
In MVVM4FX, the solution for working with composite components is implemented as follows.
216-
217-
1. Separate Composer Interfaces. In the `View` and `ViewModel` classes of a composite component, nested `Composer`
218-
interfaces are defined: `View.Composer` contains the methods that the `View` will use to work with the `Composer`,
219-
and `ViewModel.Composer` contains the methods that the `ViewModel` will use to work with the `Composer`.
220-
The need to use interfaces is explained, firstly, by the requirement to test the component independently of other
221-
components, and secondly, by the fact that the `Composer` must know about both the `View` and the `ViewModel`, which
222-
would otherwise violate MVVM principles.
216+
In MVVM4FX, the solution for working with composite components is implemented using two classes: `Composer` and `Mediator`:
223217

218+
1. `Mediator`. This is the interface that the `ViewModel` uses to interact with the `Composer`. The need for an
219+
interface is driven by two factors: first, it allows the `ViewModel` to be tested independently of other components;
220+
second, the `Composer` must know about both the `View` and the `ViewModel`, while the `ViewModel` must not know about
221+
the `View`.
224222

225223
```java
226-
public class FooViewModel extends AbstractChildViewModel {
227-
228-
public interface Composer extends ComponentViewModel.Composer {...}
224+
public interface FooMediator extends ChildMediator {
229225

230226
...
231227
}
232228

233-
public class FooView extends AbstractChildView<FooViewModel> {
234-
235-
public interface Composer extends ComponentView.Composer {...}
229+
public class FooViewModel extends AbstractChildViewModel {
236230

237231
...
232+
233+
@Override
234+
public FooMediator getMediator() {
235+
return (FooMediator) super.getMediator();
236+
}
238237
}
239238
```
240-
2. Unified Composer Implementation. A single `Composer` class serves as the main implementation, which directly
241-
implements the `View.Composer` interface and contains a nested class implementing the `ViewModel.Composer` interface.
242-
The composer holds a reference to the associated `View` instance, allowing both the main class and nested class to
243-
access view-specific functionality while maintaining proper separation of concerns.
239+
240+
2. `Composer`. This class contains the methods that manage the entire lifecycle of child components, as well as the
241+
methods the `View` uses to interact with the `Composer`. In addition, it defines a non-static inner class that
242+
implements the corresponding `Mediator`.
244243

245244
```java
246-
public class FooComposer extends AbstractChildComposer<FooView> implements FooView.Composer {
245+
public class FooComposer extends AbstractChildComposer<FooView> {
247246

248-
protected class ViewModelComposer
249-
extends AbstractChildComposer.ViewModelComposer
250-
implements FooViewModel.Composer {...}
247+
protected class Mediator extends AbstractChildComposer.Mediator implements FooMediator {...}
251248

252249
...
250+
251+
@Override
252+
protected FooMediator createMediator() {
253+
return new FooComposer.Mediator();
254+
}
255+
}
256+
257+
public class FooView extends AbstractChildView<FooViewModel> {
258+
259+
...
260+
261+
@Override
262+
public FooComposer getComposer() {
263+
return (FooComposer) super.getComposer();
264+
}
265+
266+
@Override
267+
protected ComponentComposer<?> createComposer() {
268+
return new FooComposer(this);
269+
}
253270
}
254271
```
255-
3. Composer Assignment. To assign a `Composer` to the `View` and `ViewModel`, use the public method
256-
`AbstractComponentView#setComposer(...)`. There is also the method `AbstractComponentView#createComposer()`, which can
257-
be overridden to automate `Composer` creation during construction.
272+
273+
3. To assign a `Composer` to a `View` and a `Mediator` to a `ViewModel`, use the public method
274+
`AbstractComponentView#setComposer(...)`. There is also a `AbstractComponentView#createComposer()` method, which can
275+
be overridden to automatically create the `Composer` during construction.
258276

259277
Advantages of this approach:
260278

261-
* Strict Separation. Using the `View.Composer` and `ViewModel.Composer` interfaces enforces a clear separation of
262-
layers according to MVVM and simplifies testing.
263-
* Clean Architecture. The `Composer` class takes over all work related to managing child components, keeping the
264-
`View` and `ViewModel` free from logic that does not belong to them.
265-
* MVVM Compliance. The `Composer` class is where the `ViewModel`’s ability to initiate the addition or removal of
266-
a component is implemented without violating MVVM principles.
279+
* Strict Separation. Using a `Composer` together with a `Mediator` enforces a clear separation of layers according to
280+
MVVM and simplifies testing.
281+
* Clean Architecture. The `Composer` centralizes all logic related to managing child components, keeping the
282+
`View` and `ViewModel` free from responsibilities that do not belong to them.
283+
* MVVM Compliance. The `Mediator` interface defines how a `ViewModel` can initiate the addition or removal of a
284+
component without violating MVVM principles.
267285

268286
### When to Create a Component? <a name="when-to-create-component"></a>
269287
* The element has independent testable state or business logic that can exist without a `View`.

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractChildComposer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@
2222
*/
2323
public abstract class AbstractChildComposer<T extends ChildView<?>> extends AbstractParentComposer<T> {
2424

25-
protected abstract class ViewModelComposer extends AbstractParentComposer.ViewModelComposer {
25+
protected abstract class Mediator extends AbstractParentComposer.Mediator implements ChildMediator {
2626

2727
}
2828

2929
public AbstractChildComposer(T view) {
3030
super(view);
3131
}
32+
33+
@Override
34+
public ChildMediator getMediator() {
35+
return (ChildMediator) super.getMediator();
36+
}
3237
}

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractChildView.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public ParentView<?> getParent() {
4242
return this.parent.get();
4343
}
4444

45+
@Override
46+
public ChildComposer<?> getComposer() {
47+
return (ChildComposer<?>) super.getComposer();
48+
}
49+
4550
@Override
4651
protected void addListeners(T viewModel) {
4752
super.addListeners(viewModel);

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractChildViewModel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ public ParentViewModel getParent() {
4141
return this.parent.get();
4242
}
4343

44+
@Override
45+
public ChildMediator getMediator() {
46+
return (ChildMediator) super.getMediator();
47+
}
48+
4449
ReadOnlyObjectWrapper<ParentViewModel> parentWrapper() {
4550
return parent;
4651
}

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractComponentComposer.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@
2222
*/
2323
public abstract class AbstractComponentComposer<T extends ComponentView<?>> implements ComponentComposer<T> {
2424

25-
protected abstract class ViewModelComposer implements ComponentViewModel.Composer {
25+
protected abstract class Mediator implements ComponentMediator {
2626

2727
}
2828

2929
private final T view;
3030

31-
private final ComponentViewModel.Composer viewModelComposer;
31+
private final ComponentMediator mediator;
3232

3333
public AbstractComponentComposer(T view) {
3434
this.view = view;
35-
this.viewModelComposer = createViewModelComposer();
35+
this.mediator = createMediator();
3636
}
3737

3838
@Override
39-
public ParentViewModel.Composer getViewModelComposer() {
40-
return viewModelComposer;
39+
public ComponentMediator getMediator() {
40+
return mediator;
4141
}
4242

4343
@Override
@@ -54,5 +54,5 @@ protected final T getView() {
5454
return view;
5555
}
5656

57-
protected abstract ComponentViewModel.Composer createViewModelComposer();
57+
protected abstract ComponentMediator createMediator();
5858
}

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractComponentView.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public abstract class AbstractComponentView<T extends AbstractComponentViewModel
2929

3030
private T viewModel;
3131

32-
private ComponentView.Composer composer;
32+
private ComponentComposer<?> composer;
3333

3434
public AbstractComponentView(T viewModel) {
3535
this.viewModel = viewModel;
@@ -91,27 +91,29 @@ public final void deinitialize() {
9191
}
9292
}
9393

94-
public ComponentView.Composer getComposer() {
94+
public ComponentComposer<?> getComposer() {
9595
return composer;
9696
}
9797

9898
/**
99-
* Assigns the given {@link ComponentComposer} to this view and propagates its corresponding
100-
* {@link ComponentViewModel.Composer} to the view model.
99+
* Sets the {@link ComponentComposer} for this view and updates the view model with the corresponding
100+
* {@link ComposerMediator}.
101101
*
102-
* <p>Since {@link ComponentComposer} extends {@link ComponentView.Composer}, it contains all composition logic
103-
* required by the view itself, while also providing a dedicated composer for the associated view model.
102+
* <p>When a non-{@code null} composer is provided, the view becomes associated with that composer, and the view
103+
* model receives the mediator obtained via {@link ComponentComposer#getMediator()}. This mediator enables
104+
* communication between the view model and the rest of the component through the composer.
104105
*
105-
* <p>If {@code composer} is {@code null}, both the view and the view model are detached from any composer.
106+
* <p>If {@code composer} is {@code null}, the view is detached from any composer, and the view model's mediator
107+
* is cleared as well, effectively disconnecting both layers from the component's composition mechanism.
106108
*
107-
* @param composer the composer instance to attach to this view; may be {@code null}
109+
* @param composer the composer to attach to this view, or {@code null} to detach
108110
*/
109111
public void setComposer(ComponentComposer<?> composer) {
110112
this.composer = composer;
111113
if (composer == null) {
112-
viewModel.setComposer(null);
114+
viewModel.setMediator(null);
113115
} else {
114-
viewModel.setComposer(composer.getViewModelComposer());
116+
viewModel.setMediator(composer.getMediator());
115117
}
116118
}
117119

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractComponentViewModel.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public abstract class AbstractComponentViewModel implements ComponentViewModel {
3737

3838
private ComponentHistory<?> history;
3939

40-
private ComponentViewModel.Composer composer;
40+
private ComponentMediator mediator;
4141

4242
public AbstractComponentViewModel() {
4343
this.descriptor = createDescriptor();
@@ -113,12 +113,12 @@ public void setHistoryProvider(HistoryProvider historyProvider) {
113113
this.historyProvider = historyProvider;
114114
}
115115

116-
public Composer getComposer() {
117-
return composer;
116+
public ComponentMediator getMediator() {
117+
return this.mediator;
118118
}
119119

120-
protected void setComposer(Composer composer) {
121-
this.composer = composer;
120+
protected void setMediator(ComponentMediator mediator) {
121+
this.mediator = mediator;
122122
}
123123

124124
protected void initialize() {

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractParentComposer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222
*/
2323
public abstract class AbstractParentComposer<T extends ParentView<?>> extends AbstractComponentComposer<T> {
2424

25-
protected abstract class ViewModelComposer extends AbstractComponentComposer.ViewModelComposer {
25+
protected abstract class Mediator extends AbstractComponentComposer.Mediator implements ParentMediator {
2626

2727
}
2828

2929
public AbstractParentComposer(T view) {
3030
super(view);
3131
}
3232

33+
@Override
34+
public ParentMediator getMediator() {
35+
return (Mediator) super.getMediator();
36+
}
3337
}

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractParentView.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ List<ParentView<?>> getChildren(ParentView<?> parent) {
6666
};
6767
}
6868

69+
@Override
70+
public ParentComposer<?> getComposer() {
71+
return (ParentComposer<?>) super.getComposer();
72+
}
73+
6974
@Override
7075
protected void addListeners(T viewModel) {
7176
super.addListeners(viewModel);

mvvm4fx-core/src/main/java/com/techsenger/mvvm4fx/core/AbstractParentViewModel.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ List<ParentViewModel> getChildren(ParentViewModel parent) {
6262
};
6363
}
6464

65+
@Override
66+
public ParentMediator getMediator() {
67+
return (ParentMediator) super.getMediator();
68+
}
69+
6570
ObservableList<ChildViewModel> getModifiableChildren() {
6671
return modifiableChildren;
6772
}

0 commit comments

Comments
 (0)