Przeglądaj źródła

Added experimental (but unused) building block pattern base classes

peter_backlund 17 lat temu
rodzic
commit
7645ec834f

+ 17
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/shared/experimental/DomainEvent.java Wyświetl plik

@@ -0,0 +1,17 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+/**
4
+ * A domain event is something that is unique, but does not have a lifecycle.
5
+ * The identity may be explicit, for example the sequence number of a payment,
6
+ * or it could be derived from various aspects of the event such as where, when and what
7
+ * has happened.
8
+ */
9
+public interface DomainEvent<T> {
10
+
11
+  /**
12
+   * @param other The other domain event.
13
+   * @return <code>true</code> if the given domain event and this event are regarded as being the same event.
14
+   */
15
+  boolean sameEventAs(T other);
16
+
17
+}

+ 19
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/shared/experimental/Entity.java Wyświetl plik

@@ -0,0 +1,19 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+/**
4
+ * An entity, as explained in the DDD book.
5
+ *
6
+ */
7
+public interface Entity<T,ID> {
8
+
9
+  /**
10
+   * Entities compare by identity, not by attributes.
11
+   *
12
+   * @param other The other entity.
13
+   * @return true if the identities are the same, regardles of other attributes.
14
+   */
15
+  boolean sameIdentityAs(T other);
16
+
17
+  ID identity();
18
+
19
+}

+ 71
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/shared/experimental/EntitySupport.java Wyświetl plik

@@ -0,0 +1,71 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+import java.lang.reflect.Field;
4
+
5
+/**
6
+ *
7
+ *
8
+ */
9
+public abstract class EntitySupport<T extends Entity, ID> implements Entity<T, ID> {
10
+
11
+    private static Field identityField;
12
+
13
+    @Override
14
+    public final boolean sameIdentityAs(final T other) {
15
+        return other != null && this.identity().equals(other.identity());
16
+    }
17
+
18
+    @Override
19
+    public final ID identity() {
20
+        if (identityField == null) {
21
+            identityField = identityFieldLazyDetermination(this.getClass());
22
+        }
23
+
24
+        try {
25
+            return (ID) identityField.get(this);
26
+        } catch (IllegalAccessException e) {
27
+            throw new RuntimeException(e);
28
+        }
29
+    }
30
+
31
+    private static Field identityFieldLazyDetermination(final Class cls) {
32
+        Field identityField = null;
33
+
34
+        for (Field field : cls.getDeclaredFields()) {
35
+            if (field.getAnnotation(Identity.class) != null) {
36
+                field.setAccessible(true);
37
+                if (identityField != null) {
38
+                    throw new IllegalStateException("Only one field can be annotated with " + Identity.class);
39
+                } else {
40
+                    identityField = field;
41
+                }
42
+            }
43
+        }
44
+
45
+        if (identityField == null) {
46
+            if (cls == Object.class) {
47
+                throw new IllegalStateException(
48
+                  "This class, or one of its superclasses, " +
49
+                  "must have a unique field annotated with " + Identity.class);
50
+            } else {
51
+                return identityFieldLazyDetermination(cls.getSuperclass());
52
+            }
53
+        }
54
+
55
+        return identityField;
56
+    }
57
+
58
+    @Override
59
+    public final int hashCode() {
60
+        return identity().hashCode();
61
+    }
62
+
63
+    @Override
64
+    public final boolean equals(final Object o) {
65
+        if (this == o) return true;
66
+        if (o == null || getClass() != o.getClass()) return false;
67
+
68
+        return sameIdentityAs((T) o);
69
+    }
70
+
71
+}

+ 15
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/shared/experimental/Identity.java Wyświetl plik

@@ -0,0 +1,15 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+import java.lang.annotation.ElementType;
4
+import java.lang.annotation.Retention;
5
+import java.lang.annotation.RetentionPolicy;
6
+import java.lang.annotation.Target;
7
+
8
+/**
9
+ * Every class that inherits from {@link se.citerus.dddsample.domain.shared.experimental.EntitySupport}
10
+ * must have exactly one field annotated with this annotation.
11
+ */
12
+@Retention(RetentionPolicy.RUNTIME)
13
+@Target(ElementType.FIELD)
14
+public @interface Identity {
15
+}

+ 23
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/shared/experimental/ValueObject.java Wyświetl plik

@@ -0,0 +1,23 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+import java.io.Serializable;
4
+
5
+/**
6
+ * A value object.
7
+ *
8
+ */
9
+public interface ValueObject<T> extends Serializable {
10
+
11
+  /**
12
+   * Value objects compare by the values of their attributes, they don't have an identity.
13
+   *
14
+   * @param other The other value object.
15
+   * @return <code>true</code> if the given value object's and this value object's attributes are the same.
16
+   */
17
+  boolean sameValueAs(T other);
18
+
19
+
20
+  // Value objects can be safely copied:
21
+  // T copy();
22
+
23
+}

+ 42
- 0
dddsample/src/main/java/se/citerus/dddsample/domain/shared/experimental/ValueObjectSupport.java Wyświetl plik

