mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-08-11 14:22:45 +00:00
* #2151 add module and pom.xml * #2151 add Calculator Actions * #2151 add remaining mvi pattern classes (actions already implemented) * #2151 add Main and unit tests * add README.md and class diagrams * #2151 add module and pom.xml * #2151 add Calculator Actions * #2151 add remaining mvi pattern classes (actions already implemented) * #2151 add Main and unit tests * add README.md and class diagrams * fixes for lint errors * #2151 add module and pom.xml * #2151 add Calculator Actions * #2151 add remaining mvi pattern classes (actions already implemented) * #2151 add Main and unit tests * add README.md and class diagrams * fixes for lint errors * use Lombok @Data decorator and decouple View from ViewModel * add comments and documentation * fix checkstyle, the smart switch syntax was breaking checkstyle, so I had to change it back
This commit is contained in:
@@ -0,0 +1,245 @@
|
|||||||
|
---
|
||||||
|
title: Model-View-Controller
|
||||||
|
category: Architectural
|
||||||
|
language: en
|
||||||
|
tags:
|
||||||
|
- Decoupling
|
||||||
|
- Encapsulation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
MVI is a derivation of the original MVC architectural pattern. Instead of working with a
|
||||||
|
proactive controller MVI works with the reactive component called intent: it's a component
|
||||||
|
which translates user input events into model updates.
|
||||||
|
|
||||||
|
## Explanation
|
||||||
|
|
||||||
|
> MVI is a Reactive Architecture Pattern which is short for Model -View-Intent.
|
||||||
|
It introduces two new concepts: the intent and the state.
|
||||||
|
UI might have different states — Loading State, Fetch Data State, Error State,
|
||||||
|
and user events are submitted in the form of an Intent.
|
||||||
|
|
||||||
|
* [Stateful Android Apps With MVI (MODEL — VIEW — INTENT)](https://medium.com/huawei-developers/stateful-android-apps-with-mvi-architecture-model-view-intent-d106b09bd967)
|
||||||
|
|
||||||
|
## Class diagram
|
||||||
|

|
||||||
|
|
||||||
|
**Programmatic Example**
|
||||||
|
|
||||||
|
CalculatorAction defines our Intent in MVI for user interactions. It has to be an interface
|
||||||
|
instead of enum, so that we can pass parameters to certain children.
|
||||||
|
```java
|
||||||
|
public interface CalculatorAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes identifying action trivial.
|
||||||
|
*
|
||||||
|
* @return subclass tag.
|
||||||
|
* */
|
||||||
|
String tag();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
CalculatorModel defines the state of our view or in out case, variable and output of the calculator.
|
||||||
|
```java
|
||||||
|
@Data
|
||||||
|
public class CalculatorModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current calculator variable used for operations.
|
||||||
|
**/
|
||||||
|
final Double variable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current calculator output -> is affected by operations.
|
||||||
|
**/
|
||||||
|
final Double output;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
CalculatorView will serve as a mock view which will expose potential user actions and
|
||||||
|
display calculator state -> output and current variable
|
||||||
|
```java
|
||||||
|
@Slf4j
|
||||||
|
public class CalculatorView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model param handling the operations.
|
||||||
|
* */
|
||||||
|
private final CalculatorViewModel viewModel = new CalculatorViewModel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display current view model output with logger.
|
||||||
|
* */
|
||||||
|
void displayTotal() {
|
||||||
|
LOGGER.info(
|
||||||
|
"Total value = {}",
|
||||||
|
viewModel.getCalculatorModel().getOutput().toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle addition action.
|
||||||
|
* */
|
||||||
|
void add() {
|
||||||
|
viewModel.handleAction(new AdditionCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle subtraction action.
|
||||||
|
* */
|
||||||
|
void subtract() {
|
||||||
|
viewModel.handleAction(new SubtractionCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle multiplication action.
|
||||||
|
* */
|
||||||
|
void multiply() {
|
||||||
|
viewModel.handleAction(new MultiplicationCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle division action.
|
||||||
|
* */
|
||||||
|
void divide() {
|
||||||
|
viewModel.handleAction(new DivisionCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle setting new variable action.
|
||||||
|
*
|
||||||
|
* @param value -> new calculator variable.
|
||||||
|
* */
|
||||||
|
void setVariable(final Double value) {
|
||||||
|
viewModel.handleAction(new SetVariableCalculatorAction(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, ViewModel handles the exposed events with the handleAction(event) method, which delegates
|
||||||
|
the specific handling to private methods. Initially calculator output and variable are equal to 0.
|
||||||
|
```java
|
||||||
|
public final class CalculatorViewModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current calculator model (can be changed).
|
||||||
|
*/
|
||||||
|
private CalculatorModel model =
|
||||||
|
new CalculatorModel(0.0, 0.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle calculator action.
|
||||||
|
*
|
||||||
|
* @param action -> transforms calculator model.
|
||||||
|
*/
|
||||||
|
void handleAction(final CalculatorAction action) {
|
||||||
|
switch (action.tag()) {
|
||||||
|
case AdditionCalculatorAction.TAG -> add();
|
||||||
|
case SubtractionCalculatorAction.TAG -> subtract();
|
||||||
|
case MultiplicationCalculatorAction.TAG -> multiply();
|
||||||
|
case DivisionCalculatorAction.TAG -> divide();
|
||||||
|
case SetVariableCalculatorAction.TAG -> {
|
||||||
|
SetVariableCalculatorAction setVariableAction =
|
||||||
|
(SetVariableCalculatorAction) action;
|
||||||
|
setVariable(setVariableAction.getVariable());
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter.
|
||||||
|
*
|
||||||
|
* @return current calculator model.
|
||||||
|
*/
|
||||||
|
public CalculatorModel getCalculatorModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set new calculator model variable.
|
||||||
|
*
|
||||||
|
* @param variable -> value of new calculator model variable.
|
||||||
|
*/
|
||||||
|
private void setVariable(final Double variable) {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
variable,
|
||||||
|
model.getOutput()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add variable to model output.
|
||||||
|
*/
|
||||||
|
private void add() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() + model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtract variable from model output.
|
||||||
|
*/
|
||||||
|
private void subtract() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() - model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiply model output by variable.
|
||||||
|
*/
|
||||||
|
private void multiply() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() * model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divide model output by variable.
|
||||||
|
*/
|
||||||
|
private void divide() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() / model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Applicability
|
||||||
|
Use the Model-View-Intent pattern when
|
||||||
|
|
||||||
|
* You want to clearly separate the domain data from its user interface representation
|
||||||
|
* You want to minimise the public api of the view model
|
||||||
|
|
||||||
|
## Known uses
|
||||||
|
A popular architecture pattern in android. The small public api is particularly powerful
|
||||||
|
with the new Android Compose UI, as you can pass a single method (viewModel::handleEvent)
|
||||||
|
to all Composables(parts of UI) as a callback for user input event.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
Pros:
|
||||||
|
* Encapsulation
|
||||||
|
* Separation of concerns
|
||||||
|
* Clear list of all possible user events
|
||||||
|
|
||||||
|
Cons:
|
||||||
|
* More boilerplate code compared to alternatives (especially in Java)
|
||||||
|
|
||||||
|
## Related patterns
|
||||||
|
MVC:
|
||||||
|
* [Trygve Reenskaug - Model-view-controller](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
|
||||||
|
* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
* [Model View Intent: a new Android Architecture Pattern](https://apiumacademy.com/blog/model-view-intent-pattern/)
|
||||||
|
* [MVI Architecture for Android Tutorial](https://www.kodeco.com/817602-mvi-architecture-for-android-tutorial-getting-started)
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
@@ -0,0 +1,74 @@
|
|||||||
|
@startuml
|
||||||
|
package com.iluwatar.model.view.intent.actions {
|
||||||
|
class AdditionCalculatorAction {
|
||||||
|
+ TAG : String {static}
|
||||||
|
+ AdditionCalculatorAction()
|
||||||
|
+ tag() : String
|
||||||
|
}
|
||||||
|
interface CalculatorAction {
|
||||||
|
+ tag() : String {abstract}
|
||||||
|
}
|
||||||
|
class DivisionCalculatorAction {
|
||||||
|
+ TAG : String {static}
|
||||||
|
+ DivisionCalculatorAction()
|
||||||
|
+ tag() : String
|
||||||
|
}
|
||||||
|
class MultiplicationCalculatorAction {
|
||||||
|
+ TAG : String {static}
|
||||||
|
+ MultiplicationCalculatorAction()
|
||||||
|
+ tag() : String
|
||||||
|
}
|
||||||
|
class SetVariableCalculatorAction {
|
||||||
|
+ TAG : String {static}
|
||||||
|
+ variable : Double
|
||||||
|
+ SetVariableCalculatorAction(variable : Double)
|
||||||
|
+ tag() : String
|
||||||
|
}
|
||||||
|
class SubtractionCalculatorAction {
|
||||||
|
+ TAG : String {static}
|
||||||
|
+ SubtractionCalculatorAction()
|
||||||
|
+ tag() : String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
package com.iluwatar.model.view.intent {
|
||||||
|
class App {
|
||||||
|
+ App()
|
||||||
|
+ main(args : String[]) {static}
|
||||||
|
}
|
||||||
|
class CalculatorModel {
|
||||||
|
+ output : Double
|
||||||
|
+ variable : Double
|
||||||
|
+ CalculatorModel(output : Double, variable : Double)
|
||||||
|
+ copy(output : Double, variable : Double) : CalculatorModel
|
||||||
|
}
|
||||||
|
class CalculatorView {
|
||||||
|
- LOGGER : Logger {static}
|
||||||
|
~ viewModel : CalculatorViewModel
|
||||||
|
+ CalculatorView()
|
||||||
|
~ add()
|
||||||
|
~ displayTotal()
|
||||||
|
~ divide()
|
||||||
|
~ multiply()
|
||||||
|
~ setVariable(value : Double)
|
||||||
|
~ subtract()
|
||||||
|
}
|
||||||
|
class CalculatorViewModel {
|
||||||
|
- model : CalculatorModel
|
||||||
|
+ CalculatorViewModel()
|
||||||
|
- add()
|
||||||
|
- divide()
|
||||||
|
+ getCalculatorModel() : CalculatorModel
|
||||||
|
~ handleAction(action : CalculatorAction)
|
||||||
|
- multiply()
|
||||||
|
- setVariable(variable : Double)
|
||||||
|
- subtract()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CalculatorView --> "-viewModel" CalculatorViewModel
|
||||||
|
CalculatorViewModel --> "-model" CalculatorModel
|
||||||
|
AdditionCalculatorAction ..|> CalculatorAction
|
||||||
|
DivisionCalculatorAction ..|> CalculatorAction
|
||||||
|
MultiplicationCalculatorAction ..|> CalculatorAction
|
||||||
|
SetVariableCalculatorAction ..|> CalculatorAction
|
||||||
|
SubtractionCalculatorAction ..|> CalculatorAction
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
|
||||||
|
The MIT License
|
||||||
|
Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.iluwatar</groupId>
|
||||||
|
<artifactId>java-design-patterns</artifactId>
|
||||||
|
<version>1.26.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>model-view-intent</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.iluwatar.model.view.controller.App</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license.
|
||||||
|
* Module model-view-viewmodel is using ZK framework licensed under LGPL
|
||||||
|
* (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.model.view.intent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model-View-Intent is a pattern for implementing user interfaces.
|
||||||
|
* Its main advantage over MVVM which it closely mirrors is a
|
||||||
|
* minimal public api with which user events can be exposed to the ViewModel.
|
||||||
|
* In case of the MVI every event is exposed by using a single method
|
||||||
|
* with 1 argument which implements UserEvent interface.
|
||||||
|
* Specific parameters can be expressed as its parameters. In this case,
|
||||||
|
* we'll be using MVI to implement a simple calculator
|
||||||
|
* with +, -, /, * operations and the ability to set the variable.
|
||||||
|
* It's important to note, that every user action happens through the
|
||||||
|
* view, we never interact with the ViewModel directly.
|
||||||
|
*/
|
||||||
|
public final class App {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To avoid magic value lint error.
|
||||||
|
*/
|
||||||
|
private static final double RANDOM_VARIABLE = 10.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program entry point.
|
||||||
|
*
|
||||||
|
* @param args command line args
|
||||||
|
*/
|
||||||
|
public static void main(final String[] args) {
|
||||||
|
// create model, view and controller
|
||||||
|
|
||||||
|
// initialize calculator view, output and variable = 0
|
||||||
|
var view = new CalculatorView(new CalculatorViewModel());
|
||||||
|
var variable1 = RANDOM_VARIABLE;
|
||||||
|
|
||||||
|
// calculator variable = RANDOM_VARIABLE -> 10.0
|
||||||
|
view.setVariable(variable1);
|
||||||
|
|
||||||
|
// add calculator variable to output -> calculator output = 10.0
|
||||||
|
view.add();
|
||||||
|
view.displayTotal(); // display output
|
||||||
|
|
||||||
|
variable1 = 2.0;
|
||||||
|
view.setVariable(variable1); // calculator variable = 2.0
|
||||||
|
|
||||||
|
// subtract calculator variable from output -> calculator output = 8
|
||||||
|
view.subtract();
|
||||||
|
|
||||||
|
// divide calculator output by variable -> calculator output = 4.0
|
||||||
|
view.divide();
|
||||||
|
|
||||||
|
// multiply calculator output by variable -> calculator output = 8.0
|
||||||
|
view.multiply();
|
||||||
|
view.displayTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Avoid default constructor lint error.
|
||||||
|
*/
|
||||||
|
private App() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.iluwatar.model.view.intent;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current state of calculator.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CalculatorModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current calculator variable used for operations.
|
||||||
|
**/
|
||||||
|
@Getter
|
||||||
|
private final Double variable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current calculator output -> is affected by operations.
|
||||||
|
**/
|
||||||
|
@Getter
|
||||||
|
private final Double output;
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.iluwatar.model.view.intent;
|
||||||
|
|
||||||
|
import com.iluwatar.model.view.intent.actions.AdditionCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.DivisionCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.MultiplicationCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.SetVariableCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.SubtractionCalculatorAction;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes changes to the state of calculator
|
||||||
|
* to {@link CalculatorViewModel} through
|
||||||
|
* {@link com.iluwatar.model.view.intent.actions.CalculatorAction}
|
||||||
|
* and displays its updated {@link CalculatorModel}.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Data
|
||||||
|
public class CalculatorView {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View model param handling the operations.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final CalculatorViewModel viewModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display current view model output with logger.
|
||||||
|
*/
|
||||||
|
void displayTotal() {
|
||||||
|
LOGGER.info(
|
||||||
|
"Total value = {}",
|
||||||
|
viewModel.getCalculatorModel().getOutput().toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle addition action.
|
||||||
|
*/
|
||||||
|
void add() {
|
||||||
|
viewModel.handleAction(new AdditionCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle subtraction action.
|
||||||
|
*/
|
||||||
|
void subtract() {
|
||||||
|
viewModel.handleAction(new SubtractionCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle multiplication action.
|
||||||
|
*/
|
||||||
|
void multiply() {
|
||||||
|
viewModel.handleAction(new MultiplicationCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle division action.
|
||||||
|
*/
|
||||||
|
void divide() {
|
||||||
|
viewModel.handleAction(new DivisionCalculatorAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle setting new variable action.
|
||||||
|
*
|
||||||
|
* @param value -> new calculator variable.
|
||||||
|
*/
|
||||||
|
void setVariable(final Double value) {
|
||||||
|
viewModel.handleAction(new SetVariableCalculatorAction(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package com.iluwatar.model.view.intent;
|
||||||
|
|
||||||
|
import com.iluwatar.model.view.intent.actions.AdditionCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.CalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.DivisionCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.MultiplicationCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.SetVariableCalculatorAction;
|
||||||
|
import com.iluwatar.model.view.intent.actions.SubtractionCalculatorAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle transformations to {@link CalculatorModel}
|
||||||
|
* based on intercepted {@link CalculatorAction}.
|
||||||
|
*/
|
||||||
|
public final class CalculatorViewModel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current calculator model (can be changed).
|
||||||
|
*/
|
||||||
|
private CalculatorModel model =
|
||||||
|
new CalculatorModel(0.0, 0.0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle calculator action.
|
||||||
|
*
|
||||||
|
* @param action -> transforms calculator model.
|
||||||
|
*/
|
||||||
|
void handleAction(final CalculatorAction action) {
|
||||||
|
switch (action.tag()) {
|
||||||
|
case AdditionCalculatorAction.TAG:
|
||||||
|
add();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SubtractionCalculatorAction.TAG:
|
||||||
|
subtract();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MultiplicationCalculatorAction.TAG:
|
||||||
|
multiply();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DivisionCalculatorAction.TAG:
|
||||||
|
divide();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SetVariableCalculatorAction.TAG:
|
||||||
|
SetVariableCalculatorAction setVariableAction =
|
||||||
|
(SetVariableCalculatorAction) action;
|
||||||
|
setVariable(setVariableAction.getVariable());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter.
|
||||||
|
*
|
||||||
|
* @return current calculator model.
|
||||||
|
*/
|
||||||
|
public CalculatorModel getCalculatorModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set new calculator model variable.
|
||||||
|
*
|
||||||
|
* @param variable -> value of new calculator model variable.
|
||||||
|
*/
|
||||||
|
private void setVariable(final Double variable) {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
variable,
|
||||||
|
model.getOutput()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add variable to model output.
|
||||||
|
*/
|
||||||
|
private void add() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() + model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtract variable from model output.
|
||||||
|
*/
|
||||||
|
private void subtract() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() - model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiply model output by variable.
|
||||||
|
*/
|
||||||
|
private void multiply() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() * model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Divide model output by variable.
|
||||||
|
*/
|
||||||
|
private void divide() {
|
||||||
|
model = new CalculatorModel(
|
||||||
|
model.getVariable(),
|
||||||
|
model.getOutput() / model.getVariable()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addition {@link CalculatorAction}.
|
||||||
|
* */
|
||||||
|
public class AdditionCalculatorAction implements CalculatorAction {
|
||||||
|
/**
|
||||||
|
* Subclass tag.
|
||||||
|
* */
|
||||||
|
public static final String TAG = "ADDITION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes checking subclass type trivial.
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public String tag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines what outside interactions can be consumed by view model.
|
||||||
|
* */
|
||||||
|
public interface CalculatorAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes identifying action trivial.
|
||||||
|
*
|
||||||
|
* @return subclass tag.
|
||||||
|
* */
|
||||||
|
String tag();
|
||||||
|
}
|
||||||
|
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Division {@link CalculatorAction}.
|
||||||
|
* */
|
||||||
|
public class DivisionCalculatorAction implements CalculatorAction {
|
||||||
|
/**
|
||||||
|
* Subclass tag.
|
||||||
|
* */
|
||||||
|
public static final String TAG = "DIVISION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes checking subclass type trivial.
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public String tag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplication {@link CalculatorAction}.
|
||||||
|
* */
|
||||||
|
public class MultiplicationCalculatorAction implements CalculatorAction {
|
||||||
|
/**
|
||||||
|
* Subclass tag.
|
||||||
|
* */
|
||||||
|
public static final String TAG = "MULTIPLICATION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes checking subclass type trivial.
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public String tag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetVariable {@link CalculatorAction}.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public final class SetVariableCalculatorAction implements CalculatorAction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass tag.
|
||||||
|
*/
|
||||||
|
public static final String TAG = "SET_VARIABLE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by {@link com.iluwatar.model.view.intent.CalculatorViewModel}.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final Double variable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes checking subclass type trivial.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String tag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subtraction {@link CalculatorAction}.
|
||||||
|
* */
|
||||||
|
public class SubtractionCalculatorAction implements CalculatorAction {
|
||||||
|
/**
|
||||||
|
* Subclass tag.
|
||||||
|
* */
|
||||||
|
public static final String TAG = "SUBTRACTION";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes checking subclass type trivial.
|
||||||
|
* */
|
||||||
|
@Override
|
||||||
|
public String tag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Handle actions for {@link com.iluwatar.model.view.intent.CalculatorModel}
|
||||||
|
* defined by {@link com.iluwatar.model.view.intent.actions.CalculatorAction}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.model.view.intent.actions;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Define Model, View and ViewModel.
|
||||||
|
* Use them in {@link com.iluwatar.model.view.intent.App}
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.iluwatar.model.view.intent;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
|
||||||
|
*
|
||||||
|
* The MIT License
|
||||||
|
* Copyright © 2014-2022 Ilkka Seppälä
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.iluwatar.model.view.intent;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application test
|
||||||
|
*/
|
||||||
|
class AppTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExecuteApplicationWithoutException() {
|
||||||
|
assertDoesNotThrow(() -> App.main(new String[]{}));
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
package com.iluwatar.model.view.intent;
|
||||||
|
|
||||||
|
import com.iluwatar.model.view.intent.actions.*;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CalculatorViewModelTest {
|
||||||
|
|
||||||
|
private CalculatorModel modelAfterExecutingActions(List<CalculatorAction> actions) {
|
||||||
|
CalculatorViewModel viewModel = new CalculatorViewModel();
|
||||||
|
for (CalculatorAction action : actions) {
|
||||||
|
viewModel.handleAction(action);
|
||||||
|
}
|
||||||
|
return viewModel.getCalculatorModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetup() {
|
||||||
|
CalculatorModel model = modelAfterExecutingActions(new ArrayList<>());
|
||||||
|
assert model.getVariable() == 0 && model.getOutput() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetVariable() {
|
||||||
|
List<CalculatorAction> actions = List.of(
|
||||||
|
new SetVariableCalculatorAction(10.0)
|
||||||
|
);
|
||||||
|
CalculatorModel model = modelAfterExecutingActions(actions);
|
||||||
|
assert model.getVariable() == 10.0 && model.getOutput() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddition() {
|
||||||
|
List<CalculatorAction> actions = List.of(
|
||||||
|
new SetVariableCalculatorAction(2.0),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new SetVariableCalculatorAction(7.0),
|
||||||
|
new AdditionCalculatorAction()
|
||||||
|
);
|
||||||
|
CalculatorModel model = modelAfterExecutingActions(actions);
|
||||||
|
assert model.getVariable() == 7.0 && model.getOutput() == 11.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSubtraction() {
|
||||||
|
List<CalculatorAction> actions = List.of(
|
||||||
|
new SetVariableCalculatorAction(2.0),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new SubtractionCalculatorAction()
|
||||||
|
);
|
||||||
|
CalculatorModel model = modelAfterExecutingActions(actions);
|
||||||
|
assert model.getVariable() == 2.0 && model.getOutput() == 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMultiplication() {
|
||||||
|
List<CalculatorAction> actions = List.of(
|
||||||
|
new SetVariableCalculatorAction(2.0),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new MultiplicationCalculatorAction()
|
||||||
|
);
|
||||||
|
CalculatorModel model = modelAfterExecutingActions(actions);
|
||||||
|
assert model.getVariable() == 2.0 && model.getOutput() == 8.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDivision() {
|
||||||
|
List<CalculatorAction> actions = List.of(
|
||||||
|
new SetVariableCalculatorAction(2.0),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new AdditionCalculatorAction(),
|
||||||
|
new SetVariableCalculatorAction(2.0),
|
||||||
|
new DivisionCalculatorAction()
|
||||||
|
);
|
||||||
|
CalculatorModel model = modelAfterExecutingActions(actions);
|
||||||
|
assert model.getVariable() == 2.0 && model.getOutput() == 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -198,6 +198,7 @@
|
|||||||
<module>metadata-mapping</module>
|
<module>metadata-mapping</module>
|
||||||
<module>service-to-worker</module>
|
<module>service-to-worker</module>
|
||||||
<module>client-session</module>
|
<module>client-session</module>
|
||||||
|
<module>model-view-intent</module>
|
||||||
<module>embedded-value</module>
|
<module>embedded-value</module>
|
||||||
<module>currying</module>
|
<module>currying</module>
|
||||||
<module>serialized-entity</module>
|
<module>serialized-entity</module>
|
||||||
|
|||||||
Reference in New Issue
Block a user