Spring 實(shí)現(xiàn)策略模式--自定義注解方式解耦if...else

策略模式

定義

定義一簇算法類识窿,將每個(gè)算法分別封裝起來换团,讓他們可以互相替換把还,策略模式可以使算法的變化獨(dú)立于使用它們的客戶端

場(chǎng)景

使用策略模式,可以避免冗長(zhǎng)的if-else 或 switch分支判斷

實(shí)現(xiàn)

  1. 策略的定義

    策略的定義需要定義一個(gè)策略接口和一組實(shí)現(xiàn)這個(gè)接口的策略類俏脊,因?yàn)樗械牟呗灶惗紝?shí)現(xiàn)相同的接口

public interface Strategy{
    void algorithm();
}

public class ConcreteStrategyA implements Strategy {
 @Override
 public void algorithm() {
 //具體的算法...
 }
}
public class ConcreteStrategyB implements Strategy {
 @Override
 public void algorithm() {
 //具體的算法...
 }
}
  1. 策略的創(chuàng)建

    在使用的時(shí)候全谤,一般會(huì)通過類型來判斷創(chuàng)建哪個(gè)策略來使用,在策略上下文中爷贫,可以使用map維護(hù)好策略類

  2. 策略的使用

    策略模式包含一組可選策略认然,在使用策略時(shí),一般如何確定使用哪個(gè)策略呢漫萄?最常見的是運(yùn)行時(shí)動(dòng)態(tài)確定使用哪種策略季眷。程序在運(yùn)行期間,根據(jù)配置卷胯、計(jì)算結(jié)果子刮、網(wǎng)絡(luò)等這些不確定因素,動(dòng)態(tài)決定使用哪種策略

public class StrategyContext{
    private static final Map<String, Strategy> strategies = new HashMap<>();
    
    static {
     strategies.put("A", new ConcreteStrategyA());
     strategies.put("B", new ConcreteStrategyB());
    }
    
    private static Strategy getStrategy(String type) {
         if (type == null || type.isEmpty()) {
             throw new IllegalArgumentException("type should not be empty.");
         }
         return strategies.get(type);
    }
    
    public void algorithm(String type){
        Strategy strategy = this.getStrategy(type);
        strategy.algorithm();
    }
}

UML

image.png

策略模式的創(chuàng)建和使用--Spring和自定義注解

在介紹策略模式時(shí)窑睁,在上下文中使用了map存儲(chǔ)好的策略實(shí)例挺峡,在根據(jù)type獲取具體的策略,調(diào)用策略算法担钮。
當(dāng)需要添加一種策略時(shí)橱赠,需要修改context代碼,這違反了開閉原則:對(duì)修改關(guān)閉箫津,對(duì)擴(kuò)展開放狭姨。

要實(shí)現(xiàn)對(duì)擴(kuò)展開放,就要對(duì)type和具體的策略實(shí)現(xiàn)類在代碼中進(jìn)行關(guān)聯(lián)苏遥,可以使用自定義注解的方式饼拍,在注解中指定策略的type。
策略上下文實(shí)現(xiàn)類實(shí)現(xiàn) BeanPostProcessor 接口田炭,在該接口中編寫策略類型與bean的關(guān)系并維護(hù)到策略上下文中师抄。

package com.masterlink.strategy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Component
public class StrategyDemoBeanPostProcessor implements BeanPostProcessor, Ordered {

    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

    private final StrategyContext strategyContext;

    private StrategyDemoBeanPostProcessor(StrategyContext context) {
        this.strategyContext = context;
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {

        if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
            // 獲取使用 @StrategyDemo 注解的Class信息
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            Class<Strategy> orderStrategyClass = (Class<Strategy>) targetClass;
            StrategyDemo ann = findAnnotation(targetClass);
            if (ann != null) {
                processListener(ann, orderStrategyClass);
            }
        }
        return bean;
    }

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

    protected void processListener(StrategyDemo annotation,
                                   Class<Strategy> classes) {
        // 注冊(cè)策略
        this.strategyContext
                .registerStrategy(annotation.type(), classes);
    }

    private StrategyDemo findAnnotation(Class<?> clazz) {

        StrategyDemo ann = AnnotatedElementUtils.findMergedAnnotation(clazz, StrategyDemo.class);
        return ann;
    }

}


@Component
public class StrategyContext implements ApplicationContextAware {
    private final Map<String, Class<Strategy>> strategyClassMap = new ConcurrentHashMap<>(64);

    private final Map<String, Strategy> beanMap = new ConcurrentHashMap<>(64);

    private ApplicationContext applicationContext;

