基于切面實(shí)現(xiàn)觀察者模式

觀察者模式的基本定義和基礎(chǔ)實(shí)現(xiàn)就不介紹了波附,可以參考這篇文章

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

我們接著這個思路來贝或。
在上文中壤躲,最后的實(shí)現(xiàn)部分霞掺,簡單來說砸紊,是通過兩個接口來完成的传于。

1 Observer
2 Subject

這里面包括幾個過程:

1 定義一個Subject接口
2 定義一個Observer接口
3 實(shí)現(xiàn)一個Subject類
4 實(shí)現(xiàn)一個Observer類
5 向一個Suject類注冊O(shè)bserver
6 當(dāng)Subject法觸發(fā)時,通知每個Observer

除了前兩步醉顽,體現(xiàn)了一定的抽象性沼溜,后面的所有部分,都是在將原本簡單的東西復(fù)雜化游添。有人說系草,這就是面向?qū)ο螅@就是接口編程否淤。思路也許是對的悄但,但這種實(shí)現(xiàn)方式,我并不認(rèn)同石抡。
所以今天,我們換一種思路助泽,把這些復(fù)雜啰扛,冗余,糟糕的接口全部去掉嗡贺,回歸到觀察者模式最簡單的部分隐解,回歸到用戶的角度。

如果你是一個用戶诫睬,你想使用觀察者模式煞茫,其實(shí)只需要搞清楚兩點(diǎn):

1 觸發(fā)的事件(Subject)
2 后續(xù)的通知事件(Observer)

換句話說,我對你觀察者本身的實(shí)現(xiàn)方式并不關(guān)心,我只想看到這樣的形式:

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

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

其他的所有续徽,對我來說蚓曼,都是多余的。

下面來說實(shí)現(xiàn)思路:

1 我們借助spring來管理我們的bean
2 在spring加載bean時钦扭,我們通過注解知道一個類是否應(yīng)用了觀察者模式
3 如果類使用了觀察者模式纫版,則將所有的Observer注冊到一個中間結(jié)構(gòu)中
4 當(dāng)Subject的event事件觸發(fā)時,我們通過aop的方式客情,調(diào)用中間結(jié)構(gòu)中的Observer方法

接下來是具體實(shí)現(xiàn):
首先定義兩個注解其弊。

@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中發(fā)現(xiàn)Observer注解膀斋,并將其注冊到一個中間結(jié)構(gòu)中梭伐,我們先看中間結(jié)構(gòu)的定義:

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中發(fā)現(xiàn)注解的過程,我們通過實(shí)現(xiàn)BeanPostProcessor接口來實(shí)現(xiàn)仰担,這里不展開BeanPostProcessor接口的作用(如果希望詳細(xì)了解籽御,請自行百度)。代碼如下:

@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;
    }
}

當(dāng)這一切都準(zhǔn)備好之后技掏。
你就可以方便的使用觀察者模式了。
請忘記那些惡心而復(fù)雜的接口吧项鬼,回歸到最本質(zhì)的調(diào)用哑梳,還是以上文的事例為例子,只是這一次绘盟,簡潔清晰了很多:

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 顯示當(dāng)前天氣的公告牌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函數(shù)

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

總結(jié)
設(shè)計(jì)模式的本質(zhì)思想鸠真,是將不變的框架固化,而將變化的部分抽象出來龄毡。
在以前吠卷,我們只能通過一層層的接口嵌套,把變化的東西剝離沦零,集中祭隔,再抽象。這種方式路操,往往會將原本的簡單代碼疾渴,過度設(shè)計(jì)。換句話說屯仗,我們犧牲了程序的簡潔性搞坝,來換取邏輯的清晰性。

現(xiàn)在魁袜,有了aop這種利器桩撮,終于可以魚和熊掌兼得了敦第。

有機(jī)會,把java的設(shè)計(jì)模式店量,用aop一個一個實(shí)現(xiàn)一遍芜果。
簡潔的,才是美好的垫桂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末师幕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子诬滩,更是在濱河造成了極大的恐慌霹粥,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疼鸟,死亡現(xiàn)場離奇詭異后控,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)空镜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門浩淘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吴攒,你說我怎么就攤上這事张抄。” “怎么了洼怔?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵署惯,是天一觀的道長。 經(jīng)常有香客問我镣隶,道長极谊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任安岂,我火速辦了婚禮轻猖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘域那。我一直安慰自己咙边,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布琉雳。 她就那樣靜靜地躺著样眠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翠肘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天辫秧,我揣著相機(jī)與錄音束倍,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛绪妹,可吹牛的內(nèi)容都是我干的甥桂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼邮旷,長吁一口氣:“原來是場噩夢啊……” “哼黄选!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婶肩,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤办陷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后律歼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體民镜,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年险毁,在試婚紗的時候發(fā)現(xiàn)自己被綠了制圈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡畔况,死狀恐怖鲸鹦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跷跪,我是刑警寧澤馋嗜,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站域庇,受9級特大地震影響嵌戈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜听皿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一熟呛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧尉姨,春花似錦庵朝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至覆致,卻和暖如春侄旬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背煌妈。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工儡羔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宣羊,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓汰蜘,卻偏偏與公主長得像仇冯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子族操,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容