This article is included in personal blog: www.chengxy-nds.top, technical resources sharing, progress together

The company seems to have secured funding recently! I started to look for channels to promote like crazy, and now I finally understand why THE previous period of large-scale recruitment. It turned out to be a big chess game, which is a good thing for employees in general. Maybe it’s time to mention the topic of salary increase with the boss.

But even before the salary increase came, the demand grew by the busload, new channels were added every day, and each channel had to provide personalized support, and the volume of development skyrocketed. Recently have no time more text, on time to get off work have become extravagant hope!


Due to the proliferation of promotion channels, and each source of orders in the order to do special logic processing, may be added every two days of a source, has changed the previous order logic face. For long-term consideration, I decided to reconstruct the existing logic, after all, it is better to have a long pain than a short pain.

Traditional implementation

We look at the pseudo-code below, which is roughly the code of order logic before reconstruction. Due to the few sources, simple if-else logic judgment is enough to meet the requirements.

At present, the processing logic of each order source has hundreds of lines of code, which seems quite bloated, but I have not started reconstruction. On the one hand, the business side always pushes you to meet the deadline like a pusher, and it is the fastest to write in this way. On the other hand, they don’t want to move, they want to play it safe in the face of antique code.

This time, however, there are dozens of sources, and it is impossible to maintain them in this way. Imagine the bloated if-else code, let alone the development.

public class OrderServiceImpl implements IOrderService {
    @Override
    public String handle(OrderDTO dto) {
        String type = dto.getType();
        if ("1".equals(type)) {
 return "Handle general orders";  } else if ("2".equals(type)) {  return "Deal with group purchase orders";  } else if ("3".equals(type)) {  return "Processing promotional orders";  }  return null;  } } Copy the code

Implementation of the policy pattern

To think about refactoring based on the current business scenario, it is better to use the policy pattern, which is one of the more well-known design patterns in OOP, an abstraction of method behavior.

A policy pattern defines a family of algorithms with common behavior, each of which is encapsulated, interchangeable, and varies independently of the client.

I. Application scenarios of policy mode:

  • For the same problem of a variety of ways to deal with, only specific behavior is different;
  • When multiple operations of the same type need to be securely encapsulated;
  • There are multiple subclasses of the same abstract class that the client needs to useif-elseorswitch-caseTo select a concrete subclass.

Here is the code modified with the policy mode:

