Merge pull request #362 from JuhoKang/master

Value object pattern #349
This commit is contained in:
Ilkka Seppälä
2016-02-05 21:04:21 +02:00
9 changed files with 272 additions and 7 deletions

View File

@ -120,6 +120,7 @@
<module>delegation</module>
<module>event-driven-architecture</module>
<module>feature-toggle</module>
<module>value-object</module>
</modules>
<dependencyManagement>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<class-diagram version="1.1.9" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
realizations="true" associations="true" dependencies="true" nesting-relationships="true" router="FAN">
<class id="1" language="java" name="com.iluwatar.value.object.HeroStat" project="value-object"
file="/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java" binary="false" corner="BOTTOM_RIGHT">
<position height="-1" width="-1" x="520" y="337"/>
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</display>
</class>
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
sort-features="false" accessors="true" visibility="true">
<attributes public="true" package="true" protected="true" private="true" static="true"/>
<operations public="true" package="true" protected="true" private="true" static="true"/>
</classifier-display>
<association-display labels="true" multiplicity="true"/>
</class-diagram>

34
value-object/index.md Normal file
View File

@ -0,0 +1,34 @@
---
layout: pattern
title: Value Object
folder: value-object
permalink: /patterns/value-object/
categories: Creational
tags:
- Java
- Difficulty-Beginner
---
## 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
equal when they have the same value, not necessarily being the same object.
![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
## Real world examples
* [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)
* [joda-time, money, beans](http://www.joda.org/)
## Credits
* [Patterns of Enterprise Application Architecture](http://www.martinfowler.com/books/eaa.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)

30
value-object/pom.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.11.0-SNAPSHOT</version>
</parent>
<artifactId>value-object</artifactId>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>19.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,32 @@
package com.iluwatar.value.object;
/**
* A Value Object are objects which follow value semantics rather than reference semantics. This
* means value objects' equality are not based on identity. Two value objects are equal when they
* have the same value, not necessarily being the same object..
*
* Value Objects must override equals(), hashCode() to check the equality with values.
* Value Objects should be immutable so declare members final.
* Obtain instances by static factory methods.
* The elements of the state must be other values, including primitive types.
* Provide methods, typically simple getters, to get the elements of the state.
* A Value Object must check equality with equals() not ==
*
* For more specific and strict rules to implement value objects check the rules from Stephen
* Colebourne's term VALJO : http://blog.joda.org/2014/03/valjos-value-java-objects.html
*/
public class App {
/**
* This practice creates three HeroStats(Value object) and checks equality between those.
*/
public static void main(String[] args) {
HeroStat statA = HeroStat.valueOf(10, 5, 0);
HeroStat statB = HeroStat.valueOf(10, 5, 0);
HeroStat statC = HeroStat.valueOf(5, 1, 8);
System.out.println(statA.toString());
System.out.println("Is statA and statB equal : " + statA.equals(statB));
System.out.println("Is statA and statC equal : " + statA.equals(statC));
}
}

View File

@ -0,0 +1,90 @@
package com.iluwatar.value.object;
/**
* HeroStat is a value object
*
* {@link http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html}
*/
public class HeroStat {
// Stats for a hero
private final int strength;
private final int intelligence;
private final int luck;
// All constructors must be private.
private HeroStat(int strength, int intelligence, int luck) {
super();
this.strength = strength;
this.intelligence = intelligence;
this.luck = luck;
}
// Static factory method to create new instances.
public static HeroStat valueOf(int strength, int intelligence, int luck) {
return new HeroStat(strength, intelligence, luck);
}
public int getStrength() {
return strength;
}
public int getIntelligence() {
return intelligence;
}
public int getLuck() {
return luck;
}
/*
* Recommended to provide a static factory method capable of creating an instance from the formal
* string representation declared like this. public static HeroStat parse(String string) {}
*/
// toString, hashCode, equals
@Override
public String toString() {
return "HeroStat [strength=" + strength + ", intelligence=" + intelligence
+ ", luck=" + luck + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + intelligence;
result = prime * result + luck;
result = prime * result + strength;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
HeroStat other = (HeroStat) obj;
if (intelligence != other.intelligence) {
return false;
}
if (luck != other.luck) {
return false;
}
if (strength != other.strength) {
return false;
}
return true;
}
// The clone() method should not be public. Just don't override it.
}

View File

@ -0,0 +1,15 @@
package com.iluwatar.value.object;
import org.junit.Test;
/**
* Application test
*/
public class AppTest {
@Test
public void test() {
String[] args = {};
App.main(args);
}
}

View File

@ -0,0 +1,44 @@
package com.iluwatar.value.object;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
/**
* Unit test for HeroStat.
*/
public class HeroStatTest {
/**
* Tester for equals() and hashCode() methods of a class. Using guava's EqualsTester.
*
* @see http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/
* EqualsTester.html
*/
@Test
public void testEquals() {
HeroStat heroStatA = HeroStat.valueOf(3, 9, 2);
HeroStat heroStatB = HeroStat.valueOf(3, 9, 2);
new EqualsTester().addEqualityGroup(heroStatA, heroStatB).testEquals();
}
/**
* The toString() for two equal values must be the same. For two non-equal values it must be
* different.
*/
@Test
public void testToString() {
HeroStat heroStatA = HeroStat.valueOf(3, 9, 2);
HeroStat heroStatB = HeroStat.valueOf(3, 9, 2);
HeroStat heroStatC = HeroStat.valueOf(3, 9, 8);
assertThat(heroStatA.toString(), is(heroStatB.toString()));
assertThat(heroStatA.toString(), is(not(heroStatC.toString())));
}
}