基于切面实现观察者模式

观察者模式的基本定义和基础实现就不介绍了,可以参考这篇文章

http://www.jianshu.com/p/d55ee6e83d66

我们接着这个思路来。
在上文中,最后的实现部分,简单来说,是通过两个接口来完成的。

1 Observer
2 Subject

这里面包括几个过程:

1 定义一个Subject接口
2 定义一个Observer接口
3 实现一个Subject类
4 实现一个Observer类
5 向一个Suject类注册Observer
6 当Subject法触发时,通知每个Observer

除了前两步,体现了一定的抽象性,后面的所有部分,都是在将原本简单的东西复杂化。有人说,这就是面向对象,这就是接口编程。思路也许是对的,但这种实现方式,我并不认同。
所以今天,我们换一种思路,把这些复杂,冗余,糟糕的接口全部去掉,回归到观察者模式最简单的部分,回归到用户的角度。

如果你是一个用户,你想使用观察者模式,其实只需要搞清楚两点:

1 触发的事件(Subject)
2 后续的通知事件(Observer)

换句话说,我对你观察者本身的实现方式并不关心,我只想看到这样的形式:

class MySubject {
    @Subject
    void event() {
        .....
    }
}

class MyObserver {
    @Observer
    void update() {
        .....
    }
}

其他的所有,对我来说,都是多余的。

下面来说实现思路:

1 我们借助spring来管理我们的bean
2 在spring加载bean时,我们通过注解知道一个类是否应用了观察者模式
3 如果类使用了观察者模式,则将所有的Observer注册到一个中间结构中
4 当Subject的event事件触发时,我们通过aop的方式,调用中间结构中的Observer方法

接下来是具体实现:
首先定义两个注解。

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documentedpublic 
@interface Subject {    
    String value();
}

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Subject {
    String value();
}

这两个注解就是我们的Subject和Observer。

接下来是在spring中发现Observer注解,并将其注册到一个中间结构中,我们先看中间结构的定义:

public class Subscriber {
    @AllArgsConstructor
    @Data
    public static class MethodHolder {
        Method method;
        Object target;

        public void execute(Object param) {
            try {
                method.invoke(target, param);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    static Map<String, List<MethodHolder>> observerMethodMap = new ConcurrentHashMap<String, List<MethodHolder>>();

    public static void addMethod(String id, Method method, Object target) {
        if (null == observerMethodMap.get(id)) {
            observerMethodMap.put(id, new ArrayList<MethodHolder>());
        }

        observerMethodMap.get(id).add(new MethodHolder(method, target));
    }

    public static void notify(String id, Object param) {
        List<MethodHolder> methodHolders = observerMethodMap.get(id);

        if (null != methodHolders) {
            for (MethodHolder methodHolder : methodHolders) {
                methodHolder.execute(param);
            }
        }
    }
}

之后是在spring中发现注解的过程,我们通过实现BeanPostProcessor接口来实现,这里不展开BeanPostProcessor接口的作用(如果希望详细了解,请自行百度)。代码如下:

@Service
public class ObserverBeanProcessor implements BeanPostProcessor {
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        List<Method> methodList = ReflectTool.getObserverMethod(clazz.getDeclaredMethods(), Observer.class);

        for (Method method : methodList) {
            Observer observer = method.getAnnotation(Observer.class);
            String id = observer.value();

            Subscriber.addMethod(id, method, bean);
        }

        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

最后我们需要对Subject的事件做切面,代码如下:

@Service
@Aspect
public class SubjectAspect {
    @Pointcut("@annotation(com.littlersmall.observer.annotation.Subject)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object doAfter(final ProceedingJoinPoint proceedingJoinPoint) {
        Object res = null;
        String id = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getAnnotation(Subject.class).value();

        try {
            res = proceedingJoinPoint.proceed();
            Subscriber.notify(id, res);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return res;
    }
}

当这一切都准备好之后。
你就可以方便的使用观察者模式了。
请忘记那些恶心而复杂的接口吧,回归到最本质的调用,还是以上文的事例为例子,只是这一次,简洁清晰了很多:

1 WeatherData

@Data
public class WeatherDataModel {
    float temperature;
    float humidity;
    float pressure;

    List<Float> forecastTemperatures;
}

2 Weather主题定义(subject)

@Service
public class Weather {
    @Subject("weatherChanged")
    public WeatherDataModel measurementChanged(WeatherDataModel weatherDataModel) {
        System.out.println("weather changed: ");

        return weatherDataModel;
    }
}

3 显示当前天气的公告牌CurrentConditionsDisplay(observer1)

@Service
public class CurrentConditionsDisplay {
    @Observer("weatherChanged")
    public void currentConditions(WeatherDataModel weatherDataModel) {
        System.out.println("温度: " + weatherDataModel.getTemperature());
        System.out.println("湿度: " + weatherDataModel.getHumidity());
        System.out.println("气压: " + weatherDataModel.getPressure());
    }
}

4 显示未来几天天气的公告牌ForecastDisplay(observer2)

@Service
public class ForecastDisplay {
    @Observer("weatherChanged")
    public void futureConditions(WeatherDataModel weatherDataModel) {
        for (int i = 0; i < weatherDataModel.getForecastTemperatures().size(); i++) {
            System.out.println("day: " + i + " " + weatherDataModel.getForecastTemperatures().get(i) + "℃");
        }
    }
}

5 main函数

public class ObserverTest {
    public static void main(String[] args) {
        WeatherDataModel weatherDataModel = new WeatherDataModel();

        weatherDataModel.setTemperature(22f);
        weatherDataModel.setHumidity(0.8f);
        weatherDataModel.setPressure(1.2f);

        weatherDataModel.setForecastTemperatures(new ArrayList<Float>());
        weatherDataModel.getForecastTemperatures().add(22f);
        weatherDataModel.getForecastTemperatures().add(23f);
        weatherDataModel.getForecastTemperatures().add(27f);

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Weather weather = ac.getBean(Weather.class);

        weather.measurementChanged(weatherDataModel);
    }
}

github地址

https://github.com/littlersmall/observer-pattern

总结
设计模式的本质思想,是将不变的框架固化,而将变化的部分抽象出来。
在以前,我们只能通过一层层的接口嵌套,把变化的东西剥离,集中,再抽象。这种方式,往往会将原本的简单代码,过度设计。换句话说,我们牺牲了程序的简洁性,来换取逻辑的清晰性。

现在,有了aop这种利器,终于可以鱼和熊掌兼得了。

有机会,把java的设计模式,用aop一个一个实现一遍。
简洁的,才是美好的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容