diff --git a/bloc/Readme.md b/bloc/Readme.md deleted file mode 100644 index 228af1634052..000000000000 --- a/bloc/Readme.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -title: "Bloc Pattern in Java: State Management Simplified" -shortTitle: Bloc -description: "Learn how the Bloc pattern helps manage state changes in Java applications. This guide covers dynamic listener management, real-world examples, and clean code practices for state management." -category: Structural -language: en -tag: - - State Management - - Event-driven - - Listener Management - - Object Composition - - Dynamic Behavior ---- - -## Also known as - -* Event-driven State Management -* State Listener Pattern - -## Intent of the Bloc Pattern - -The Bloc pattern manages the state of an object and allows for dynamically notifying interested listeners about state changes. It separates state management logic from the rest of the application, improving code organization and flexibility. - -## Detailed explanation of the Bloc pattern with real-World examples - -### Real-world example - -> Consider a digital counter application where multiple parts of the UI need to be updated whenever the counter changes. For example, a label displaying the counter value and an activity log showing changes. Instead of directly modifying these UI components, the Bloc pattern manages the counter state and notifies all registered listeners about the state change. Listeners can dynamically subscribe or unsubscribe from receiving updates. - -### In plain words - -> The Bloc pattern manages a single state object and dynamically notifies registered listeners whenever the state changes. - -### Wikipedia says - -> While not a formalized "Gang of Four" design pattern, Bloc is widely used in state-driven applications. It centralizes state management and propagates state changes to registered observers, following principles of separation of concerns. - ---- - -## Programmatic Example of the Bloc Pattern in Java - -### **Core Components of the Bloc Pattern** - -#### **1. State Object** - -The `State` class holds the representation of the state of the application that will be passed to listeners whenever there is a change to do but in this example it's simplified to be a single value. - -```java -package com.iluwatar.bloc; - -import lombok.Getter; - -@Getter -public class State { - private final int value; - - public State(int value) { - this.value = value; - } - -} -``` -The `ListenerManager` interface manages the basic operations for the listeners and is implemented by bloc class -```java -import java.util.List; - -public interface ListenerManager { - void addListener(StateListener listener); - void removeListener(StateListener listener); - List> getListeners(); -} -``` -The `StateListener` interface has a method that the listener needs to react to changes in the state and is used by bloC to notify listeners whenever there is an update to state. -```java -public interface StateListener { -void onStateChange(T state); -} -``` - -The `Bloc` class holds the current state and manages logic of states and notifies the list of listeners when states changes. -The `Bloc` class contains methods for listeners and states like emitstate which updates the currentstate and notifies listeners addlistener which adds new listener to the listeners list and notifies it with the currentstate removelistener which removes listener from the listeners list and increment which increases the state value by 1 which is like an update to the current state and notifies the listeners in listeners list with the new state which holds a value incremented by 1 and decrement functions which does the opposite of increment function and notifies listeners in listeners list. -```java -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class Bloc implements ListenerManager { -private State currentState; -private final List> listeners = new ArrayList<>(); - -public Bloc() { -this.currentState = new State(0); -} - -@Override -public void addListener(StateListener listener) { -listeners.add(listener); -listener.onStateChange(currentState); -} - -@Override -public void removeListener(StateListener listener) { -listeners.remove(listener); -} - -@Override -public List> getListeners() { -return Collections.unmodifiableList(listeners); -} - -private void emitState(State newState) { -currentState = newState; -for (StateListener listener : listeners) { -listener.onStateChange(currentState); -} -} - -public void increment() { -emitState(new State(currentState.getValue() + 1)); -} - -public void decrement() { -emitState(new State(currentState.getValue() - 1)); -} -} -``` -The `main` class have a simple gui to try and test the bloc pattern components separately from the ui components. -the `main` class creates an instance of bloc then adds a listener to update the ui which resembles the counter and some buttons to change the states and toggle the listener dynamically -```java -import javax.swing.*; -import java.awt.*; - -public class Main { -public static void main(String[] args) { -Bloc bloc = new Bloc(); -JFrame frame = new JFrame("Bloc Example"); -frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); -frame.setSize(400, 300); -JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); -counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); -JButton incrementButton = new JButton("Increment"); -JButton decrementButton = new JButton("Decrement"); -JButton toggleListenerButton = new JButton("Disable Listener"); - - frame.setLayout(new BorderLayout()); - frame.add(counterLabel, BorderLayout.CENTER); - frame.add(incrementButton, BorderLayout.NORTH); - frame.add(decrementButton, BorderLayout.SOUTH); - frame.add(toggleListenerButton, BorderLayout.EAST); - - StateListener stateListener = state -> counterLabel.setText("Counter: " + state.getValue()); - - bloc.addListener(stateListener); - - toggleListenerButton.addActionListener(e -> { - if (bloc.getListeners().contains(stateListener)) { - bloc.removeListener(stateListener); - toggleListenerButton.setText("Enable Listener"); - } else { - bloc.addListener(stateListener); - toggleListenerButton.setText("Disable Listener"); - } - }); - - incrementButton.addActionListener(e -> bloc.increment()); - decrementButton.addActionListener(e -> bloc.decrement()); - - frame.setVisible(true); -} -} -``` -## Program Output - -- **On Increment** - `Counter: 1` - -- **On Decrement** - `Counter: 0` - -- **Dynamic Listener Toggle** - - Listener disabled: Counter stops updating. - - Listener enabled: Counter updates again. - ---- - -## When to Use the Bloc Pattern - -Use the Bloc pattern when: - -- You need a centralized system to manage state updates. -- You want to dynamically add/remove listeners without tight coupling. -- You are building an event-driven or state-driven system, such as UI frameworks. ---- - -## Real-World Applications of Bloc Pattern - -- **UI State Management**: Reacting to button clicks, updating labels, and toggling views. -- **Event-driven Systems**: Handling multiple subscribers efficiently for state updates. ---- - -## Benefits and Trade-offs of Bloc Pattern - -### Benefits: -- Clean separation of state management and UI logic. -- Flexibility to dynamically add/remove listeners. -- Centralized state propagation. - -### Trade-offs: -- Adds some complexity with the listener management mechanism. -- May introduce performance concerns with excessive listeners. -- the bloc class handles too many methods which violates the single responsbility principle ---- - -## Related Patterns - -- **Observer**: Bloc is a specialized implementation of the Observer pattern. -- **Mediator**: Bloc centralizes communication and state propagation. -- **cubit**: bloC is more general implementation than cubit ---- - -## References and Credits - -- [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) -- [Java Swing Documentation](https://docs.oracle.com/javase/tutorial/uiswing/) -- [Event-Driven Programming in Java](https://www.oracle.com/java/) -- [bloC archetecture](https://bloclibrary.dev/architecture/) -- [flutter bloC package](https://pub.dev/documentation/flutter_bloc/latest/) - diff --git a/bloc/etc/bloc.png b/bloc/etc/bloc.png deleted file mode 100644 index 60d6eb77c8fc..000000000000 Binary files a/bloc/etc/bloc.png and /dev/null differ diff --git a/bloc/etc/bloc.puml b/bloc/etc/bloc.puml deleted file mode 100644 index 5991f533ae70..000000000000 --- a/bloc/etc/bloc.puml +++ /dev/null @@ -1,41 +0,0 @@ -@startuml -package com.iluwatar.bloc { - - class State { - - value : int - + State(value : int) - + getValue() : int - } - - interface StateListener { - + onStateChange(state : T) - } - - interface ListenerManager { - + addListener(listener : StateListener) - + removeListener(listener : StateListener) - + getListeners() : List> - } - - class BloC { - - currentState : State - - listeners : List> - + BloC() - + addListener(listener : StateListener) - + removeListener(listener : StateListener) - + getListeners() : List> - - emitState(newState : State) - + increment() - + decrement() - } - - class Main { - + main(args : String[]) - } - - ListenerManager <|.. BloC - StateListener <|.. BloC - BloC o-- State - BloC *-- StateListener -} -@enduml diff --git a/bloc/pom.xml b/bloc/pom.xml deleted file mode 100644 index 1edc49f03721..000000000000 --- a/bloc/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.26.0-SNAPSHOT - - bloc - - - org.junit.jupiter - junit-jupiter-api - test - - - org.testng - testng - 7.7.1 - test - - - org.assertj - assertj-core - 3.24.2 - test - - - org.mockito - mockito-inline - test - - - junit - junit - test - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - - com.iluwatar.bloC.App - - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - diff --git a/bloc/src/main/java/com/iluwatar/bloc/Bloc.java b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java deleted file mode 100644 index caed7080ab1c..000000000000 --- a/bloc/src/main/java/com/iluwatar/bloc/Bloc.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.iluwatar.bloc; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * The Bloc class is responsible for managing the current state and notifying registered listeners - * whenever the state changes. It implements the ListenerManager interface, allowing listeners - * to be added, removed, and notified of state changes. - */ -public class Bloc implements ListenerManager { - - private State currentState; - private final List> listeners = new ArrayList<>(); - - /** - * Constructs a new Bloc instance with an initial state of value 0. - */ - public Bloc() { - this.currentState = new State(0); - } - - /** - * Adds a listener to receive state change notifications. - * - * @param listener the listener to add - */ - @Override - public void addListener(StateListener listener) { - listeners.add(listener); - listener.onStateChange(currentState); - } - - /** - * Removes a listener from receiving state change notifications. - * - * @param listener the listener to remove - */ - @Override - public void removeListener(StateListener listener) { - listeners.remove(listener); - } - - /** - * Returns an unmodifiable list of all registered listeners. - * - * @return an unmodifiable list of listeners - */ - @Override - public List> getListeners() { - return Collections.unmodifiableList(listeners); - } - - /** - * Emits a new state and notifies all registered listeners of the change. - * - * @param newState the new state to emit - */ - private void emitState(State newState) { - currentState = newState; - for (StateListener listener : listeners) { - listener.onStateChange(currentState); - } - } - - /** - * Increments the current state value by 1 and notifies listeners of the change. - */ - public void increment() { - emitState(new State(currentState.value() + 1)); - } - - /** - * Decrements the current state value by 1 and notifies listeners of the change. - */ - public void decrement() { - emitState(new State(currentState.value() - 1)); - } -} diff --git a/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java deleted file mode 100644 index e74536c10c40..000000000000 --- a/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.iluwatar.bloc; - -import java.awt.BorderLayout; -import java.awt.Font; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.SwingConstants; -import javax.swing.WindowConstants; - -/** - * The BlocUI class handles the creation and management of the UI components. - */ -public class BlocUi { - - /** - * Creates and shows the UI. - */ - public void createAndShowUi() { - // Create a Bloc instance to manage the state - final Bloc bloc = new Bloc(); - - // setting up a frame window with a title - JFrame frame = new JFrame("BloC example"); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.setSize(400, 300); - - // label to display the counter value - JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); - counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); - - // buttons for increment, decrement, and toggling listener - JButton decrementButton = new JButton("Decrement"); - JButton toggleListenerButton = new JButton("Disable Listener"); - JButton incrementButton = new JButton("Increment"); - - frame.setLayout(new BorderLayout()); - frame.add(counterLabel, BorderLayout.CENTER); - frame.add(incrementButton, BorderLayout.NORTH); - frame.add(decrementButton, BorderLayout.SOUTH); - frame.add(toggleListenerButton, BorderLayout.EAST); - - // making a state listener to update the counter label when the state changes - StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value()); - - // adding the listener to the Bloc instance - bloc.addListener(stateListener); - - toggleListenerButton.addActionListener(e -> { - if (bloc.getListeners().contains(stateListener)) { - bloc.removeListener(stateListener); - toggleListenerButton.setText("Enable Listener"); - } else { - bloc.addListener(stateListener); - toggleListenerButton.setText("Disable Listener"); - } - }); - - incrementButton.addActionListener(e -> bloc.increment()); - decrementButton.addActionListener(e -> bloc.decrement()); - - frame.setVisible(true); - } -} \ No newline at end of file diff --git a/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java deleted file mode 100644 index 66caeddafea0..000000000000 --- a/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.iluwatar.bloc; -import java.util.List; - -/** - * Interface for managing listeners for state changes. - * - * @param The type of state to be handled by the listeners. - */ - -public interface ListenerManager { - - /** - * Adds a listener that will be notified of state changes. - * - * @param listener the listener to be added - */ - void addListener(StateListener listener); - - /** - * Removes a listener so that it no longer receives state change notifications. - * - * @param listener the listener to be removed - */ - void removeListener(StateListener listener); - - /** - * Returns a list of all listeners currently registered for state changes. - * - * @return a list of registered listeners - */ - List> getListeners(); -} diff --git a/bloc/src/main/java/com/iluwatar/bloc/Main.java b/bloc/src/main/java/com/iluwatar/bloc/Main.java deleted file mode 100644 index 1499e6211e56..000000000000 --- a/bloc/src/main/java/com/iluwatar/bloc/Main.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.iluwatar.bloc; - -/** - * The BLoC (Business Logic Component) pattern is a software design pattern primarily used - * in Flutter applications. It facilitates the separation of business logic from UI code, - * making the application more modular, testable, and scalable. The BLoC pattern uses streams - * to manage the flow of data and state changes, allowing widgets to react to new states as - * they arrive. - * In the BLoC pattern, the application is divided into three key components: - * - Input streams: Represent user interactions or external events fed into the BLoC. - * - Business logic: Processes the input and determines the resulting state or actions. - * - Output streams: Emit the updated state for the UI to consume. - * The BLoC pattern is especially useful in reactive programming scenarios and aligns well with the declarative nature of Flutter. - * By using this pattern, developers can ensure a clear separation of concerns, enhance reusability, and maintain consistent state management throughout the application. - */ - -public class Main { - - /** - * The entry point of the application. Initializes the GUI. - * - * @param args command-line arguments (not used in this example) - */ - public static void main(String[] args) { - BlocUi blocUi = new BlocUi(); - blocUi.createAndShowUi(); - } -} \ No newline at end of file diff --git a/bloc/src/main/java/com/iluwatar/bloc/State.java b/bloc/src/main/java/com/iluwatar/bloc/State.java deleted file mode 100644 index 5e184d2af20b..000000000000 --- a/bloc/src/main/java/com/iluwatar/bloc/State.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.iluwatar.bloc; - -/** - * The {@code State} class represents a state with an integer value. - * This class encapsulates the value and provides methods to retrieve it. - */ -public record State(int value) { -} \ No newline at end of file diff --git a/bloc/src/main/java/com/iluwatar/bloc/StateListener.java b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java deleted file mode 100644 index 49527441eedb..000000000000 --- a/bloc/src/main/java/com/iluwatar/bloc/StateListener.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.iluwatar.bloc; - -/** - * The {@code StateListener} interface defines the contract for listening to state changes. - * Implementations of this interface should handle state changes and define actions to take when the state changes. - * - * @param the type of state that this listener will handle - */ -public interface StateListener { - - /** - * This method is called when the state has changed. - * - * @param state the updated state - */ - void onStateChange(T state); -} diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java deleted file mode 100644 index e47d24634a70..000000000000 --- a/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.iluwatar.bloc; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.*; - -class BlocTest { - private Bloc bloc; - private AtomicInteger stateValue; - @BeforeEach - void setUp() { - bloc = new Bloc(); - stateValue = new AtomicInteger(0); - } - @Test - void initialState() { - assertTrue(bloc.getListeners().isEmpty(), "No listeners should be present initially."); - } - - @Test - void IncrementUpdateState() { - bloc.addListener(state -> stateValue.set(state.value())); - bloc.increment(); - assertEquals(1, stateValue.get(), "State should increment to 1"); - } - - @Test - void DecrementUpdateState() { - bloc.addListener(state -> stateValue.set(state.value())); - bloc.decrement(); - assertEquals(-1, stateValue.get(), "State should decrement to -1"); - } - - @Test - void addingListener() { - bloc.addListener(state -> {}); - assertEquals(1, bloc.getListeners().size(), "Listener count should be 1."); - } - - @Test - void removingListener() { - StateListener listener = state -> {}; - bloc.addListener(listener); - bloc.removeListener(listener); - assertTrue(bloc.getListeners().isEmpty(), "Listener count should be 0 after removal."); - } - @Test - void multipleListeners() { - AtomicInteger secondValue = new AtomicInteger(); - bloc.addListener(state -> stateValue.set(state.value())); - bloc.addListener(state -> secondValue.set(state.value())); - bloc.increment(); - assertEquals(1, stateValue.get(), "First listener should receive state 1."); - assertEquals(1, secondValue.get(), "Second listener should receive state 1."); - } -} diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java deleted file mode 100644 index 7b793f89461a..000000000000 --- a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.iluwatar.bloc; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import javax.swing.*; -import java.awt.*; - -import static org.junit.Assert.assertEquals; - -public class BlocUiTest { - - private JFrame frame; - private JLabel counterLabel; - private JButton incrementButton; - private JButton decrementButton; - private JButton toggleListenerButton; - private Bloc bloc; - private StateListener stateListener; - - @Before - public void setUp() { - bloc = new Bloc(); // Re-initialize the Bloc for each test - - frame = new JFrame("BloC example"); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - frame.setSize(400, 300); - - counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); - counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); - - incrementButton = new JButton("Increment"); - decrementButton = new JButton("Decrement"); - toggleListenerButton = new JButton("Disable Listener"); - - frame.setLayout(new BorderLayout()); - frame.add(counterLabel, BorderLayout.CENTER); - frame.add(incrementButton, BorderLayout.NORTH); - frame.add(decrementButton, BorderLayout.SOUTH); - frame.add(toggleListenerButton, BorderLayout.EAST); - - stateListener = state -> counterLabel.setText("Counter: " + state.value()); - bloc.addListener(stateListener); - - incrementButton.addActionListener(e -> bloc.increment()); - decrementButton.addActionListener(e -> bloc.decrement()); - toggleListenerButton.addActionListener(e -> { - if (bloc.getListeners().contains(stateListener)) { - bloc.removeListener(stateListener); - toggleListenerButton.setText("Enable Listener"); - } else { - bloc.addListener(stateListener); - toggleListenerButton.setText("Disable Listener"); - } - }); - - frame.setVisible(true); - } - - @After - public void tearDown() { - frame.dispose(); - bloc = new Bloc(); // Reset Bloc state after each test to avoid state carryover - } - - - @Test - public void testIncrementButton() { - simulateButtonClick(incrementButton); - assertEquals("Counter: 1", counterLabel.getText()); - } - - @Test - public void testDecrementButton() { - simulateButtonClick(decrementButton); - assertEquals("Counter: -1", counterLabel.getText()); - } - - @Test - public void testToggleListenerButton() { - // Disable listener - simulateButtonClick(toggleListenerButton); - simulateButtonClick(incrementButton); - assertEquals("Counter: 0", counterLabel.getText()); // Listener is disabled - - // Enable listener - simulateButtonClick(toggleListenerButton); - simulateButtonClick(incrementButton); - assertEquals("Counter: 2", counterLabel.getText()); // Listener is re-enabled - } - - private void simulateButtonClick(JButton button) { - for (var listener : button.getActionListeners()) { - listener.actionPerformed(null); - } - } -} diff --git a/bloc/src/test/java/com/iluwatar/bloc/MainTest.java b/bloc/src/test/java/com/iluwatar/bloc/MainTest.java deleted file mode 100644 index b8d1b0c70ca4..000000000000 --- a/bloc/src/test/java/com/iluwatar/bloc/MainTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.iluwatar.bloc; - -import org.junit.jupiter.api.Test; - -import static org.mockito.Mockito.mockStatic; - - -class MainTest { - - @Test - void testMain() { - try (var mockedBlocUi = mockStatic(BlocUi.class)) { - // Call the main method - Main.main(new String[]{}); - - // Verify that createAndShowUi was called - mockedBlocUi.verify(() -> new BlocUi().createAndShowUi()); - } - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5f0d579cf78f..5cc512b7de94 100644 --- a/pom.xml +++ b/pom.xml @@ -223,7 +223,6 @@ templateview money table-inheritance - bloc