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