Skip to content

Value object pattern #349 #362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
<module>delegation</module>
<module>event-driven-architecture</module>
<module>feature-toggle</module>
<module>value-object</module>
</modules>

<dependencyManagement>
Expand Down Expand Up @@ -199,8 +200,8 @@
<build>
<pluginManagement>
<plugins>
<!-- This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. TODO: Remove when the
<!-- This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. TODO: Remove when the
m2e plugin can correctly bind to Maven lifecycle -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
Expand Down Expand Up @@ -256,10 +257,10 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<!-- The following exclude configuration was added because error occurred
<!-- The following exclude configuration was added because error occurred
when executing "mvn clean test jacoco:report coveralls:report" -->
<!-- [ERROR] Failed to execute goal org.eluder.coveralls:coveralls-maven-plugin:3.1.0:report
(default-cli) on project java-design-patterns: I/O operation failed: No source
<!-- [ERROR] Failed to execute goal org.eluder.coveralls:coveralls-maven-plugin:3.1.0:report
(default-cli) on project java-design-patterns: I/O operation failed: No source
found for domainapp/dom/modules/simple/QSimpleObject.java -> [Help 1] -->
<configuration>
<excludes>
Expand Down Expand Up @@ -354,7 +355,7 @@
<excludeFromFailureFile>exclude-pmd.properties</excludeFromFailureFile>
</configuration>
</execution>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why these blanks exist and make differences in this file. I only added one line value-object.

</executions>
</executions>
</plugin>

<plugin>
Expand Down Expand Up @@ -390,5 +391,5 @@
</plugin>
</plugins>
</reporting>

</project>
Binary file added value-object/etc/value-object.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions value-object/etc/value-object.ucls
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 0 deletions value-object/index.md
Original file line number Diff line number Diff line change
@@ -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 changes: 30 additions & 0 deletions value-object/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
32 changes: 32 additions & 0 deletions value-object/src/main/java/com/iluwatar/value/object/App.java
Original file line number Diff line number Diff line change
@@ -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));
}
}
90 changes: 90 additions & 0 deletions value-object/src/main/java/com/iluwatar/value/object/HeroStat.java
Original file line number Diff line number Diff line change
@@ -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.

}
15 changes: 15 additions & 0 deletions value-object/src/test/java/com/iluwatar/value/object/AppTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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())));
}

}