@@ -0,0 +1,42 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+import org.apache.commons.lang.builder.EqualsBuilder;
4
+import org.apache.commons.lang.builder.HashCodeBuilder;
5
+import se.citerus.dddsample.domain.model.ValueObject;
6
+
7
+public class ValueObjectSupport<T extends ValueObject> implements ValueObject<T> {
8
+
9
+    private transient int cachedHashCode = 0;
10
+
11
+    @Override
12
+    public final boolean sameValueAs(final T other) {
13
+        return other != null && EqualsBuilder.reflectionEquals(this, other, false);
14
+    }
15
+
16
+    @Override
17
+    public final int hashCode() {
18
+        // Using a local variable to ensure that we only do a single read
19
+        // of the cachedHashCode field, to avoid race conditions.
20
+        // It doesn't matter if several threads compute the hash code and overwrite
21
+        // each other, but it's important that we never return 0, which could happen
22
+        // with multiple field reads.
23
+        int h = cachedHashCode;
24
+        if (h == 0) {
25
+            // Lazy initialization of hash code.
26
+            // Value objects are immutable, so the hash code never changes.
27
+            h = HashCodeBuilder.reflectionHashCode(this, false);
28
+            cachedHashCode = h;
29
+        }
30
+
31
+        return h;
32
+    }
33
+
34
+    @Override
35
+    public final boolean equals(final Object o) {
36
+        if (this == o) return true;
37
+        if (o == null || getClass() != o.getClass()) return false;
38
+
39
+        return sameValueAs((T) o);
40
+    }
41
+
42
+}

+ 62
- 0
dddsample/src/test/java/se/citerus/dddsample/domain/shared/experimental/EntitySupportTest.java Wyświetl plik

@@ -0,0 +1,62 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+import junit.framework.TestCase;
4
+
5
+public class EntitySupportTest extends TestCase {
6
+
7
+    public void testNoIdentityAnnotationFail() {
8
+        NoAnnotationEntity entity = new NoAnnotationEntity();
9
+
10
+        try {
11
+            entity.identity();
12
+            fail("Entity must have a unique identity");
13
+        } catch (IllegalStateException expected) {
14
+        }
15
+    }
16
+
17
+    public void testTwoIdentityAnnotationsFail() {
18
+        TwoAnnotationsEntity entity = new TwoAnnotationsEntity();
19
+        try {
20
+            entity.identity();
21
+            fail("Entity must have a unique identity");
22
+        } catch (IllegalStateException expected) {
23
+        }
24
+    }
25
+
26
+    public void testOneAnnotationSuccess() {
27
+        OneAnnotationEntity entity = new OneAnnotationEntity("id");
28
+        assertEquals("id", entity.identity());
29
+    }
30
+
31
+    public void testSameIdentityEqualsHashcode() {
32
+        OneAnnotationEntity entity1 = new OneAnnotationEntity("A");
33
+        OneAnnotationEntity entity2 = new OneAnnotationEntity("A");
34
+        OneAnnotationEntity entity3 = new OneAnnotationEntity("B");
35
+
36
+        assertTrue(entity1.sameIdentityAs(entity2));
37
+        assertFalse(entity2.sameIdentityAs(entity3));
38
+
39
+        assertTrue(entity1.equals(entity2));
40
+        assertFalse(entity2.equals(entity3));
41
+
42
+        assertTrue(entity1.hashCode() == entity2.hashCode());
43
+        assertFalse(entity2.hashCode() == entity3.hashCode());
44
+    }
45
+
46
+    class NoAnnotationEntity extends EntitySupport<NoAnnotationEntity, String> {
47
+    }
48
+
49
+    class OneAnnotationEntity extends EntitySupport<OneAnnotationEntity, String> {
50
+        private @Identity String id;
51
+
52
+        OneAnnotationEntity(String id) {
53
+            this.id = id;
54
+        }
55
+    }
56
+
57
+    class TwoAnnotationsEntity extends EntitySupport<TwoAnnotationsEntity, String> {
58
+        private @Identity String id1 = "id1";
59
+        private @Identity String id2 = "id2";
60
+    }
61
+
62
+}

+ 43
- 0
dddsample/src/test/java/se/citerus/dddsample/domain/shared/experimental/ValueObjectSupportTest.java Wyświetl plik

@@ -0,0 +1,43 @@
1
+package se.citerus.dddsample.domain.shared.experimental;
2
+
3
+import junit.framework.TestCase;
4
+
5
+
6
+public class ValueObjectSupportTest extends TestCase {
7
+
8
+    public void testEquals() {
9
+        final AValueObject vo1 = new AValueObject("A");
10
+        final AValueObject vo2 = new AValueObject("A");
11
+        final BValueObject vo3 = new BValueObject("A", 1);
12
+
13
+        assertEquals(vo1, vo2);
14
+        assertEquals(vo2, vo1);
15
+        assertFalse(vo2.equals(vo3));
16
+        assertFalse(vo3.equals(vo2));
17
+
18
+        assertTrue(vo1.sameValueAs(vo2));
19
+        assertFalse(vo2.sameValueAs(vo3));
20
+    }
21
+
22
+    class AValueObject extends ValueObjectSupport<AValueObject> {
23
+        String s;
24
+
25
+        AValueObject(String s) {
26
+            this.s = s;
27
+        }
28
+
29
+        AValueObject() {
30
+        }
31
+    }
32
+
33
+    class BValueObject extends AValueObject {
34
+        int x;
35
+
36
+        BValueObject(String s, int x) {
37
+            super(s);
38
+            this.x = x;
39
+        }
40
+
41
+    }
42
+
43
+}