From f09b777e4df039d048d7d6d239d91d5263273565 Mon Sep 17 00:00:00 2001 From: mperor Date: Mon, 3 Feb 2025 13:21:31 +0100 Subject: [PATCH] Add state design pattern implementations with tests --- DesignPatterns/README.md | 1 + .../behavioral/state/flow/GreenLight.java | 14 +++++++++ .../behavioral/state/flow/RedLight.java | 14 +++++++++ .../behavioral/state/flow/TrafficLight.java | 22 +++++++++++++ .../state/flow/TrafficLightState.java | 8 +++++ .../behavioral/state/flow/YellowLight.java | 14 +++++++++ .../behavioral/state/machine/MediaPlayer.java | 26 ++++++++++++++++ .../state/machine/MediaPlayerState.java | 10 ++++++ .../behavioral/state/machine/PausedState.java | 21 +++++++++++++ .../state/machine/PlayingState.java | 21 +++++++++++++ .../state/machine/StoppedState.java | 20 ++++++++++++ .../state/flow/TrafficLightStateTest.java | 31 +++++++++++++++++++ .../state/machine/MediaPlayerStateTest.java | 30 ++++++++++++++++++ 13 files changed, 232 insertions(+) create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/GreenLight.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/RedLight.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLight.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightState.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/YellowLight.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayer.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerState.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PausedState.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PlayingState.java create mode 100644 DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/StoppedState.java create mode 100644 DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightStateTest.java create mode 100644 DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerStateTest.java diff --git a/DesignPatterns/README.md b/DesignPatterns/README.md index 4c03597..2f7ce65 100644 --- a/DesignPatterns/README.md +++ b/DesignPatterns/README.md @@ -12,6 +12,7 @@ with practical examples and best practices for using design patterns to create r - [Command](src/main/java/pl/mperor/lab/java/design/pattern/behavioral/command) 📝 - [Execute Around Method (EAM)](src/main/java/pl/mperor/lab/java/design/pattern/behavioral/eam) ⭕ - [Observer](src/main/java/pl/mperor/lab/java/design/pattern/behavioral/observer) 👀 +- [State](src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state) 📜 - [Strategy](src/main/java/pl/mperor/lab/java/design/pattern/behavioral/strategy) 🎯 - [Template Method](src/main/java/pl/mperor/lab/java/design/pattern/behavioral/template/method) 📋 diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/GreenLight.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/GreenLight.java new file mode 100644 index 0000000..f45b437 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/GreenLight.java @@ -0,0 +1,14 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.flow; + +record GreenLight() implements TrafficLightState { + + @Override + public void change(TrafficLight trafficLight) { + trafficLight.setState(new YellowLight()); + } + + @Override + public void display() { + System.out.println("🟢 GO! The light is GREEN."); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/RedLight.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/RedLight.java new file mode 100644 index 0000000..c25bbea --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/RedLight.java @@ -0,0 +1,14 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.flow; + +record RedLight() implements TrafficLightState { + + @Override + public void change(TrafficLight trafficLight) { + trafficLight.setState(new GreenLight()); + } + + @Override + public void display() { + System.out.println("🔴 STOP! The light is RED."); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLight.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLight.java new file mode 100644 index 0000000..fdacef0 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLight.java @@ -0,0 +1,22 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.flow; + +class TrafficLight { + + private TrafficLightState state; + + TrafficLight() { + this.state = new RedLight(); + } + + void setState(TrafficLightState state) { + this.state = state; + } + + void change() { + state.change(this); + } + + void display() { + state.display(); + } +} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightState.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightState.java new file mode 100644 index 0000000..3bf3520 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightState.java @@ -0,0 +1,8 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.flow; + +sealed interface TrafficLightState permits RedLight, YellowLight, GreenLight { + + void change(TrafficLight trafficLight); + + void display(); +} diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/YellowLight.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/YellowLight.java new file mode 100644 index 0000000..3ace0a9 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/YellowLight.java @@ -0,0 +1,14 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.flow; + +record YellowLight() implements TrafficLightState { + + @Override + public void change(TrafficLight trafficLight) { + trafficLight.setState(new RedLight()); + } + + @Override + public void display() { + System.out.println("🟡 Caution! The light is YELLOW."); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayer.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayer.java new file mode 100644 index 0000000..2d6c147 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayer.java @@ -0,0 +1,26 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.machine; + +public class MediaPlayer { + + private MediaPlayerState state; + + public MediaPlayer() { + this.state = new StoppedState(); + } + + public void setState(MediaPlayerState state) { + this.state = state; + } + + public void pressPlay() { + state.pressPlay(this); + } + + public void pressPause() { + state.pressPause(this); + } + + public void pressStop() { + state.pressStop(this); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerState.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerState.java new file mode 100644 index 0000000..95c231f --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerState.java @@ -0,0 +1,10 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.machine; + +sealed interface MediaPlayerState permits PausedState, PlayingState, StoppedState { + + void pressPlay(MediaPlayer player); + + void pressPause(MediaPlayer player); + + void pressStop(MediaPlayer player); +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PausedState.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PausedState.java new file mode 100644 index 0000000..2363c60 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PausedState.java @@ -0,0 +1,21 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.machine; + +record PausedState() implements MediaPlayerState { + + @Override + public void pressPlay(MediaPlayer player) { + System.out.println("▶ Resuming music..."); + player.setState(new PlayingState()); + } + + @Override + public void pressPause(MediaPlayer player) { + System.out.println("⏸ Already paused!"); + } + + @Override + public void pressStop(MediaPlayer player) { + System.out.println("⏹ Stopping music..."); + player.setState(new StoppedState()); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PlayingState.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PlayingState.java new file mode 100644 index 0000000..d68ca08 --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/PlayingState.java @@ -0,0 +1,21 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.machine; + +record PlayingState() implements MediaPlayerState { + + @Override + public void pressPlay(MediaPlayer player) { + System.out.println("▶ Already playing!"); + } + + @Override + public void pressPause(MediaPlayer player) { + System.out.println("⏸ Pausing music..."); + player.setState(new PausedState()); + } + + @Override + public void pressStop(MediaPlayer player) { + System.out.println("⏹ Stopping music..."); + player.setState(new StoppedState()); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/StoppedState.java b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/StoppedState.java new file mode 100644 index 0000000..1a283ca --- /dev/null +++ b/DesignPatterns/src/main/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/StoppedState.java @@ -0,0 +1,20 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.machine; + +record StoppedState() implements MediaPlayerState { + + @Override + public void pressPlay(MediaPlayer player) { + System.out.println("▶ Starting music..."); + player.setState(new PlayingState()); + } + + @Override + public void pressPause(MediaPlayer player) { + System.out.println("⏸ Can't pause! Music is stopped."); + } + + @Override + public void pressStop(MediaPlayer player) { + System.out.println("⏹ Already stopped!"); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightStateTest.java b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightStateTest.java new file mode 100644 index 0000000..c5623df --- /dev/null +++ b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/flow/TrafficLightStateTest.java @@ -0,0 +1,31 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.flow; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import pl.mperor.lab.common.TestUtils; + +import java.util.stream.Stream; + +public class TrafficLightStateTest { + + @Test + public void testTrafficLightStateChanges() { + var out = TestUtils.setTempSystemOut(); + var trafficLight = new TrafficLight(); + + Stream.generate(() -> { + trafficLight.display(); + trafficLight.change(); + return trafficLight; + }).limit(4).count(); + + Assertions.assertLinesMatch(""" + 🔴 STOP! The light is RED. + 🟢 GO! The light is GREEN. + 🟡 Caution! The light is YELLOW. + 🔴 STOP! The light is RED. + """.lines(), out.lines().stream()); + + TestUtils.resetSystemOut(); + } +} \ No newline at end of file diff --git a/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerStateTest.java b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerStateTest.java new file mode 100644 index 0000000..c4c503e --- /dev/null +++ b/DesignPatterns/src/test/java/pl/mperor/lab/java/design/pattern/behavioral/state/machine/MediaPlayerStateTest.java @@ -0,0 +1,30 @@ +package pl.mperor.lab.java.design.pattern.behavioral.state.machine; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import pl.mperor.lab.common.TestUtils; + +public class MediaPlayerStateTest { + + @Test + public void shouldBehaveLikeStateMachine() { + var out = TestUtils.setTempSystemOut(); + + var player = new MediaPlayer(); + player.pressPlay(); // Stopped -> Playing + player.pressPause(); // Playing -> Paused + player.pressPlay(); // Paused -> Playing + player.pressStop(); // Playing -> Stopped + player.pressPause(); // Invalid + + Assertions.assertLinesMatch(""" + ▶ Starting music... + ⏸ Pausing music... + ▶ Resuming music... + ⏹ Stopping music... + ⏸ Can't pause! Music is stopped. + """.lines(), out.lines().stream()); + + TestUtils.resetSystemOut(); + } +} \ No newline at end of file