diff --git a/pom.xml b/pom.xml index 482110e64..c15ca20cf 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,7 @@ delegation event-driven-architecture feature-toggle + value-object @@ -199,8 +200,8 @@ - org.eclipse.m2e @@ -256,10 +257,10 @@ org.jacoco jacoco-maven-plugin ${jacoco.version} - - @@ -354,7 +355,7 @@ exclude-pmd.properties - + @@ -390,5 +391,5 @@ - + diff --git a/value-object/etc/value-object.png b/value-object/etc/value-object.png new file mode 100644 index 000000000..69a244c80 Binary files /dev/null and b/value-object/etc/value-object.png differ diff --git a/value-object/etc/value-object.ucls b/value-object/etc/value-object.ucls new file mode 100644 index 000000000..7bbf5e47c --- /dev/null +++ b/value-object/etc/value-object.ucls @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/value-object/index.md b/value-object/index.md new file mode 100644 index 000000000..83223d8a2 --- /dev/null +++ b/value-object/index.md @@ -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) diff --git a/value-object/pom.xml b/value-object/pom.xml new file mode 100644 index 000000000..1ed2adb43 --- /dev/null +++ b/value-object/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.11.0-SNAPSHOT + + value-object + + + com.google.guava + guava-testlib + 19.0 + test + + + junit + junit + test + + + org.mockito + mockito-core + test + + + diff --git a/value-object/src/main/java/com/iluwatar/value/object/App.java b/value-object/src/main/java/com/iluwatar/value/object/App.java new file mode 100644 index 000000000..85779fcb7 --- /dev/null +++ b/value-object/src/main/java/com/iluwatar/value/object/App.java @@ -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)); + } +} diff --git a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java new file mode 100644 index 000000000..101837db0 --- /dev/null +++ b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java @@ -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. + +} diff --git a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java new file mode 100644 index 000000000..aed3c2f20 --- /dev/null +++ b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java @@ -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); + } +} diff --git a/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java b/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java new file mode 100644 index 000000000..785a4d8fe --- /dev/null +++ b/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java @@ -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()))); + } + +}