    /**
     * 注冊(cè)策略
     * @param type
     * @param strategyClass
     */
    public void registerStrategy(String type, Class<Strategy> strategyClass){
        if (strategyClassMap.containsKey(type)){
            throw new RuntimeException("strategy type:"+type+" exist");
        }
        strategyClassMap.put(type, strategyClass);
    }

    /**
     * 執(zhí)行策略
     * @param type
     */
    public void algorithm(String type){
        Strategy strategy = this.getStrategy(type);
        strategy.algorithm();
    }

    private Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
        }
        Class<Strategy> strategyClass = strategyClassMap.get(type);
        return createOrGetStrategy(type, strategyClass);
    }

    private Strategy createOrGetStrategy(String type,Class<Strategy> strategyClass ){
        if (beanMap.containsKey(type)){
            return beanMap.get(type);
        }
        Strategy strategy = this.applicationContext.getBean(strategyClass);
        beanMap.put(type, strategy);
        return strategy;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

實(shí)用案例

在我們的平臺(tái)中,有一部分是使用的netty框架編寫的tcp服務(wù)教硫,在服務(wù)端叨吮,需要將二進(jìn)制轉(zhuǎn)換為對(duì)象,在協(xié)議設(shè)計(jì)階段瞬矩,定義第一個(gè)字節(jié)表示對(duì)象類型茶鉴,比如int,String等,第二三個(gè)字節(jié)景用,表示數(shù)據(jù)長(zhǎng)度涵叮,后面的字節(jié)位傳輸內(nèi)容。
比如,
0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09围肥,解析出來的內(nèi)容是int類型數(shù)字9剿干。
0x02, 0x00, 0x03, 0x31, 0x32, 0x33, 解析出的內(nèi)容是String類型蜂怎,內(nèi)容是 123穆刻。
在不使用策略模式的時(shí)候,需要將第一個(gè)字節(jié)解析出來杠步,然會(huì)使用if--else判斷類型氢伟,對(duì)后繼的字節(jié)進(jìn)行解析。
在實(shí)際的實(shí)現(xiàn)過程中幽歼,是使用了策略模式朵锣,并且使用注解的方式表示數(shù)據(jù)類型,實(shí)現(xiàn)過程如下甸私。

定義策略接口和注解

定義 CodecStrategyType 注解和編碼解碼器的策略接口 CodecStrategy

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CodecStrategyType {
    /**
     * 編碼解碼類型
     * @return
     */
    byte type();
}

public interface CodecStrategy<T> {
    T decoding(byte[] buffer);
}

/*
* 通用解碼接口
 */
public interface Codec {
    Object decoding(byte[] bytes);
}


策略實(shí)現(xiàn)

實(shí)現(xiàn)兩種類型的解碼器: IntegerString

/**
 * integer解碼
 */
@CodecStrategyType(type = (byte)0x01)
@Service
public class IntgerCodecStrategy implements CodecStrategy<Integer> {
    
    @Override
    public Integer decoding(byte[] buffer) {
        int value;
        value = (int) ((buffer[3] & 0xFF)
                | ((buffer[2] & 0xFF)<<8)
                | ((buffer[1] & 0xFF)<<16)
                | ((buffer[0] & 0xFF)<<24));
        return value;
    }
}

@CodecStrategyType(type = (byte)0x02)
@Service
public class StringCodecStrategy implements CodecStrategy<String> {

    @Override
    public String decoding(byte[] bufferr) {
        return new String(bufferr);
    }
}

策略上下文和策略注冊(cè)

策略上下文類 CodecStrategyContext 提供了統(tǒng)一解碼入口诚些,將 byte[] 轉(zhuǎn)換為 Object 類型,同時(shí)提供策略的注解接口 void registerStrategy(Byte type, Class<CodecStrategy<?>> strategyClass) 皇型,注冊(cè)解碼類型對(duì)應(yīng)的策略實(shí)現(xiàn)類诬烹。
策略上下文類同時(shí)還提供了策略Bean的創(chuàng)建,根據(jù)類型從Spring 的 ApplicationContext 獲取策略bean弃鸦,并緩存到map绞吁。
策略Bean處理類 CodecStrategyTypeBeanPostProcessor 中解析 CodecStrategyType 注解中指定的類型。


@Component
public class CodecStrategyContext implements ApplicationContextAware, Codec {
    private final Map<Byte, Class<CodecStrategy<?>>> strategyClassMap = new ConcurrentHashMap<>(64);

    private final Map<Byte, CodecStrategy<?>> beanMap = new ConcurrentHashMap<>(64);

    private ApplicationContext applicationContext;

    /**
     * 注冊(cè)策略
     * @param type
     * @param strategyClass
     */
    public void registerStrategy(Byte type, Class<CodecStrategy<?>> strategyClass){
        if (strategyClassMap.containsKey(type)){
            throw new RuntimeException("strategy type:"+type+" exist");
        }
        strategyClassMap.put(type, strategyClass);
    }

    /**
     * 執(zhí)行策略
     */
    @Override
    public Object decoding(byte[] bytes){
        Byte type = bytes[0];
        CodecStrategy<?> strategy =this.getStrategy(type);
        byte l1 = bytes[1];
        byte l2= bytes[2];
        short length =  (short) ((l2 & 0xFF)
                | ((l1 & 0xFF)<<8));
        byte[] contentBytes = new byte[length];
        arraycopy(bytes,3,contentBytes,0, length);
        return strategy.decoding(contentBytes);
    }

    private CodecStrategy<?> getStrategy(Byte type) {
        Class<CodecStrategy<?>> strategyClass = strategyClassMap.get(type);
        return createOrGetStrategy(type, strategyClass);
    }

    private CodecStrategy<?> createOrGetStrategy(Byte type, Class<CodecStrategy<?>> strategyClass ){
        if (beanMap.containsKey(type)){
            return beanMap.get(type);
        }
        CodecStrategy<?> strategy = this.applicationContext.getBean(strategyClass);
        beanMap.put(type, strategy);
        return strategy;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

@Component
public class CodecStrategyTypeBeanPostProcessor implements BeanPostProcessor, Ordered {

    private final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));

    private final CodecStrategyContext strategyContext;

    private CodecStrategyTypeBeanPostProcessor(CodecStrategyContext context) {
        this.strategyContext = context;
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {

        if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
            // 獲取使用 @StrategyDemo 注解的Class信息
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            Class<CodecStrategy<?>> orderStrategyClass = (Class<CodecStrategy<?>>) targetClass;
            CodecStrategyType ann = findAnnotation(targetClass);
            if (ann != null) {
                processListener(ann, orderStrategyClass);
            }
        }
        return bean;
    }

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

    protected void processListener(CodecStrategyType annotation,
                                   Class<CodecStrategy<?>> classes) {
        // 注冊(cè)策略
        this.strategyContext
                .registerStrategy(annotation.type(), classes);
    }

    private CodecStrategyType findAnnotation(Class<?> clazz) {

        CodecStrategyType ann = AnnotatedElementUtils.findMergedAnnotation(clazz, CodecStrategyType.class);
        return ann;
    }

}

使用和測(cè)試

測(cè)試Integer和String類型的策略:

  1. 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09唬格,解析出來的內(nèi)容是int類型數(shù)字9家破。
  2. 0x02, 0x00, 0x03, 0x31, 0x32, 0x33, 解析出的內(nèi)容是String類型,內(nèi)容是 123购岗。

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {CodecStrategyTest.CodecStrategyTestConfig.class})
public class CodecStrategyTest {

