策略模式
定義
定義一簇算法類识窿,將每個(gè)算法分別封裝起來换团,讓他們可以互相替換把还,策略模式可以使算法的變化獨(dú)立于使用它們的客戶端
場(chǎng)景
使用策略模式,可以避免冗長(zhǎng)的if-else 或 switch分支判斷
實(shí)現(xiàn)
-
策略的定義
策略的定義需要定義一個(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() {
//具體的算法...
}
}
-
策略的創(chuàng)建
在使用的時(shí)候全谤,一般會(huì)通過類型來判斷創(chuàng)建哪個(gè)策略來使用,在策略上下文中爷贫,可以使用map維護(hù)好策略類
-
策略的使用
策略模式包含一組可選策略认然,在使用策略時(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
策略模式的創(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)兩種類型的解碼器: Integer
和 String
/**
* 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類型的策略:
- 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09唬格,解析出來的內(nèi)容是int類型數(shù)字9家破。
- 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ǔ)類型依賴了 Integer
和 String
注服,所以在User的解碼策略類中韭邓,依賴了 IntgerCodecStrategy
和 StringCodecStrategy
@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é)
- 使用策略模式鸭你,可以避免冗長(zhǎng)的if-else 或 switch分支判斷
- 掌握自定義注解的是使用方式
- 與使用
@Service("name")
注解相比,自定義注解方式支撐和擴(kuò)展的類型或更靈活