mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-08-06 04:23:22 +00:00
* added bloC design pattern * added bloC design pattern * added Readme file * fixed checkstyle warnings * added tests for the ui * fixed a test in MainTest file * separating ui from main file and adding more tests * added pom.xml plugins and properties and fixed readme.md * fixed renaming problem and added context to main * chsnged state class to record * syncing changes for conflicts * Revert "fixed conflicts" * restored files * renamed readme file and abstracted pom file
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
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<State> {
|
||||
|
||||
private State currentState;
|
||||
private final List<StateListener<State>> 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<State> 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<State> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable list of all registered listeners.
|
||||
*
|
||||
* @return an unmodifiable list of listeners
|
||||
*/
|
||||
@Override
|
||||
public List<StateListener<State>> 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<State> 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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
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<State> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.iluwatar.bloc;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for managing listeners for state changes.
|
||||
*
|
||||
* @param <T> The type of state to be handled by the listeners.
|
||||
*/
|
||||
|
||||
public interface ListenerManager<T> {
|
||||
|
||||
/**
|
||||
* Adds a listener that will be notified of state changes.
|
||||
*
|
||||
* @param listener the listener to be added
|
||||
*/
|
||||
void addListener(StateListener<T> listener);
|
||||
|
||||
/**
|
||||
* Removes a listener so that it no longer receives state change notifications.
|
||||
*
|
||||
* @param listener the listener to be removed
|
||||
*/
|
||||
void removeListener(StateListener<T> listener);
|
||||
|
||||
/**
|
||||
* Returns a list of all listeners currently registered for state changes.
|
||||
*
|
||||
* @return a list of registered listeners
|
||||
*/
|
||||
List<StateListener<T>> getListeners();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
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) {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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 <T> the type of state that this listener will handle
|
||||
*/
|
||||
public interface StateListener<T> {
|
||||
|
||||
/**
|
||||
* This method is called when the state has changed.
|
||||
*
|
||||
* @param state the updated state
|
||||
*/
|
||||
void onStateChange(T state);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
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<State> 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.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
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<State> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user