    @Resource
    Codec codec;

    @Test
    public void testInterDecoding(){
        byte[] buffer = new byte[]{
                0x01,0x00,  0x04, 0x00, 0x00,0x00, 0x09
        };
        Integer decoding = (Integer)codec.decoding(buffer);
        assertThat(decoding)
                .isNotNull()
                .isEqualTo(9);
    }

    @Test
    public void testStringDecoding(){
        byte[] buffer = new byte[]{
                0x02, 0x00, 0x03, 0x31, 0x32,0x33
        };
        String decoding = (String)codec.decoding(buffer);
        assertThat(decoding)
                .isNotNull()
                .isEqualTo("123");
    }

    @ComponentScan({"com.masterlink.strategy"})
    @Configuration
    public static class CodecStrategyTestConfig {
    }
}

擴(kuò)展復(fù)雜類型

自定義復(fù)雜類型User類汰聋,對(duì)應(yīng)協(xié)議類型為 0xA0, 第2 喊积、3 字節(jié)表示整個(gè)對(duì)象的字段長(zhǎng)度马僻,緊接著是 Integer 類型的age 和 String 類型的name,
比如 0xA0, 0x00 0x10 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x00, 0x08, 0x5A,0x68,0x61,0x6E,0x67,0x53, 0x61,0x6E, 對(duì)應(yīng)的user對(duì)象是

{
  "age": 23,
  "name": "ZhangSan"
}
@Data
public class User {
    private Integer age;
    private String name;
}

實(shí)現(xiàn)解碼策略類