@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{

 @Autowired
 private OrderStencilledService orderStencilledService;   @Override  public void handle(OrderBO orderBO) {   // Order completion broadcast notification (1 - Payment completed)  orderStencilledService.dispatchModeFanout(orderBO);   // SCMS outbound list  orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());  } } Copy the code

Each order source has its own logical implementation class, and every time you need to add an order source, just create a new implementation class and change the value of @OrderHandlerType(16) instead of going through the long, smelly IF-Lese.

Not only that, when assigning tasks, each person is responsible for the development of several order source logic, can do not interfere with each other, and greatly reduce the conflict of merged code.

Ii. Specific implementation process:

1. Define annotations

Define an annotation @OrderHandlerType that identifies the source of the order

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {
 int value() default 0; } Copy the code

2. Abstract business processor

A concrete business processor is abstracted up

public abstract class AbstractHandler {
 abstract public void handle(OrderBO orderBO);
}
Copy the code

3. Project start scanhandlerThe entrance

@Component
@SuppressWarnings({"unused"."rawtypes"})
public class HandlerProcessor implements BeanFactoryPostProcessor {
 
 private String basePackage = "com.ecej.order.pipeline.processor";
  public static final Logger log = LoggerFactory.getLogger(HandlerProcessor.class);   @Override  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {   Map<Integer,Class> map = new HashMap<Integer,Class>();   ClassScaner.scan(basePackage, OrderHandlerType.class).forEach(x ->{  int type = x.getAnnotation(OrderHandlerType.class).value();  map.put(type,x);  });   beanFactory.registerSingleton(OrderHandlerType.class.getName(), map);   log.info("Processor initialization {}", JSONObject.toJSONString(beanFactory.getBean(OrderHandlerType.class.getName())));  } } Copy the code

4. Scan the tool classes needed


public class ClassScaner {
 private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

 private final List<TypeFilter> includeFilters = new ArrayList<TypeFilter>();
  private final List<TypeFilter> excludeFilters = new ArrayList<TypeFilter>();   private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);   // Add the included Fiter  public void addIncludeFilter(TypeFilter includeFilter) {  this.includeFilters.add(includeFilter);  }   // Add the excluded Fiter  public void addExcludeFilter(TypeFilter excludeFilter) {  this.excludeFilters.add(excludeFilter);  }   // Scan the specified package to get all classes under the package  public static Set<Class<? >> scan(String basePackage, Class<? >... targetTypes) { ClassScaner cs = new ClassScaner();  for(Class<? > targetType : targetTypes){ if(TypeUtils.isAssignable(Annotation.class, targetType)){  cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));  }else{  cs.addIncludeFilter(new AssignableTypeFilter(targetType));  }  }  return cs.doScan(basePackage);  }   // Scan the specified package to get all classes under the package  public static Set<Class<? >> scan(String[] basePackages, Class<? >... targetTypes) { ClassScaner cs = new ClassScaner();  for(Class<? > targetType : targetTypes){ if(TypeUtils.isAssignable(Annotation.class, targetType)){  cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));  }else{  cs.addIncludeFilter(new AssignableTypeFilter(targetType));  }  }  Set<Class<? >> classes =newHashSet<Class<? > > (); for (String s : basePackages){  classes.addAll(cs.doScan(s));  }  return classes;  }   // Scan the specified package to get all classes under the package  public Set<Class<? >> doScan(String [] basePackages) {  Set<Class<? >> classes =newHashSet<Class<? > > (); for (String basePackage :basePackages) {  classes.addAll(doScan(basePackage));  }  return classes;  }   // Scan the specified package to get all classes under the package  public Set<Class<? >> doScan(String basePackage) {  Set<Class<? >> classes =newHashSet<Class<? > > (); try {  String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX  + ClassUtils.convertClassNameToResourcePath(  SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";  Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);  for (int i = 0; i < resources.length; i++) {  Resource resource = resources[i];  if (resource.isReadable()) {  MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);  if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {  try {  classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));  } catch (ClassNotFoundException ignore) {}  }  }  }  } catch (IOException ex) {  throw new RuntimeException("I/O failure during classpath scanning", ex);  }  return classes;  }   private boolean matches(MetadataReader metadataReader) throws IOException {  for (TypeFilter tf : this.excludeFilters) {  if (tf.match(metadataReader, this.metadataReaderFactory)) {  return false;  }  }  for (TypeFilter tf : this.includeFilters) {  if (tf.match(metadataReader, this.metadataReaderFactory)) {  return true;  }  }  return false;  } }  Copy the code

Instantiate abstract classes by type


@Component
public class HandlerContext {

 @Autowired
 private ApplicationContext beanFactory;   public AbstractHandler getInstance(Integer type){   Map<Integer,Class> map = (Map<Integer, Class>) beanFactory.getBean(OrderHandlerType.class.getName());   return (AbstractHandler)beanFactory.getBean(map.get(type));  }  } Copy the code

6. Call entry

Here I am processing multiple order sources when receiving an MQ message, with different order sources routed to different business processing classes.


@Component
@RabbitListener(queues = "OrderPipelineQueue")
public class PipelineSubscribe{
 
 private final Logger LOGGER = LoggerFactory.getLogger(PipelineSubscribe.class);   @Autowired  private HandlerContext HandlerContext;   @Autowired  private OrderValidateService orderValidateService;   @RabbitHandler  public void subscribeMessage(MessageBean bean){   OrderBO orderBO = JSONObject.parseObject(bean.getOrderBO(), OrderBO.class);   if(null! = orderBO &&CollectionUtils.isNotEmpty(bean.getType())) {  for(int value:bean.getType())  {  AbstractHandler handler = HandlerContext.getInstance(value);  handler.handle(orderBO);  }  }  } } Copy the code

Receive the entity MessageBean class code

public class MessageBean implements Serializable {
    private static final long serialVersionUID = 5454831432308782668L;
    private String cachKey;
    private List<Integer> type;
    private String orderBO;
  public MessageBean(List<Integer> type, String orderBO) {  this.type = type;  this.orderBO = orderBO;  } } Copy the code

The above design mode looks slightly complicated, and some small partners question it: “You for an if-else, make such trouble, and custom annotations, and make so many classes is not trouble?” Others struggle with performance issues, and policy mode may indeed perform worse than if-else.

But I think it’s worth a little more complexity and a loss of performance in exchange for clean and maintainable code. However, a person an idea, how to choose or see the specific business scene!


Advantages and disadvantages of strategic patterns

advantages

  • Easy to expand, add a new policy only need to add a specific policy class, basically do not need to change the original code, in line with the open and closed principle
  • Avoid the use of multiple conditional selection statements, fully reflect the object-oriented design idea policy classes can be switched freely, because the policy classes implement the same interface, so they can be switched freely
  • Each policy class uses one policy class, which conforms to the single responsibility principle. The client is decoupled from the policy algorithm, and both depend on the abstract policy interface, which conforms to the dependency inversion principle
  • The client does not need to know which policy classes are available, which complies with the minimum knowledge principle

disadvantages

  • Policy pattern, when there are too many policy algorithms, will result in too many policy classes
  • The client does not know which policy classes are available and cannot decide which policy class to use, which can be resolved by encapsulating the Common public package or by enablingThe IOC containerandDependency injectionTo solve the problem.

The following is part of the order source policy class, which I have to say is quite extensive.

conclusion

Everything has its own two sides, if-else layer nesting and also has its own advantages and disadvantages:

  • If-else has the advantage of simplicity. If you want to iterate quickly, the logic nesting is less and will not continue to increase. If-else is better, but the disadvantage is also obvious, the code is bloated and cumbersome and not easy to maintain.

  • The policy mode will separate the logic of each scenario for maintenance. The same abstract class has multiple subclasses. If you need to use if-else or switch-case to select a specific subclass, you are advised to use the policy mode.

The two implementation methods have their own advantages and disadvantages. The choice should be based on the specific business scenario, or the design mode should be used in the most appropriate position rather than for the purpose of use.

Small talk

I often chat with my fans in private, and many people feel about learning design mode: Design mode memorized a lot, but the usual development is not all day to write if-else business logic, it is not needed at all.

Learning design patterns is not useless, but sometimes there is no suitable scenario, such as the business scenario we are talking about today, using design patterns can be perfectly solved.

It is a common thing that many technologies can not be used in work after learning them. A stable project will have many considerations when using one technology. Will the new technology improve the system complexity? What are its performance bottlenecks? These must be taken into account, after all, the stability of the project is the most important, no one dare to take a risk.

And we learn technology not only for the current project whether it will be used, is to do a technology accumulation, long-term plan, people go to the heights, do not have some ability can not.


Original is not easy, burning hair output content, I hope you can have a drop drop harvest!

I sorted out hundreds of technical e-books and gave them to my friends. Please reply to [” 666 “] for self-collection. I set up a technology exchange group with some friends to discuss technology and share technical information, aiming to learn and progress together. If you are interested, just scan code and join us!