diff --git a/pom.xml b/pom.xml
index 5c2a7e1..ea6faab 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,11 @@
spring-boot-starter-web
3.4.5
+
+ com.google.guava
+ guava
+ 33.4.0-jre
+
\ No newline at end of file
diff --git a/src/main/java/observer/App.java b/src/main/java/observer/App.java
new file mode 100644
index 0000000..6759d83
--- /dev/null
+++ b/src/main/java/observer/App.java
@@ -0,0 +1,36 @@
+package observer;
+
+import observer.event.WeatherUpdateEvent;
+import observer.eventbus.EventBus;
+import observer.listener.User;
+import observer.publisher.WeatherStationPublisher;
+
+public class App {
+
+ public static void main(String[] args) throws InterruptedException {
+ EventBus eventBus = new EventBus();
+ WeatherStationPublisher weatherStation = new WeatherStationPublisher(eventBus);
+
+ // create users
+ User user1 = new User("Tom", System.out::println);
+ User user2 = new User("Jerry", System.out::println);
+
+ // subscribe to weather update events
+ eventBus.subscribe(WeatherUpdateEvent.class, user1);
+ eventBus.subscribe(WeatherUpdateEvent.class, user2);
+
+ // activate the weather station
+ weatherStation.start();
+ System.out.println("The weather broadcast system has been activated");
+
+ // stop after running for a period of time
+ Thread.sleep(6100);
+ eventBus.unsubscribe(WeatherUpdateEvent.class, user1);
+ System.out.println("user1 has been unsubscribed");
+ Thread.sleep(6100);
+ weatherStation.stop();
+
+ System.out.println("The weather broadcast system has been turned off");
+ }
+
+}
diff --git a/src/main/java/observer/event/BaseEvent.java b/src/main/java/observer/event/BaseEvent.java
new file mode 100644
index 0000000..5f543ac
--- /dev/null
+++ b/src/main/java/observer/event/BaseEvent.java
@@ -0,0 +1,31 @@
+package observer.event;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Objects;
+
+public abstract class BaseEvent implements Event {
+ private final long timestamp;
+ private final T source;
+ private static final SimpleDateFormat DATE_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+ protected BaseEvent(T source) {
+ this.timestamp = System.currentTimeMillis();
+ this.source = Objects.requireNonNull(source);
+ }
+
+ @Override
+ public long timestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public T source() {
+ return source;
+ }
+
+ public String formattedTimestamp() {
+ return DATE_FORMAT.format(new Date(timestamp));
+ }
+}
diff --git a/src/main/java/observer/event/Event.java b/src/main/java/observer/event/Event.java
new file mode 100644
index 0000000..2a613e2
--- /dev/null
+++ b/src/main/java/observer/event/Event.java
@@ -0,0 +1,7 @@
+package observer.event;
+
+public interface Event {
+ long timestamp();
+
+ T source();
+}
diff --git a/src/main/java/observer/event/WeatherUpdateEvent.java b/src/main/java/observer/event/WeatherUpdateEvent.java
new file mode 100644
index 0000000..8d82e41
--- /dev/null
+++ b/src/main/java/observer/event/WeatherUpdateEvent.java
@@ -0,0 +1,7 @@
+package observer.event;
+
+public class WeatherUpdateEvent extends BaseEvent {
+ public WeatherUpdateEvent(String source) {
+ super(source);
+ }
+}
diff --git a/src/main/java/observer/eventbus/EventBus.java b/src/main/java/observer/eventbus/EventBus.java
new file mode 100644
index 0000000..ff7454c
--- /dev/null
+++ b/src/main/java/observer/eventbus/EventBus.java
@@ -0,0 +1,56 @@
+package observer.eventbus;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import observer.event.Event;
+import observer.listener.EventListener;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Event bus, responsible for event publishing and subscription
+ */
+public class EventBus {
+
+ private final Map, List>> listenerMap = Maps.newConcurrentMap();
+
+ /**
+ * subscribe event
+ *
+ * @param eventClass event type
+ * @param eventListener event listener
+ * @param event generics
+ */
+ public > void subscribe(Class eventClass, EventListener eventListener) {
+ listenerMap.computeIfAbsent(eventClass, v -> Lists.newCopyOnWriteArrayList()).add(eventListener);
+ }
+
+ /**
+ * cancel
+ *
+ * @param eventClass event type
+ * @param eventListener event listener
+ * @param event generics
+ */
+ public > void unsubscribe(Class eventClass, EventListener eventListener) {
+ List> eventListeners = listenerMap.get(eventClass);
+ if (eventListeners != null) {
+ eventListeners.remove(eventListener);
+ }
+ }
+
+ /**
+ * publish event
+ *
+ * @param event event
+ * @param event generics
+ */
+ @SuppressWarnings("unchecked")
+ public > void publish(E event) {
+ List> eventListeners = listenerMap.get(event.getClass());
+ if (eventListeners != null) {
+ eventListeners.forEach(eventListener -> ((EventListener) eventListener).onEvent(event));
+ }
+ }
+}
diff --git a/src/main/java/observer/listener/EventListener.java b/src/main/java/observer/listener/EventListener.java
new file mode 100644
index 0000000..f6c1cb4
--- /dev/null
+++ b/src/main/java/observer/listener/EventListener.java
@@ -0,0 +1,8 @@
+package observer.listener;
+
+import observer.event.Event;
+
+@FunctionalInterface
+public interface EventListener> {
+ void onEvent(T event);
+}
diff --git a/src/main/java/observer/listener/User.java b/src/main/java/observer/listener/User.java
new file mode 100644
index 0000000..908eb6e
--- /dev/null
+++ b/src/main/java/observer/listener/User.java
@@ -0,0 +1,21 @@
+package observer.listener;
+
+import observer.event.WeatherUpdateEvent;
+
+import java.util.function.Consumer;
+
+public class User implements EventListener {
+ private final String name;
+ private final Consumer consumer;
+
+ public User(String name, Consumer consumer) {
+ this.name = name;
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void onEvent(WeatherUpdateEvent event) {
+ String message = String.format("[%s] received weather update event: %s, current time is %s", name, event.source(), event.formattedTimestamp());
+ consumer.accept(message);
+ }
+}
diff --git a/src/main/java/observer/publisher/Publisher.java b/src/main/java/observer/publisher/Publisher.java
new file mode 100644
index 0000000..7ca6c03
--- /dev/null
+++ b/src/main/java/observer/publisher/Publisher.java
@@ -0,0 +1,52 @@
+package observer.publisher;
+
+import observer.event.Event;
+import observer.eventbus.EventBus;
+
+import java.util.Objects;
+
+public abstract class Publisher> {
+ private final EventBus eventBus;
+ private final int intervalMillis;
+ private volatile boolean running;
+ private Thread workerThread;
+
+ public Publisher(EventBus eventBus, int intervalMillis) {
+ this.eventBus = Objects.requireNonNull(eventBus);
+ this.intervalMillis = intervalMillis;
+ }
+
+ protected abstract E getEventInfo();
+
+ public final void start() {
+ if (running) {
+ return;
+ }
+ running = true;
+ workerThread = new Thread(() -> {
+ try {
+ while (running) {
+ E event = getEventInfo();
+ if (event != null) {
+ eventBus.publish(event);
+ Thread.sleep(intervalMillis);
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ workerThread.start();
+ }
+
+ public final void stop() {
+ running = false;
+ if (workerThread != null) {
+ workerThread.interrupt();
+ }
+ }
+
+ public final boolean isRunning() {
+ return running;
+ }
+}
diff --git a/src/main/java/observer/publisher/WeatherStationPublisher.java b/src/main/java/observer/publisher/WeatherStationPublisher.java
new file mode 100644
index 0000000..6f48abd
--- /dev/null
+++ b/src/main/java/observer/publisher/WeatherStationPublisher.java
@@ -0,0 +1,20 @@
+package observer.publisher;
+
+import observer.event.WeatherUpdateEvent;
+import observer.eventbus.EventBus;
+
+import java.util.Random;
+
+public class WeatherStationPublisher extends Publisher {
+ private final Random random = new Random();
+
+ public WeatherStationPublisher(EventBus eventBus) {
+ super(eventBus, 3000);
+ }
+
+ @Override
+ protected WeatherUpdateEvent getEventInfo() {
+ String info = random.nextBoolean() ? "Sunny Day" : "Rain Day";
+ return new WeatherUpdateEvent(info);
+ }
+}
diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java
deleted file mode 100644
index 0c1e8ee..0000000
--- a/src/main/java/org/example/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package org.example;
-
-public class Main {
- public static void main(String[] args) {
- System.out.println("Hello, World!");
- }
-}
\ No newline at end of file