Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.4.0-jre</version>
</dependency>
</dependencies>

</project>
36 changes: 36 additions & 0 deletions src/main/java/observer/App.java
Original file line number Diff line number Diff line change
@@ -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");
}

}
31 changes: 31 additions & 0 deletions src/main/java/observer/event/BaseEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package observer.event;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

public abstract class BaseEvent<T> implements Event<T> {
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));
}
}
7 changes: 7 additions & 0 deletions src/main/java/observer/event/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package observer.event;

public interface Event<T> {
long timestamp();

T source();
}
7 changes: 7 additions & 0 deletions src/main/java/observer/event/WeatherUpdateEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package observer.event;

public class WeatherUpdateEvent extends BaseEvent<String> {
public WeatherUpdateEvent(String source) {
super(source);
}
}
56 changes: 56 additions & 0 deletions src/main/java/observer/eventbus/EventBus.java
Original file line number Diff line number Diff line change
@@ -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<Class<?>, List<EventListener<?>>> listenerMap = Maps.newConcurrentMap();

/**
* subscribe event
*
* @param eventClass event type
* @param eventListener event listener
* @param <E> event generics
*/
public <E extends Event<?>> void subscribe(Class<E> eventClass, EventListener<E> eventListener) {
listenerMap.computeIfAbsent(eventClass, v -> Lists.newCopyOnWriteArrayList()).add(eventListener);
}

/**
* cancel
*
* @param eventClass event type
* @param eventListener event listener
* @param <E> event generics
*/
public <E extends Event<?>> void unsubscribe(Class<E> eventClass, EventListener<E> eventListener) {
List<EventListener<?>> eventListeners = listenerMap.get(eventClass);
if (eventListeners != null) {
eventListeners.remove(eventListener);
}
}

/**
* publish event
*
* @param event event
* @param <E> event generics
*/
@SuppressWarnings("unchecked")
public <E extends Event<?>> void publish(E event) {
List<EventListener<?>> eventListeners = listenerMap.get(event.getClass());
if (eventListeners != null) {
eventListeners.forEach(eventListener -> ((EventListener<E>) eventListener).onEvent(event));
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/observer/listener/EventListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package observer.listener;

import observer.event.Event;

@FunctionalInterface
public interface EventListener<T extends Event<?>> {
void onEvent(T event);
}
21 changes: 21 additions & 0 deletions src/main/java/observer/listener/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package observer.listener;

import observer.event.WeatherUpdateEvent;

import java.util.function.Consumer;

public class User implements EventListener<WeatherUpdateEvent> {
private final String name;
private final Consumer<String> consumer;

public User(String name, Consumer<String> 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);
}
}
52 changes: 52 additions & 0 deletions src/main/java/observer/publisher/Publisher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package observer.publisher;

import observer.event.Event;
import observer.eventbus.EventBus;

import java.util.Objects;

public abstract class Publisher<E extends Event<?>> {
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;
}
}
20 changes: 20 additions & 0 deletions src/main/java/observer/publisher/WeatherStationPublisher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package observer.publisher;

import observer.event.WeatherUpdateEvent;
import observer.eventbus.EventBus;

import java.util.Random;

public class WeatherStationPublisher extends Publisher<WeatherUpdateEvent> {
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);
}
}
7 changes: 0 additions & 7 deletions src/main/java/org/example/Main.java

This file was deleted.