Compare commits

..

1 Commits

Author SHA1 Message Date
d2fab302ce Refactor and add explanation for value object 2022-01-04 20:36:57 +02:00
10 changed files with 273 additions and 229 deletions
unit-of-work
value-object
README.md
src
main
java
com
iluwatar

@ -12,20 +12,20 @@ tags:
## Intent
When a business transaction is completed, all the updates are sent as one big unit of work to be
When a business transaction is completed, all the the updates are sent as one big unit of work to be
persisted in one go to minimize database round-trips.
## Explanation
Real-world example
Real world example
> Arms dealer has a database containing weapon information. Merchants all over the town are
> constantly updating this information and it causes a high load on the database server. To make the
> We have a database containing student information. Administrators all over the country are
> constantly updating this information and it causes high load on the database server. To make the
> load more manageable we apply to Unit of Work pattern to send many small updates in batches.
In plain words
> Unit of Work merges many small database updates in a single batch to optimize the number of
> Unit of Work merges many small database updates in single batch to optimize the number of
> round-trips.
[MartinFowler.com](https://martinfowler.com/eaaCatalog/unitOfWork.html) says
@ -35,20 +35,37 @@ In plain words
**Programmatic Example**
Here's the `Weapon` entity that is being persisted in the database.
Here's the `Student` entity that is being persisted to the database.
```java
@Getter
@RequiredArgsConstructor
public class Weapon {
private final Integer id;
private final String name;
public class Student {
private final Integer id;
private final String name;
private final String address;
public Student(Integer id, String name, String address) {
this.id = id;
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Integer getId() {
return id;
}
public String getAddress() {
return address;
}
}
```
The essence of the implementation is the `ArmsDealer` implementing the Unit of Work pattern.
The essence of the implementation is the `StudentRepository` implementing the Unit of Work pattern.
It maintains a map of database operations (`context`) that need to be done and when `commit` is
called it applies them in a single batch.
called it applies them in single batch.
```java
public interface IUnitOfWork<T> {
@ -67,117 +84,96 @@ public interface IUnitOfWork<T> {
}
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
public StudentRepository(Map<String, List<Student>> context, StudentDatabase studentDatabase) {
this.context = context;
this.studentDatabase = studentDatabase;
}
@Override
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, IUnitOfWork.INSERT);
}
@Override
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, IUnitOfWork.MODIFY);
}
@Override
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, IUnitOfWork.DELETE);
}
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(IUnitOfWork.INSERT)) {
commitInsert();
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
if (context.containsKey(IUnitOfWork.MODIFY)) {
commitModify();
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
if (context.containsKey(IUnitOfWork.DELETE)) {
commitDelete();
}
LOGGER.info("Commit finished.");
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
private void commitInsert() {
var studentsToBeInserted = context.get(IUnitOfWork.INSERT);
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
/**
* All UnitOfWork operations are batched and executed together on commit only.
*/
@Override
public void commit() {
if (context == null || context.size() == 0) {
return;
}
LOGGER.info("Commit started");
if (context.containsKey(UnitActions.INSERT.getActionValue())) {
commitInsert();
}
if (context.containsKey(UnitActions.MODIFY.getActionValue())) {
commitModify();
}
if (context.containsKey(UnitActions.DELETE.getActionValue())) {
commitDelete();
}
LOGGER.info("Commit finished.");
private void commitModify() {
var modifiedStudents = context.get(IUnitOfWork.MODIFY);
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
}
private void commitDelete() {
var deletedStudents = context.get(IUnitOfWork.DELETE);
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}
```
Here is how the whole app is put together.
Finally, here's how we use the `StudentRepository` and `commit` the transaction.
```java
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(), new WeaponDatabase());
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
```
Here is the console output.
```
21:39:21.984 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering enchanted hammer for insert in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering silver trident for modify in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Registering broken great sword for delete in context.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit started
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Inserting a new weapon enchanted hammer to sales rack.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scheduling silver trident for modification work.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Scrapping broken great sword.
21:39:21.989 [main] INFO com.iluwatar.unitofwork.ArmsDealer - Commit finished.
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
```
## Class diagram
@ -190,7 +186,7 @@ Use the Unit Of Work pattern when
* To optimize the time taken for database transactions.
* To send changes to database as a unit of work which ensures atomicity of the transaction.
* To reduce the number of database calls.
* To reduce number of database calls.
## Tutorials

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.2.1" icons="true" always-add-relationships="false" generalizations="true" realizations="true"
associations="true" dependencies="false" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.unitofwork.WeaponDatabase" project="unit-of-work"
<class id="1" language="java" name="com.iluwatar.unitofwork.StudentDatabase" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentDatabase.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="170" y="406"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
@ -28,7 +28,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="4" language="java" name="com.iluwatar.unitofwork.ArmsDealer" project="unit-of-work"
<class id="4" language="java" name="com.iluwatar.unitofwork.StudentRepository" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/StudentRepository.java" binary="false"
corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="377" y="166"/>
@ -38,7 +38,7 @@
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<class id="5" language="java" name="com.iluwatar.unitofwork.Weapon" project="unit-of-work"
<class id="5" language="java" name="com.iluwatar.unitofwork.Student" project="unit-of-work"
file="/unit-of-work/src/main/java/com/iluwatar/unitofwork/Student.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="696" y="130"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"

@ -27,7 +27,7 @@ import java.util.HashMap;
import java.util.List;
/**
* {@link App} Application demonstrating unit of work pattern.
* {@link App} Application for managing student data.
*/
public class App {
/**
@ -37,19 +37,17 @@ public class App {
*/
public static void main(String[] args) {
// create some weapons
var enchantedHammer = new Weapon(1, "enchanted hammer");
var brokenGreatSword = new Weapon(2, "broken great sword");
var silverTrident = new Weapon(3, "silver trident");
var ram = new Student(1, "Ram", "Street 9, Cupertino");
var shyam = new Student(2, "Shyam", "Z bridge, Pune");
var gopi = new Student(3, "Gopi", "Street 10, Mumbai");
// create repository
var weaponRepository = new ArmsDealer(new HashMap<String, List<Weapon>>(),
new WeaponDatabase());
var context = new HashMap<String, List<Student>>();
var studentDatabase = new StudentDatabase();
var studentRepository = new StudentRepository(context, studentDatabase);
// perform operations on the weapons
weaponRepository.registerNew(enchantedHammer);
weaponRepository.registerModified(silverTrident);
weaponRepository.registerDeleted(brokenGreatSword);
weaponRepository.commit();
studentRepository.registerNew(ram);
studentRepository.registerModified(shyam);
studentRepository.registerDeleted(gopi);
studentRepository.commit();
}
}

@ -27,12 +27,14 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
/**
* {@link Weapon} is an entity.
* {@link Student} is an entity.
*/
@Getter
@RequiredArgsConstructor
public class Weapon {
public class Student {
private final Integer id;
private final String name;
private final String address;
}

@ -24,19 +24,19 @@
package com.iluwatar.unitofwork;
/**
* Act as database for weapon records.
* Act as Database for student records.
*/
public class WeaponDatabase {
public class StudentDatabase {
public void insert(Weapon weapon) {
public void insert(Student student) {
//Some insert logic to DB
}
public void modify(Weapon weapon) {
public void modify(Student student) {
//Some modify logic to DB
}
public void delete(Weapon weapon) {
public void delete(Student student) {
//Some delete logic to DB
}
}

@ -30,41 +30,41 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* {@link ArmsDealer} Weapon repository that supports unit of work for weapons.
* {@link StudentRepository} Student database repository. supports unit of work for student data.
*/
@Slf4j
@RequiredArgsConstructor
public class ArmsDealer implements IUnitOfWork<Weapon> {
public class StudentRepository implements IUnitOfWork<Student> {
private final Map<String, List<Weapon>> context;
private final WeaponDatabase weaponDatabase;
private final Map<String, List<Student>> context;
private final StudentDatabase studentDatabase;
@Override
public void registerNew(Weapon weapon) {
LOGGER.info("Registering {} for insert in context.", weapon.getName());
register(weapon, UnitActions.INSERT.getActionValue());
public void registerNew(Student student) {
LOGGER.info("Registering {} for insert in context.", student.getName());
register(student, UnitActions.INSERT.getActionValue());
}
@Override
public void registerModified(Weapon weapon) {
LOGGER.info("Registering {} for modify in context.", weapon.getName());
register(weapon, UnitActions.MODIFY.getActionValue());
public void registerModified(Student student) {
LOGGER.info("Registering {} for modify in context.", student.getName());
register(student, UnitActions.MODIFY.getActionValue());
}
@Override
public void registerDeleted(Weapon weapon) {
LOGGER.info("Registering {} for delete in context.", weapon.getName());
register(weapon, UnitActions.DELETE.getActionValue());
public void registerDeleted(Student student) {
LOGGER.info("Registering {} for delete in context.", student.getName());
register(student, UnitActions.DELETE.getActionValue());
}
private void register(Weapon weapon, String operation) {
var weaponsToOperate = context.get(operation);
if (weaponsToOperate == null) {
weaponsToOperate = new ArrayList<>();
private void register(Student student, String operation) {
var studentsToOperate = context.get(operation);
if (studentsToOperate == null) {
studentsToOperate = new ArrayList<>();
}
weaponsToOperate.add(weapon);
context.put(operation, weaponsToOperate);
studentsToOperate.add(student);
context.put(operation, studentsToOperate);
}
/**
@ -90,26 +90,26 @@ public class ArmsDealer implements IUnitOfWork<Weapon> {
}
private void commitInsert() {
var weaponsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var weapon : weaponsToBeInserted) {
LOGGER.info("Inserting a new weapon {} to sales rack.", weapon.getName());
weaponDatabase.insert(weapon);
var studentsToBeInserted = context.get(UnitActions.INSERT.getActionValue());
for (var student : studentsToBeInserted) {
LOGGER.info("Saving {} to database.", student.getName());
studentDatabase.insert(student);
}
}
private void commitModify() {
var modifiedWeapons = context.get(UnitActions.MODIFY.getActionValue());
for (var weapon : modifiedWeapons) {
LOGGER.info("Scheduling {} for modification work.", weapon.getName());
weaponDatabase.modify(weapon);
var modifiedStudents = context.get(UnitActions.MODIFY.getActionValue());
for (var student : modifiedStudents) {
LOGGER.info("Modifying {} to database.", student.getName());
studentDatabase.modify(student);
}
}
private void commitDelete() {
var deletedWeapons = context.get(UnitActions.DELETE.getActionValue());
for (var weapon : deletedWeapons) {
LOGGER.info("Scrapping {}.", weapon.getName());
weaponDatabase.delete(weapon);
var deletedStudents = context.get(UnitActions.DELETE.getActionValue());
for (var student : deletedStudents) {
LOGGER.info("Deleting {} to database.", student.getName());
studentDatabase.delete(student);
}
}
}

@ -36,102 +36,102 @@ import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* tests {@link ArmsDealer}
* tests {@link StudentRepository}
*/
class ArmsDealerTest {
private final Weapon weapon1 = new Weapon(1, "battle ram");
private final Weapon weapon2 = new Weapon(1, "wooden lance");
class StudentRepositoryTest {
private final Student student1 = new Student(1, "Ram", "street 9, cupertino");
private final Student student2 = new Student(1, "Sham", "Z bridge, pune");
private final Map<String, List<Weapon>> context = new HashMap<>();
private final WeaponDatabase weaponDatabase = mock(WeaponDatabase.class);
private final ArmsDealer armsDealer = new ArmsDealer(context, weaponDatabase);;
private final Map<String, List<Student>> context = new HashMap<>();
private final StudentDatabase studentDatabase = mock(StudentDatabase.class);
private final StudentRepository studentRepository = new StudentRepository(context, studentDatabase);;
@Test
void shouldSaveNewStudentWithoutWritingToDb() {
armsDealer.registerNew(weapon1);
armsDealer.registerNew(weapon2);
studentRepository.registerNew(student1);
studentRepository.registerNew(student2);
assertEquals(2, context.get(UnitActions.INSERT.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveDeletedStudentWithoutWritingToDb() {
armsDealer.registerDeleted(weapon1);
armsDealer.registerDeleted(weapon2);
studentRepository.registerDeleted(student1);
studentRepository.registerDeleted(student2);
assertEquals(2, context.get(UnitActions.DELETE.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveModifiedStudentWithoutWritingToDb() {
armsDealer.registerModified(weapon1);
armsDealer.registerModified(weapon2);
studentRepository.registerModified(student1);
studentRepository.registerModified(student2);
assertEquals(2, context.get(UnitActions.MODIFY.getActionValue()).size());
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldSaveAllLocalChangesToDb() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, times(1)).insert(weapon1);
verify(weaponDatabase, times(1)).modify(weapon1);
verify(weaponDatabase, times(1)).delete(weapon1);
verify(studentDatabase, times(1)).insert(student1);
verify(studentDatabase, times(1)).modify(student1);
verify(studentDatabase, times(1)).delete(student1);
}
@Test
void shouldNotWriteToDbIfContextIsNull() {
var weaponRepository = new ArmsDealer(null, weaponDatabase);
var studentRepository = new StudentRepository(null, studentDatabase);
weaponRepository.commit();
studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldNotWriteToDbIfNothingToCommit() {
var weaponRepository = new ArmsDealer(new HashMap<>(), weaponDatabase);
var studentRepository = new StudentRepository(new HashMap<>(), studentDatabase);
weaponRepository.commit();
studentRepository.commit();
verifyNoMoreInteractions(weaponDatabase);
verifyNoMoreInteractions(studentDatabase);
}
@Test
void shouldNotInsertToDbIfNoRegisteredStudentsToBeCommitted() {
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).insert(weapon1);
verify(studentDatabase, never()).insert(student1);
}
@Test
void shouldNotModifyToDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.DELETE.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.DELETE.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).modify(weapon1);
verify(studentDatabase, never()).modify(student1);
}
@Test
void shouldNotDeleteFromDbIfNotRegisteredStudentsToBeCommitted() {
context.put(UnitActions.INSERT.getActionValue(), List.of(weapon1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(weapon1));
context.put(UnitActions.INSERT.getActionValue(), List.of(student1));
context.put(UnitActions.MODIFY.getActionValue(), List.of(student1));
armsDealer.commit();
studentRepository.commit();
verify(weaponDatabase, never()).delete(weapon1);
verify(studentDatabase, never()).delete(student1);
}
}

@ -10,19 +10,80 @@ tags:
---
## Intent
Provide objects which follow value semantics rather than reference semantics.
This means value objects' equality are not based on identity. Two value objects are
This means value objects' equality is not based on identity. Two value objects are
equal when they have the same value, not necessarily being the same object.
## Explanation
Real-world example
> There is a class for hero statistics in a role-playing game. The statistics contain attributes
> such as strength, intelligence, and luck. The statistics of different heroes should be equal
> when all the attributes are equal.
In plain words
> Value objects are equal when their attributes have the same value
Wikipedia says
> In computer science, a value object is a small object that represents a simple entity whose
> equality is not based on identity: i.e. two value objects are equal when they have the same
> value, not necessarily being the same object.
**Programmatic Example**
Here is the `HeroStat` class that is the value object. Notice the use of
[Lombok's `@Value`](https://projectlombok.org/features/Value) annotation.
```java
@Value(staticConstructor = "valueOf")
class HeroStat {
int strength;
int intelligence;
int luck;
}
```
The example creates three different `HeroStat`s and compares their equality.
```java
var statA = HeroStat.valueOf(10, 5, 0);
var statB = HeroStat.valueOf(10, 5, 0);
var statC = HeroStat.valueOf(5, 1, 8);
LOGGER.info(statA.toString());
LOGGER.info(statB.toString());
LOGGER.info(statC.toString());
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));
```
Here's the console output.
```
20:11:12.199 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=10, intelligence=5, luck=0)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - HeroStat(strength=5, intelligence=1, luck=8)
20:11:12.202 [main] INFO com.iluwatar.value.object.App - Is statA and statB equal : true
20:11:12.203 [main] INFO com.iluwatar.value.object.App - Is statA and statC equal : false
```
## Class diagram
![alt text](./etc/value-object.png "Value Object")
## Applicability
Use the Value Object when
* You need to measure the objects' equality based on the objects' value
* The object's equality needs to be based on the object's value
## Real world examples
## Known uses
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
@ -31,6 +92,7 @@ Use the Value Object when
## Credits
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
* [ValueObject](https://martinfowler.com/bliki/ValueObject.html)
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)
* [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=f27d2644fbe5026ea448791a8ad09c94)

@ -43,7 +43,7 @@ import lombok.extern.slf4j.Slf4j;
public class App {
/**
* This practice creates three HeroStats(Value object) and checks equality between those.
* This example creates three HeroStats (value objects) and checks equality between those.
*/
public static void main(String[] args) {
var statA = HeroStat.valueOf(10, 5, 0);
@ -51,6 +51,8 @@ public class App {
var statC = HeroStat.valueOf(5, 1, 8);
LOGGER.info(statA.toString());
LOGGER.info(statB.toString());
LOGGER.info(statC.toString());
LOGGER.info("Is statA and statB equal : {}", statA.equals(statB));
LOGGER.info("Is statA and statC equal : {}", statA.equals(statC));

@ -23,10 +23,7 @@
package com.iluwatar.value.object;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.Value;
/**
* HeroStat is a value object.
@ -35,23 +32,10 @@ import lombok.ToString;
* http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html
* </a>
*/
@Getter
@ToString
@EqualsAndHashCode
@RequiredArgsConstructor
public class HeroStat {
// Stats for a hero
private final int strength;
private final int intelligence;
private final int luck;
// Static factory method to create new instances.
public static HeroStat valueOf(int strength, int intelligence, int luck) {
return new HeroStat(strength, intelligence, luck);
}
// The clone() method should not be public. Just don't override it.
@Value(staticConstructor = "valueOf")
class HeroStat {
int strength;
int intelligence;
int luck;
}