目前有什么问题?
在银行系统的批量开发中,我们通常会实现多个后台任务(Task)。这类任务的典型执行流程是:由shell脚本调用Java程序,传入任务ID作为参数;Java主进程根据任务ID启动新的线程(或提交给线程池),执行对应的任务逻辑。
采用Task的主要场景是:当业务逻辑复杂度较高时,使用纯shell脚本开发维护困难,因此借助Java来实现核心处理。
实现Task时常见的需求包括:
- 任务启动/完成时记录日志和数据库状态
- 异常发生时发送邮件告警
- 根据项目需求扩展通知机制,如短信提醒或消息队列通知下游系统
如何更优雅地处理上述需求,就是我们本次要讨论的问题
如何优化?
好,这个需求你想到了什么设计模式?
3,2,1
那必须是观察者模式啊!没有想到的V我50请我吃疯狂星期四
什么是观察者模式?
说是观察者,其实我认为叫监视者更合适!观察者类似于一个特务机构
讲一个故事,张作霖和日本特务机构,当年的张大帅被日本人拿炸弹炸死,这张大帅身边一定有日本的特务,这个特务就是每天观察大帅的一举一动,即时汇报给日本驻扎东北的特务头子,然后特务头子,就跟据张大帅的一举一动,做出不同的安排。
比如:张大帅发展大炮,安插在张大帅身边的小特务知道后,就去汇报给特务头子,特务头子就做出反应,不给张作霖钢铁,日本不卖给他钢铁了!
上个例子中,张大帅就是被观察者(主题),小特务属于是眼线,在观察者模式中也会有眼线的存在,大特务属于观察者,观察者观察到现象之后会做出一些动作。
手写观察者模式
先给个uml类图给各位看官看看~
首先先来个被观察的接口,我们这里叫做主题
interface Subject {
// 注册观察者(安插眼线)
void registerObserver(Observer observer);
// 移除观察者(移除眼线眼线)
void removeObserver(Observer observer);
//通知观察者
void notifyObservers();
}
观察者接口
interface Observer {
// 观察者观察到现象后执行一些逻辑,
// 可以理解为特务头子收到信息后进行的活动,比如例子中的不出售给张大帅钢铁
void update(String message);
}
观察者与主题的实现
主题
class ConcreteSubject implements Subject {
// 观察者列表
private List<Observer> observers = new ArrayList<>();
private String state;
@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(state);
}
}
// 业务方法 - 状态改变时通知观察者
public void setState(String newState) {
this.state = newState;
System.out.println("Subject state changed to: " + newState);
notifyObservers();
}
}
// 具体观察者A
class ConcreteObserverA implements Observer {
@Override
public void update(String message) {
// 这里可以添加具体的响应逻辑,如更新数据库表的状态
System.out.println("Observer A 我要炸铁路-->" + message);
}
}
// 具体观察者B
class ConcreteObserverB implements Observer {
@Override
public void update(String message) {
// 这里可以添加具体的响应逻辑 如发送短信
System.out.println("Observer B 我要限制钢铁出口-->" + message);
}
}
接下来是一个使用实例demo
// 使用示例
public class ObserverPatternDemo {
public static void main(String[] args) {
// 创建主题
ConcreteSubject subject = new ConcreteSubject();
// 创建观察者
Observer observerA = new ConcreteObserverA();
Observer observerB = new ConcreteObserverB();
// 注册观察者
subject.registerObserver(observerA);
subject.registerObserver(observerB);
// 改变主题状态(自动通知所有观察者)
subject.setState("张大帅发展大炮");
System.out.println("=============");
// 移除一个观察者
subject.removeObserver(observerA);
// 再次改变状态
subject.setState("张大帅发展铁路运输");
}
}
如何将观察者与线程完美融合?
我们先想一下线程是如何使用的?这里只讨论继承Thread类实现多线程的方式
学过Java的都知道哈,继承Thread类后只需要重写run 方法,然后点击start,就可以使用一个新的线程去完成run方法中的任务。
那么我们可不可以这样呢?
把在run方法执行前,执行后都加上主题中的notifyObservers方法,通知观察者,线程正在准备执行run方法、run方法执行有异常、run方法执行结束。
说干就干,请看代码
主题接口
interface Subject<T> {
enum Cycle {
STARTED, RUNNING, DONE, ERROR
}
// 定义启动线程的方法,主要作用是为了屏蔽Thread的其他方法
void start();
// 定义线程的打断方法,作用与start方法一样,也是为了屏蔽Thread的其他方法
void interrupt();
// 获取当前任务的生命周期状态
Cycle getCycle();
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Cycle cycle, T result, Exception e);
}
// 观察者接口
interface Observer<T> {
void onStart(Thread thread);
// 任务正在运行时会触发onRunning方法
void onRunning(Thread thread);
// 任务运行结束时会触发onFinish方法,其中result是任务执行结束后的结果
void onFinish(Thread thread, T result);
// 任务执行报错时会触发onError方法
void onError(Thread thread, Exception e);
}
主题接口实现
class ThreadSubject<T> extends Thread implements Subject {
private List<Observer> observers = new ArrayList<>();
private Cycle cycle;
private final Task<T> task;
public ThreadSubject(Observer<T> observer, Task<T> task) {
super();
// Task不允许为null
if (task == null)
throw new IllegalArgumentException("The task is required.");
this.registerObserver(observer);
this.task = task;
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public final void run() {
// 在执行线程逻辑单元的时候,分别触发相应的事件
this.notifyObservers(Cycle.STARTED, null, null);
try {
this.notifyObservers(Cycle.RUNNING, null, null);
T result = this.task.call();
this.notifyObservers(Cycle.DONE, result, null);
} catch (Exception e) {
this.notifyObservers(Cycle.ERROR, null, e);
}
}
@Override
public void notifyObservers(Cycle cycle, Object result, Exception e) {
this.cycle = cycle;
if (observers == null || observers.isEmpty())
return;
try {
switch (cycle) {
case STARTED:
// 使用lambda循环通知观察者
observers.forEach(observer -> observer.onStart(currentThread()));
break;
case RUNNING:
observers.forEach(observer -> observer.onRunning(currentThread()));
break;
case DONE:
observers.forEach(observer -> observer.onFinish(currentThread(), result));
break;
case ERROR:
observers.forEach(observer -> observer.onError(currentThread(), e));
break;
}
} catch (Exception ex) {
if (cycle == Cycle.ERROR) {
throw ex;
}
}
}
@Override
public Cycle getCycle() {
return this.cycle;
}
}
观察者A
// 具体观察者A
class ConcreteObserverA implements Observer {
@Override
public void onStart(Thread thread) {
System.out.println("观察者A:开始执行");
}
@Override
public void onRunning(Thread thread) {
System.out.println("观察者A:正在执行");
}
@Override
public void onFinish(Thread thread, Object result) {
System.out.println("观察者A:执行结束");
}
@Override
public void onError(Thread thread, Exception e) {
System.out.println("观察者A:执行异常");
}
}
FunctionalInterface的task接口
@FunctionalInterface
public interface Task<T>
{
//任务执行接口,该接口允许有返回值
T call() throws InterruptedException;
}
测试demo
public class ObserverPatternDemo {
public static void main(String[] args) throws InterruptedException {
Task<String> task = () -> {
System.out.println("任务执行中...");
for (int i = 1; i <= 5; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("执行" + (i / 5.0) * 100 + "%。。。。。。。");
}
System.out.println("任务执行结束。。。。。。。。");
return "Success";
};
ConcreteObserverA concreteObserverA = new ConcreteObserverA();
// 创建主题
ThreadSubject subject = new ThreadSubject(concreteObserverA,task);
Observer observerB = new ConcreteObserverB();
subject.registerObserver(observerB);
subject.start();
// 等待上述线程执行结束
subject.join();
System.out.println("======================");
// 移除一个观察者
subject.removeObserver(concreteObserverA);
ThreadSubject newSubject = new ThreadSubject(concreteObserverA,task);
newSubject.start();
}
}
执行截图
好的我们来总结一下改动点
- 使用了函数式接口来定义任务,任务的执行作为主题线程的run方法的主要逻辑
- 在任务执行的不同阶段(启动、运行中、完成、出错)主动调用 notifyObservers(…) 方法通知所有观察者;
- 使用了枚举类型来表示的任务的不同状态,不同的状态会触发观察的相对应的方法
- ThreadSubject重写父类的run方法,其修饰为final,不允许子类再次对其进行重写
还有什么问题?
还有什么问题呢?
- 观察者的列表呢不是一个安全的,我们需要更改一下CopyOnWriteArrayList 就很合适,或者自己加上synchronized 关键字
- 观察者接口的各个方法 如onFinish,onRunning… 这写方法在实现的时候一定要注意异常处理和超时控制,不然会导致notifyObservers 方法跟着一起失败,一起出现异常,如果其中一个观察者出现异常,那么后续的观察者将收不到消息。