Strategy pattern structure diagram
The policy mode is mainly composed of the above three identities. Here we will not introduce the basic knowledge of the policy mode. By default, everyone has a basic understanding of the policy mode.
Business requirements
There is a demand for burying point reporting of advertising click data. The reported burying point data is reported according to different clicked advertising positions, and the data of each advertising position is stored in separate tables. (eg: we don't need to delve into why sub table storage should be done here. We'll just talk about the practical application of the policy mode)
code implementation
As it is a practical case, we are based on the SpringBoot framework and mainly use some Spring functions, so we should pay attention to it.
Step 1: define policy class
First, we define an interface for reporting
public interface AdvertisingDataReported { String advertisingDataReported(Object param); }
Step 2: define specific policy implementation classes
@Service public class BottomAdvertisingDataReported implements AdvertisingDataReported { @Override public String advertisingDataReported(Object param) { // The specific business logic is omitted return null; } }
Step 3: policy control class
Since the policy model has many specific policy implementations, which strategy to use needs to be judged according to our input, that is, the type of advertising in our business. How can we judge gracefully?
Let's look at this way first
public static void main(String[] args) { String advertisingType = "1"; if (advertisingType.equals("1")) { // Execute strategy A } else if (advertisingType.equals("2")) { // Execution strategy 2 } }
There are many people who write this, and we don't discuss these issues here. Let's take a look at the problems with this writing first?
Existing problems:
1. In violation of the opening and closing principle, each time a new policy implementation class is added, one must be added if Judgment; 2. With the increase of policy implementation classes, the code becomes bloated and more and more difficult to maintain;
Based on this situation, can we initialize all policy implementation classes and store them in the Map when the project is started, with the advertisement type as the key and the implementation class as the Value? Let's see the following code:
@Component public class StrategyFactory implements ApplicationContextAware { private final Map<String, AdvertisingDataReported> STRATEGY_MAP = new ConcurrentHashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // Returns all implementation classes of the interface Map<String, AdvertisingDataReported> tempMap = applicationContext.getBeansOfType(AdvertisingDataReported.class); tempMap.values().forEach(strategyService -> STRATEGY_MAP.put(strategyService.getClass().getName(), strategyService)); } public <T extends AdvertisingDataReported> AdvertisingDataReported getInstance(Class<T> clazz) { return STRATEGY_MAP.get(clazz.getName()); } }
Our policy control class implements ApplicationContextAware. As you can understand this class, it can obtain the context of ApplicationContext. As we talked about the case of SpringBoot, our policy class implementation classes are annotated with @ Service and injected into the Spring container, so we can directly get all the implementation classes of the policy class from the container.
After obtaining all the policy implementation classes, we took the class path as the key and stored the class implementation as the value in the map. At that time, I thought it was done.
What problems do you think still exist?
How do we know which specific policy class this parameter needs to go? You also need to define a separate class to map the advertisement type and policy class. Isn't that the same logic as judgment? You have to maintain this mapping relationship all the time.
reform
If we don't want to define a class separately to map the advertisement type and policy class one by one, can we solve it in the policy class? Each policy class implementation class knows which type it wants to deal with. In this way, we can replace the value of the Key class path in the map with the advertisement type, so that we can input the advertisement type according to the submission interface, get directly from the map.
There are two specific implementations. You can customize annotations and distinguish them by adding annotations, or you can use methods. We can directly use methods here.
Modified code:
Policy class:
public interface AdvertisingDataReported { // New method AdvertisingTypeEnum advertisingType(); String advertisingDataReported(Object param); }
Policy implementation class:
@Service public class BottomAdvertisingDataReported implements AdvertisingDataReported { @Override public AdvertisingTypeEnum advertisingType() { return AdvertisingTypeEnum.BOTTOM; } @Override public String advertisingDataReported(Object param) { return null; } }
Policy control class:
@Component public class StrategyFactory implements ApplicationContextAware { // The Key of Map is changed to the advertisement type enumeration class private final Map<AdvertisingTypeEnum, AdvertisingDataReported> STRATEGY_MAP = new ConcurrentHashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, AdvertisingDataReported> tempMap = applicationContext.getBeansOfType(AdvertisingDataReported.class); tempMap.values().forEach(strategyService -> STRATEGY_MAP.put(strategyService.advertisingType(), strategyService)); } // Obtain the corresponding policy class according to the advertisement type public <T extends AdvertisingDataReported> AdvertisingDataReported getInstance(AdvertisingTypeEnum advertisingTypeEnum) { return STRATEGY_MAP.get(advertisingTypeEnum); } }
Ad enumeration class:
public enum AdvertisingTypeEnum { BOTTOM, TOP; private String advertisingType; AdvertisingTypeEnum() {} // set get omitted }
Specific use of policy classes
@RestController public class AdvertisingDataReportedController { @Resource private StrategyFactory strategyFactory; @RequestMapping(value = "/reported/data", method = RequestMethod.POST) public String reportedData(AdvertisingTypeEnum advertisingTypeEnum, Object obj) { AdvertisingDataReported dataReported = strategyFactory.getInstance(advertisingTypeEnum); String result = dataReported.advertisingDataReported(obj); return "SUCCESS"; } }
Little summary:
Here, even if the case of our policy mode is over, there are several questions. Do you have any doubts? Why do I use Object as the method input parameter? In our case, it seems that the input parameters of each policy class are the same, but it is also possible to show the implementation class of the same policy, but the input parameters may be different. At this time, We can convert inside the method by passing in Object. Of course, if you think the policy method is too rigid, you can also add generics to the method. For specific types, we can convert by passing in generics by the caller.
After such a transformation, the two problems we just encountered are not problems. We want to add a new policy implementation class. We only need to implement the defined policy class without adding any additional code.