小明所在的学校有一个时钟(主题),每到整点时,它就会通知所有的学生(观察者)当前的时间,请你使用观察者模式实现这个时钟通知系统。
注意点:时间从 1 开始,并每隔一个小时更新一次。
输入的第一行是一个整数 N(1 ≤ N ≤ 20),表示学生的数量。
接下来的 N 行,每行包含一个字符串,表示学生的姓名。
最后一行是一个整数,表示时钟更新的次数。
对于每一次时钟更新,输出每个学生的姓名和当前的时间。
2
Alice
Bob
3
Alice 1
Bob 1
Alice 2
Bob 2
Alice 3
Bob 3
主题状态(数据)变化后,通知订阅了该主题的观察者。
亦称:发布者(Publisher)
发布者状态(数据)变化后,通知订阅者(Subscriber)/观察者。
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
public class TimeSubject implements Subject {
private List<Observer> observers;
@Setter
private Integer startTime;
public TimeSubject(Integer startTime) {
this.startTime = startTime;
this.observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(startTime);
}
startTime = (startTime + 1) % 24;
}
}
public interface Observer {
void update(Integer currentTime);
}
public class StudentObserver implements Observer {
private String name;
public StudentObserver(String name) {
this.name = name;
}
@Override
public void update(Integer currentTime) {
System.out.println(name + " " + currentTime);
}
}
public class Application {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
TimeSubject timeSubject = new TimeSubject(1);
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
String name = scanner.next();
Observer studentObserver = new StudentObserver(name);
timeSubject.registerObserver(studentObserver);
}
int frequency = scanner.nextInt();
for (int i = 0; i < frequency; i++) {
timeSubject.notifyObservers();
}
}
分析:
- 客户端使用编辑器,当出现“打开文件”或者“保存文件”时,触发发送邮件或打印日志。
- 很显然,编辑器是“铃声通知器”,是发布者。邮件监听、日志监听是观察者。
public interface Publisher {
void registerObserver(String observeType, Observer observer);
void removeObserver(String observeType, Observer observer);
void notifyObservers(String observeType);
}
public class Editor implements Publisher {
private List<Observer> emailObservers;
private List<Observer> logObservers;
private File file;
public Editor() {
emailObservers = new ArrayList<>();
logObservers = new ArrayList<>();
}
@Override
public void registerObserver(String observeType, Observer observer) {
if ("open file".equals(observeType)) {
emailObservers.add(observer);
} else if ("save file".equals(observeType)) {
logObservers.add(observer);
} else {
throw new RuntimeException("invalid observeType");
}
}
@Override
public void removeObserver(String observeType, Observer observer) {
if ("open file".equals(observeType)) {
emailObservers.remove(observer);
} else if ("save file".equals(observeType)) {
logObservers.remove(observer);
} else {
throw new RuntimeException("invalid observeType");
}
}
@Override
public void notifyObservers(String observeType) {
ObserveContext observeContext = new ObserveContext()
.setObserveType(observeType)
.setFile(file);
if ("open file".equals(observeType)) {
emailObservers.stream().forEach(observer -> observer.update(observeContext));
} else if ("save file".equals(observeType)) {
logObservers.stream().forEach(observer -> observer.update(observeContext));
} else {
throw new RuntimeException("invalid observeType");
}
}
public void openFile(String filePath) {
if (StringUtils.isEmpty(filePath)) {
throw new RuntimeException("filePath is empty");
}
this.file = new File(filePath);
notifyObservers("open file");
}
public void saveFile() {
if (null == this.file) {
throw new RuntimeException("file is null");
}
notifyObservers("save file");
}
}
public interface Observer {
void update(ObserveContext context);
}
public class EmailObserver implements Observer {
private String email;
public EmailObserver(String email) {
this.email = email;
}
@Override
public void update(ObserveContext context) {
String observeType = context.getObserveType();
File file = context.getFile();
Objects.requireNonNull(observeType, "observeType must not be null");
Objects.requireNonNull(file, "file must not be null");
System.out.println("send email to " + email + ", content: " + observeType + " " + file.getName());
}
}
public class LogObserver implements Observer {
private String logFilePath;
public LogObserver(String logFilePath) {
this.logFilePath = logFilePath;
}
@Override
public void update(ObserveContext context) {
String observeType = context.getObserveType();
File file = context.getFile();
Objects.requireNonNull(observeType, "observeType must not be null");
Objects.requireNonNull(file, "file must not be null");
System.out.println("save log to " + logFilePath + ", content: " + observeType + " " + file.getName());
}
}
@Data
@Accessors(chain = true)
public class ObserveContext {
private String observeType;
private File file;
}
打开文件 or 保存文件
public class Application {
public static void main(String[] args) {
Editor editor = new Editor();
editor.registerObserver("open file", new EmailObserver("forrest@qq.com"));
editor.registerObserver("save file", new LogObserver("/user/forrest/log/editor_log.txt"));
editor.openFile("/user/forrest/file/test.txt");
editor.saveFile();
}
}
/*
send email to forrest@qq.com, content: open file test.txt
save log to /user/forrest/log/editor_log.txt, content: save file test.txt
*/
public interface Publisher {
void registerObserver(String observeType, Observer observer);
void removeObserver(String observeType, Observer observer);
void notifyObservers(ObserveContext context); // 这里从`String observeType`变成了`ObserveContext context`(更灵活)
}
// 对发布者逻辑进行封装。
public class PublisherManager implements Publisher {
private static final Map<String, List<Observer>> OBSERVER_MAP = new HashMap<>();
@Override
public void registerObserver(String observeType, Observer observer) {
List<Observer> observerList = OBSERVER_MAP.get(observeType);
if (observerList == null) {
observerList = new ArrayList<>();
OBSERVER_MAP.put(observeType, observerList);
}
observerList.add(observer);
}
@Override
public void removeObserver(String observeType, Observer observer) {
List<Observer> observerList = OBSERVER_MAP.get(observeType);
if (observerList != null) {
observerList.remove(observer);
}
}
@Override
public void notifyObservers(ObserveContext context) {
List<Observer> observerList = OBSERVER_MAP.get(context.getObserveType());
if (observerList != null) {
for (Observer observer : observerList) {
observer.update(context);
}
}
}
}
接口
基于context进行交互,两者的耦合度极低。当发布者重构了,订阅者是无感知的。public class Editor {
private static final Publisher publisherManager = new PublisherManager();
private File file;
public static Publisher getPublisherManager() {
return publisherManager;
}
public void openFile(String filePath) {
if (StringUtils.isEmpty(filePath)) {
throw new RuntimeException("filePath is empty");
}
this.file = new File(filePath);
ObserveContext observeContext = new ObserveContext()
.setObserveType("open file")
.setFile(file);
publisherManager.notifyObservers(observeContext);
}
public void saveFile() {
if (null == this.file) {
throw new RuntimeException("file is null");
}
ObserveContext observeContext = new ObserveContext()
.setObserveType("save file")
.setFile(file);
publisherManager.notifyObservers(observeContext);
}
}
public class Application {
public static void main(String[] args) {
Editor editor = new Editor();
Editor.getPublisherManager().registerObserver("open file", new EmailObserver("forrest@qq.com"));
Editor.getPublisherManager().registerObserver("save file", new LogObserver("/user/forrest/log/editor_log.txt"));
editor.openFile("/user/forrest/file/test.txt");
editor.saveFile();
}
}
/*
send email to forrest@qq.com, content: open file test.txt
save log to /user/forrest/log/editor_log.txt, content: save file test.txt
*/