Skip to content

Commit 9b65174

Browse files
committed
Merge pull request #362 from JuhoKang/master
Value object pattern #349
2 parents 33224dd + 8353354 commit 9b65174

File tree

9 files changed

+272
-7
lines changed

9 files changed

+272
-7
lines changed

pom.xml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
<module>delegation</module>
121121
<module>event-driven-architecture</module>
122122
<module>feature-toggle</module>
123+
<module>value-object</module>
123124
</modules>
124125

125126
<dependencyManagement>
@@ -199,8 +200,8 @@
199200
<build>
200201
<pluginManagement>
201202
<plugins>
202-
<!-- This plugin's configuration is used to store Eclipse m2e settings
203-
only. It has no influence on the Maven build itself. TODO: Remove when the
203+
<!-- This plugin's configuration is used to store Eclipse m2e settings
204+
only. It has no influence on the Maven build itself. TODO: Remove when the
204205
m2e plugin can correctly bind to Maven lifecycle -->
205206
<plugin>
206207
<groupId>org.eclipse.m2e</groupId>
@@ -256,10 +257,10 @@
256257
<groupId>org.jacoco</groupId>
257258
<artifactId>jacoco-maven-plugin</artifactId>
258259
<version>${jacoco.version}</version>
259-
<!-- The following exclude configuration was added because error occurred
260+
<!-- The following exclude configuration was added because error occurred
260261
when executing "mvn clean test jacoco:report coveralls:report" -->
261-
<!-- [ERROR] Failed to execute goal org.eluder.coveralls:coveralls-maven-plugin:3.1.0:report
262-
(default-cli) on project java-design-patterns: I/O operation failed: No source
262+
<!-- [ERROR] Failed to execute goal org.eluder.coveralls:coveralls-maven-plugin:3.1.0:report
263+
(default-cli) on project java-design-patterns: I/O operation failed: No source
263264
found for domainapp/dom/modules/simple/QSimpleObject.java -> [Help 1] -->
264265
<configuration>
265266
<excludes>
@@ -354,7 +355,7 @@
354355
<excludeFromFailureFile>exclude-pmd.properties</excludeFromFailureFile>
355356
</configuration>
356357
</execution>
357-
</executions>
358+
</executions>
358359
</plugin>
359360

360361
<plugin>
@@ -390,5 +391,5 @@
390391
</plugin>
391392
</plugins>
392393
</reporting>
393-
394+
394395
</project>

value-object/etc/value-object.png

4.91 KB
Loading

value-object/etc/value-object.ucls

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<class-diagram version="1.1.9" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
3+
realizations="true" associations="true" dependencies="true" nesting-relationships="true" router="FAN">
4+
<class id="1" language="java" name="com.iluwatar.value.object.HeroStat" project="value-object"
5+
file="/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java" binary="false" corner="BOTTOM_RIGHT">
6+
<position height="-1" width="-1" x="520" y="337"/>
7+
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
8+
sort-features="false" accessors="true" visibility="true">
9+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
10+
<operations public="true" package="true" protected="true" private="true" static="true"/>
11+
</display>
12+
</class>
13+
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
14+
sort-features="false" accessors="true" visibility="true">
15+
<attributes public="true" package="true" protected="true" private="true" static="true"/>
16+
<operations public="true" package="true" protected="true" private="true" static="true"/>
17+
</classifier-display>
18+
<association-display labels="true" multiplicity="true"/>
19+
</class-diagram>

