In the Strategy Pattern, the behavior of a class or its algorithm can be changed at run time. This type of design pattern is behavioral.

In the policy pattern, we create objects that represent various policies and a context object whose behavior changes as the policy object changes. The policy object changes the execution algorithm of the context object.

In this way, only one class needs to be added/removed during subsequent channel adjustments without affecting the overall logic.

Transform optimization

In this category, shopping channels are divided into pinduoduo, Taoshi, Jingdong and Dangdang. The previous approach was to call different code if/else depending on the channel.

There is also the case that these channels consume the same MQ, which was originally written to create four consumers for four classes to consume data, and then filter and invoke the corresponding implementation classes based on one of these channels.

This method is not only very classy, but also takes up MQ channels and can be cumbersome to maintain later.

In this kind of business scenario, where you can have one entry and then route to different classes based on different names, this is a good fit for the policy pattern.

The problem with new classes in the policy

In many articles, the Context class uses different names to new different methods in the specific binding relationship. Examples are as follows:

The problem with this is that when using classes such as the CacheClusterService below handed over to Spring to manage, new will not work properly and will throw null pointer errors.

 switch (strategyType) {     case "add":         strategy = new ConcreteStrategyAdd();         break;     case "sub":         strategy = new ConcreteStrategySub();         break;     case "mul":         strategy = new ConcreteStrategyMul();         break; }
Copy the code

Middleware related classes

So let’s simulate a spring-managed class to get the value and see if it works.

The cache

The simulation uses a cache to get values.

@component @slf4j public class CacheClusterService {public String getValue(String Key){return key; }}Copy the code

The strategy pattern

The policy pattern is still an interface, which is then implemented for the purpose of abstraction.

 public interface IStrategyHard1 {     enum StrategyType{         PDD,TX,JD     }     String buy();     String getType(); }
Copy the code

The following methods implement this interface:

Jingdong channels

 @Service public class JdBuyChannel implements IStrategyHard1{     @Resource     private CacheClusterService cacheClusterService; ​     @Override     public String buy() {         return cacheClusterService.getValue("jd");     } ​     @Override     public String getType() {         return StrategyType.JD.name();     } }
Copy the code

Pinduoduo Channel

@Service public class PddBuyChannel implements IStrategyHard1{ @Resource private CacheClusterService cacheClusterService; @Override public String buy() { return cacheClusterService.getValue("pdd"); } @Override public String getType() { return StrategyType.PDD.name(); }}Copy the code

Tao is a channel

 @Service public class TxBuyChannel implements IStrategyHard1{     @Resource     private CacheClusterService cacheClusterService; ​     @Override     public String buy() {         return cacheClusterService.getValue("tx");     } ​     @Override     public String getType() {         return IStrategyHard1.StrategyType.TX.name();     } }
Copy the code

To create the Context class

At present, there are several ways to implement the method, mainly in combination with the factory pattern.

Implementation method 1: Implement the ApplicationContextAware interface

With ApplicationContextAware, when a class implements the interface (ApplicationContextAware), it makes it easy to get all the beans in the ApplicationContext. In other words, this class directly fetches all referenced bean objects in the Spring configuration file.

Override the setApplicationContext() method in the method, use applicationContext to get the various implementation classes of the interface, and then bind the class name alias to the class.

@Slf4j @Component public class OnlineBuyChannelContext implements ApplicationContextAware { private Map<String,IStrategyHard1> BUY_CHANNEL_MAP = new ConcurrentHashMap<>(); private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; Map<String,IStrategyHard1> beanTypes = applicationContext.getBeansOfType(IStrategyHard1.class); log.info("beanTypes:{}",beanTypes); beanTypes.entrySet().forEach(entry->{ BUY_CHANNEL_MAP.put(entry.getValue().getType(),entry.getValue()); }); } public IStrategyHard1 getBuyChannel(String type){ if (StringUtils.isBlank(type)){ throw new RuntimeException("type is null"); } return BUY_CHANNEL_MAP.get(type); }}Copy the code

Implementation 2: Each implementation class is initialized using @postConstruct

Use @postConstruct to implement Spring initialization by binding the class name alias to the class.

@Slf4j @Component public class OnlineBuyChannelContext2 { private Map<String, IStrategyHard2> BUY_CHANNEL_MAP2 = new ConcurrentHashMap<>(); public void register(String type,IStrategyHard2 iStrategyHard2Impl){ if (StringUtils.isBlank(type)){ throw new RuntimeException("type is null"); } BUY_CHANNEL_MAP2.put(type,iStrategyHard2Impl); } public IStrategyHard2 getBuyChannel(String type){ if (StringUtils.isBlank(type)){ throw new RuntimeException("type is null"); } return BUY_CHANNEL_MAP2.get(type); }}Copy the code

Use @postConstruct in each method instead of the String getType() method of the IStrategyHard1 interface.

For example:

@Service public class JdBuyChannel2 implements IStrategyHard2 { @Resource private CacheClusterService2 cacheClusterService2; @Resource private OnlineBuyChannelContext2 onlineBuyChannelContext2; @Override public String buy() { return cacheClusterService2.getValue("jd"); } @PostConstruct public void init(){ onlineBuyChannelContext2.register(IStrategyHard2.StrategyType.JD.name(),this); }}Copy the code

Implementation method 3: implement the InitializingBean interface

You implement the InitializingBean interface and then override the afterPropertiesSet() method.

Note that this method calls the Register method in the afterPropertiesSet method after the Spring container starts the initialization bean.

@Service public class JdBuyChannel3 implements IStrategyHard3, InitializingBean { @Resource private CacheClusterService3 cacheClusterService3; @Resource private OnlineBuyChannelContext3 onlineBuyChannelContext3; @Override public String buy() { return cacheClusterService3.getValue("jd"); } @Override public void afterPropertiesSet() throws Exception { onlineBuyChannelContext3.register(StrategyType.JD.name(),this); }}Copy the code

The test class

After testing, all of these methods can be implemented.

@RestController @Slf4j public class TestController { @Autowired private OnlineBuyChannelContext onlineBuyChannelContext;  @Autowired private OnlineBuyChannelContext2 onlineBuyChannelContext2; /** * Policy mode 1: @return String */ @getMapping ("/test/getBuy") public String getBuy(){IStrategyHard1 iStrategyHard1 = onlineBuyChannelContext.getBuyChannel(IStrategyHard1.StrategyType.DD.name()); String buyChannel = iStrategyHard1.buy(); log.info("buyChannel:{}",buyChannel); return buyChannel; } /** * Policy mode 2: @return String */ @getMapping ("/test/getBuy2") Public String getBuy2(){IStrategyHard2 iStrategyHard2 = onlineBuyChannelContext2.getBuyChannel(IStrategyHard2.StrategyType.DD.name()); String buyChannel = iStrategyHard2.buy(); log.info("buyChannel:{}",buyChannel); return buyChannel; }}Copy the code

analyse

  • As you can see from the above approach, the implementation of the factory pattern is combined with Spring, that is, the binding relationship is implemented at initialization;

  • The policy mode can realize different routes and has little impact on subsequent service adjustment.