已知 User 中的基礎(chǔ)類型依賴了 IntegerString 注服,所以在User的解碼策略類中韭邓,依賴了 IntgerCodecStrategyStringCodecStrategy


@CodecStrategyType(type = (byte) (0xA0))
@Service
public class UserCodeStrategy implements CodecStrategy<User> {
    private final StringCodecStrategy stringCodecStrategy;
    private final IntgerCodecStrategy intgerCodecStrategy;

    public UserCodeStrategy(StringCodecStrategy stringCodecStrategy, IntgerCodecStrategy intgerCodecStrategy) {
        this.stringCodecStrategy = stringCodecStrategy;
        this.intgerCodecStrategy = intgerCodecStrategy;
    }

    @Override
    public User decoding(byte[] buffer) {
        byte ageL1 = buffer[0];
        byte ageL2 = buffer[1];
        short ageLength =  (short) ((ageL2 & 0xFF)
                | ((ageL1 & 0xFF)<<8));
        byte[] ageBytes = new byte[ageLength];
        System.arraycopy(buffer,2, ageBytes,0,ageLength);

        byte nameL1 = buffer[0+ageLength];
        byte nameL2 = buffer[1+ageLength];

        short nameLength =  (short) ((nameL2 & 0xFF)
                | ((nameL1 & 0xFF)<<8));

        byte[] nameBytes = new byte[nameLength];
        System.arraycopy(buffer,2+ageLength+2, nameBytes,0,nameLength);

        User user = new User();
        user.setAge(intgerCodecStrategy.decoding(ageBytes));
        user.setName(stringCodecStrategy.decoding(nameBytes));
        return user;
    }
}

測(cè)試

通過測(cè)試可以發(fā)現(xiàn)很輕松的就擴(kuò)展了一個(gè)復(fù)雜類型的解碼算法,這樣隨著協(xié)議的增加溶弟,可以做到對(duì)修改代碼關(guān)閉女淑,對(duì)擴(kuò)展代碼開放,符合開閉原則辜御。


    @Test
    public void testUserDecoding(){
        byte[] buffer = new byte[]{
                (byte)0xA0, (byte)0x00 ,(byte)0x10 ,(byte)0x00, (byte)0x04,
                (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x17, (byte)0x00,
                (byte)0x08, (byte)0x5A, (byte)0x68, (byte)0x61, (byte)0x6E,
                (byte)0x67, (byte)0x53, (byte)0x61, (byte)0x6E
        };
        User user = (User)codec.decoding(buffer);
        assertThat(user)
                .isNotNull();
        assertThat(user.getAge()).isEqualTo(23);
        assertThat(user.getName()).isEqualTo("ZhangSan");
    }

總結(jié)

  1. 使用策略模式鸭你,可以避免冗長(zhǎng)的if-else 或 switch分支判斷
  2. 掌握自定義注解的是使用方式
  3. 與使用 @Service("name") 注解相比,自定義注解方式支撐和擴(kuò)展的類型或更靈活
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市袱巨,隨后出現(xiàn)的幾起案子阁谆,更是在濱河造成了極大的恐慌,老刑警劉巖愉老,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件场绿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嫉入,警方通過查閱死者的電腦和手機(jī)焰盗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咒林,“玉大人熬拒,你說我怎么就攤上這事〉婢海” “怎么了澎粟?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)欢瞪。 經(jīng)常有香客問我活烙,道長(zhǎng),這世上最難降的妖魔是什么引有? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任瓣颅,我火速辦了婚禮,結(jié)果婚禮上譬正,老公的妹妹穿的比我還像新娘宫补。我一直安慰自己,他們只是感情好曾我,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布粉怕。 她就那樣靜靜地躺著,像睡著了一般抒巢。 火紅的嫁衣襯著肌膚如雪贫贝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天蛉谜,我揣著相機(jī)與錄音稚晚,去河邊找鬼。 笑死型诚,一個(gè)胖子當(dāng)著我的面吹牛客燕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狰贯,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼也搓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赏廓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起傍妒,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤幔摸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后颤练,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體既忆,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年昔案,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尿贫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片电媳。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡踏揣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匾乓,到底是詐尸還是另有隱情捞稿,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布拼缝,位于F島的核電站娱局,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咧七。R本人自食惡果不足惜衰齐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望继阻。 院中可真熱鬧耻涛,春花似錦、人聲如沸瘟檩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)墨辛。三九已至卓研,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間睹簇,已是汗流浹背奏赘。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留太惠,地道東北人磨淌。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像垛叨,于是被迫代替她去往敵國(guó)和親伦糯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柜某,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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