value-object/index.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
layout: pattern
3+
title: Value Object
4+
folder: value-object
5+
permalink: /patterns/value-object/
6+
categories: Creational
7+
tags:
8+
- Java
9+
- Difficulty-Beginner
10+
---
11+
12+
## Intent
13+
Provide objects which follow value semantics rather than reference semantics.
14+
This means value objects' equality are not based on identity. Two value objects are
15+
equal when they have the same value, not necessarily being the same object.
16+
17+
![alt text](./etc/value-object.png "Value Object")
18+
19+
## Applicability
20+
Use the Value Object when
21+
22+
* you need to measure the objects' equality based on the objects' value
23+
24+
## Real world examples
25+
26+
* [java.util.Optional](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
27+
* [java.time.LocalDate](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html)
28+
* [joda-time, money, beans](http://www.joda.org/)
29+
30+
## Credits
31+
32+
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.html)
33+
* [VALJOs - Value Java Objects : Stephen Colebourne's blog](http://blog.joda.org/2014/03/valjos-value-java-objects.html)
34+
* [Value Object : Wikipedia](https://en.wikipedia.org/wiki/Value_object)

value-object/pom.xml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0"?>
2+
<project
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4+
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
5+
<modelVersion>4.0.0</modelVersion>
6+
<parent>
7+
<groupId>com.iluwatar</groupId>
8+
<artifactId>java-design-patterns</artifactId>
9+
<version>1.11.0-SNAPSHOT</version>
10+
</parent>
11+
<artifactId>value-object</artifactId>
12+
<dependencies>
13+
<dependency>
14+
<groupId>com.google.guava</groupId>
15+
<artifactId>guava-testlib</artifactId>
16+
<version>19.0</version>
17+
<scope>test</scope>
18+
</dependency>
19+
<dependency>
20+
<groupId>junit</groupId>
21+
<artifactId>junit</artifactId>
22+
<scope>test</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.mockito</groupId>
26+
<artifactId>mockito-core</artifactId>
27+
<scope>test</scope>
28+
</dependency>
29+
</dependencies>
30+
</project>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.iluwatar.value.object;
2+
3+
/**
4+
* A Value Object are objects which follow value semantics rather than reference semantics. This
5+
* means value objects' equality are not based on identity. Two value objects are equal when they
6+
* have the same value, not necessarily being the same object..
7+
*
8+
* Value Objects must override equals(), hashCode() to check the equality with values.
9+
* Value Objects should be immutable so declare members final.
10+
* Obtain instances by static factory methods.
11+
* The elements of the state must be other values, including primitive types.
12+
* Provide methods, typically simple getters, to get the elements of the state.
13+
* A Value Object must check equality with equals() not ==
14+
*
15+
* For more specific and strict rules to implement value objects check the rules from Stephen
16+
* Colebourne's term VALJO : http://blog.joda.org/2014/03/valjos-value-java-objects.html
17+
*/
18+
public class App {
19+
/**
20+
* This practice creates three HeroStats(Value object) and checks equality between those.
21+
*/
22+
public static void main(String[] args) {
23+
HeroStat statA = HeroStat.valueOf(10, 5, 0);
24+
HeroStat statB = HeroStat.valueOf(10, 5, 0);
25+
HeroStat statC = HeroStat.valueOf(5, 1, 8);
26+
27+
System.out.println(statA.toString());
28+
29+
System.out.println("Is statA and statB equal : " + statA.equals(statB));
30+
System.out.println("Is statA and statC equal : " + statA.equals(statC));
31+
}
32+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package com.iluwatar.value.object;
2+
3+
/**
4+
* HeroStat is a value object
5+
*
6+
* {@link http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html}
7+
*/
8+
public class HeroStat {
9+
10+
// Stats for a hero
11+
12+
private final int strength;
13+
private final int intelligence;
14+
private final int luck;
15+
16+
// All constructors must be private.
17+
private HeroStat(int strength, int intelligence, int luck) {
18+
super();
19+
this.strength = strength;
20+
this.intelligence = intelligence;
21+
this.luck = luck;
22+
}
23+
24+
// Static factory method to create new instances.
25+
public static HeroStat valueOf(int strength, int intelligence, int luck) {
26+
return new HeroStat(strength, intelligence, luck);
27+
}
28+
29+
public int getStrength() {
30+
return strength;
31+
}
32+
33+
public int getIntelligence() {
34+
return intelligence;
35+
}
36+
37+
public int getLuck() {
38+
return luck;
39+
}
40+
41+
/*
42+
* Recommended to provide a static factory method capable of creating an instance from the formal
43+
* string representation declared like this. public static HeroStat parse(String string) {}
44+
*/
45+
46+
// toString, hashCode, equals
47+
48+
@Override
49+
public String toString() {
50+
return "HeroStat [strength=" + strength + ", intelligence=" + intelligence
51+
+ ", luck=" + luck + "]";
52+
}
53+
54+
@Override
55+
public int hashCode() {
56+
final int prime = 31;
57+
int result = 1;
58+
result = prime * result + intelligence;
59+
result = prime * result + luck;
60+
result = prime * result + strength;
61+
return result;
62+
}
63+
64+
@Override
65+
public boolean equals(Object obj) {
66+
if (this == obj) {
67+
return true;
68+
}
69+
if (obj == null) {
70+
return false;
71+
}
72+
if (getClass() != obj.getClass()) {
73+
return false;
74+
}
75+
HeroStat other = (HeroStat) obj;
76+
if (intelligence != other.intelligence) {
77+
return false;
78+
}
79+
if (luck != other.luck) {
80+
return false;
81+
}
82+
if (strength != other.strength) {
83+
return false;
84+
}
85+
return true;
86+
}
87+
88+
// The clone() method should not be public. Just don't override it.
89+
90+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.iluwatar.value.object;
2+
3+
import org.junit.Test;
4+
5+
/**
6+
* Application test
7+
*/
8+
public class AppTest {
9+
10+
@Test
11+
public void test() {
12+
String[] args = {};
13+
App.main(args);
14+
}
15+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.iluwatar.value.object;
2+
3+
import static org.hamcrest.CoreMatchers.is;
4+
import static org.hamcrest.CoreMatchers.not;
5+
6+
import static org.junit.Assert.assertThat;
7+
8+
import com.google.common.testing.EqualsTester;
9+
10+
import org.junit.Test;
11+
12+
/**
13+
* Unit test for HeroStat.
14+
*/
15+
public class HeroStatTest {
16+
17+
/**
18+
* Tester for equals() and hashCode() methods of a class. Using guava's EqualsTester.
19+
*
20+
* @see http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/
21+
* EqualsTester.html
22+
*/
23+
@Test
24+
public void testEquals() {
25+
HeroStat heroStatA = HeroStat.valueOf(3, 9, 2);
26+
HeroStat heroStatB = HeroStat.valueOf(3, 9, 2);
27+
new EqualsTester().addEqualityGroup(heroStatA, heroStatB).testEquals();
28+
}
29+
30+
/**
31+
* The toString() for two equal values must be the same. For two non-equal values it must be
32+
* different.
33+
*/
34+
@Test
35+
public void testToString() {
36+
HeroStat heroStatA = HeroStat.valueOf(3, 9, 2);
37+
HeroStat heroStatB = HeroStat.valueOf(3, 9, 2);
38+
HeroStat heroStatC = HeroStat.valueOf(3, 9, 8);
39+
40+
assertThat(heroStatA.toString(), is(heroStatB.toString()));
41+
assertThat(heroStatA.toString(), is(not(heroStatC.toString())));
42+
}
43+
44+
}

0 commit comments

Comments
 (0)