Skip to content

Commit 35c717a

Browse files
authored
Redefine singleton test scenario and implement new patterns (#63)
1 parent 8719eec commit 35c717a

File tree

10 files changed

+204
-76
lines changed

10 files changed

+204
-76
lines changed

DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingleton.java

Lines changed: 0 additions & 22 deletions
This file was deleted.

DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/creational/singleton/Singleton.java

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pl.mperor.lab.java.design.pattern.creational.singleton;
2+
3+
import java.util.concurrent.atomic.AtomicReference;
4+
5+
public class SingletonAtomic implements SingletonInstance {
6+
7+
private static final AtomicReference<SingletonAtomic> INSTANCE = new AtomicReference<>();
8+
private final long time = System.currentTimeMillis();
9+
10+
private SingletonAtomic() {
11+
}
12+
13+
public static SingletonAtomic getInstance() {
14+
INSTANCE.compareAndSet(null, new SingletonAtomic());
15+
return INSTANCE.get();
16+
}
17+
18+
@Override
19+
public long getCreationTime() {
20+
return time;
21+
}
22+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package pl.mperor.lab.java.design.pattern.creational.singleton;
2+
3+
public class SingletonEager implements SingletonInstance {
4+
5+
private static final SingletonEager INSTANCE = new SingletonEager();
6+
private final long time = System.currentTimeMillis();
7+
8+
private SingletonEager() {
9+
}
10+
11+
public static SingletonEager getInstance() {
12+
return INSTANCE;
13+
}
14+
15+
@Override
16+
public long getCreationTime() {
17+
return time;
18+
}
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package pl.mperor.lab.java.design.pattern.creational.singleton;
2+
3+
public enum SingletonEnum implements SingletonInstance {
4+
INSTANCE;
5+
6+
private final long time = System.currentTimeMillis();
7+
8+
@Override
9+
public long getCreationTime() {
10+
return time;
11+
}
12+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pl.mperor.lab.java.design.pattern.creational.singleton;
2+
3+
public class SingletonHolder implements SingletonInstance {
4+
5+
private final long time = System.currentTimeMillis();
6+
7+
private SingletonHolder() {
8+
}
9+
10+
public static SingletonHolder getInstance() {
11+
return Holder.INSTANCE;
12+
}
13+
14+
@Override
15+
public long getCreationTime() {
16+
return time;
17+
}
18+
19+
private static class Holder {
20+
private static final SingletonHolder INSTANCE = new SingletonHolder();
21+
}
22+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package pl.mperor.lab.java.design.pattern.creational.singleton;
2+
3+
public interface SingletonInstance {
4+
5+
long getCreationTime();
6+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package pl.mperor.lab.java.design.pattern.creational.singleton;
2+
3+
import java.io.ObjectStreamException;
4+
import java.io.Serial;
5+
import java.io.Serializable;
6+
7+
public class SingletonSerializable implements Serializable, SingletonInstance {
8+
9+
@Serial
10+
private static final long serialVersionUID = 1L;
11+
12+
private static final SingletonSerializable INSTANCE = new SingletonSerializable();
13+
private final long time = System.currentTimeMillis();
14+
15+
private SingletonSerializable() {
16+
}
17+
18+
public static SingletonSerializable getInstance() {
19+
return INSTANCE;
20+
}
21+
22+
@Override
23+
public long getCreationTime() {
24+
return time;
25+
}
26+
27+
// Prevent Serialization from creating a new instance
28+
protected Object readResolve() throws ObjectStreamException {
29+
return getInstance();
30+
}
31+
}

DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/LazyInitializedSingletonTest.java

Lines changed: 0 additions & 27 deletions
This file was deleted.

DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/creational/singleton/SingletonTest.java

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,105 @@
22

33
import org.junit.jupiter.api.Assertions;
44
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.condition.EnabledIf;
56

6-
import java.util.concurrent.TimeUnit;
7+
import java.io.*;
8+
import java.util.concurrent.CompletableFuture;
9+
import java.util.concurrent.ThreadLocalRandom;
10+
import java.util.function.Supplier;
711

12+
/**
13+
* The purpose of this test is to randomly enable one of the test methods to execute.
14+
* This is due to the fact that the tests involve the creation of a Singleton object.
15+
* Since the Singleton is initialized only once, executing multiple tests could cause issues
16+
* because subsequent tests would attempt to initialize the Singleton again, which is not allowed.
17+
* By randomly selecting and enabling just one test per run, we avoid such conflicts and ensure
18+
* the Singleton is properly handled without reinitialization.
19+
*/
820
public class SingletonTest {
921

22+
private static int random = ThreadLocalRandom.current().nextInt(1, 4);
23+
24+
static boolean isRandom1() {
25+
return random == 1;
26+
}
27+
28+
static boolean isRandom2() {
29+
return random == 2;
30+
}
31+
32+
static boolean isRandom3() {
33+
return random == 3;
34+
}
35+
36+
@EnabledIf(value = "isRandom1")
37+
@Test
38+
public void testLazyInitialized() throws InterruptedException {
39+
assertLazyInitialized(() -> SingletonEnum.INSTANCE);
40+
assertLazyInitialized(SingletonEager::getInstance);
41+
assertLazyInitialized(SingletonHolder::getInstance);
42+
assertLazyInitialized(SingletonAtomic::getInstance);
43+
assertLazyInitialized(SingletonSerializable::getInstance);
44+
}
45+
46+
private void assertLazyInitialized(Supplier<SingletonInstance> supplier) throws InterruptedException {
47+
long startedTime = System.currentTimeMillis();
48+
Thread.sleep(10);
49+
var instance = supplier.get();
50+
long instanceTime = instance.getCreationTime();
51+
Assertions.assertTrue(startedTime < instanceTime);
52+
}
53+
54+
@EnabledIf(value = "isRandom2")
1055
@Test
11-
public void shouldOnlyAllowToCreateOneInstanceOfSingleton() throws InterruptedException {
12-
var instance = Singleton.getInstance();
13-
long time = instance.getTime();
56+
public void testSerializationSafe() throws IOException, ClassNotFoundException {
57+
assertSerializationSafe(() -> SingletonEnum.INSTANCE);
58+
assertSerializationSafe(SingletonSerializable::getInstance);
59+
}
60+
61+
private void assertSerializationSafe(Supplier<SingletonInstance> supplier) throws IOException, ClassNotFoundException {
62+
SingletonInstance instance = supplier.get();
1463

15-
TimeUnit.MILLISECONDS.sleep(50);
16-
long timeAfterBreak = Singleton.getInstance().getTime();
64+
var arrayOut = new ByteArrayOutputStream();
65+
try (var out = new ObjectOutputStream(arrayOut)) {
66+
out.writeObject(instance);
67+
}
1768

18-
Assertions.assertSame(instance, Singleton.getInstance());
19-
Assertions.assertEquals(time, timeAfterBreak);
69+
SingletonInstance deserializedInstance;
70+
try (var in = new ObjectInputStream(new ByteArrayInputStream(arrayOut.toByteArray()))) {
71+
deserializedInstance = (SingletonInstance) in.readObject();
72+
}
73+
74+
Assertions.assertSame(instance, deserializedInstance, "Singleton instance should be the same after deserialization");
2075
}
2176

77+
@EnabledIf(value = "isRandom3")
78+
@Test
79+
public void testThreadSafe() {
80+
assertThreadSafe(() -> SingletonEnum.INSTANCE);
81+
assertThreadSafe(SingletonEager::getInstance);
82+
assertThreadSafe(SingletonHolder::getInstance);
83+
assertThreadSafe(SingletonAtomic::getInstance);
84+
assertThreadSafe(SingletonSerializable::getInstance);
85+
}
86+
87+
private void assertThreadSafe(Supplier<SingletonInstance> supplier) {
88+
var first = CompletableFuture.supplyAsync(() -> {
89+
var singleton = supplier.get();
90+
System.out.println("First ... " + singleton.getClass().getSimpleName());
91+
return singleton;
92+
});
93+
94+
var second = CompletableFuture.supplyAsync(() -> {
95+
var singleton = supplier.get();
96+
System.out.println("Second ... " + singleton.getClass().getSimpleName());
97+
return singleton;
98+
});
99+
100+
SingletonInstance firstResult = first.join();
101+
SingletonInstance secondResult = second.join();
102+
103+
Assertions.assertSame(firstResult, secondResult);
104+
Assertions.assertEquals(firstResult.getCreationTime(), secondResult.getCreationTime());
105+
}
22106
}

0 commit comments

Comments
 (0)