In order not to make the code look like a bunch of*, I used this repeatedly in my work

Most of the time I write some business code, maybe a bunch of CRUD can solve the problem, but this kind of work does not improve the technical people much, how to free myself from the business and find the fun of writing code, I do After some attempts, using design patterns to improve your business code is one of them.

Responsibility chain design pattern

definition

The request is processed on a chain, and the acceptor on the chain decides whether to continue forwarding or interrupt the current processing flow after processing.

Applicable scene

Suitable for multi-node process processing, each node completes its own part of responsibility, and the nodes do not know each other's existence, such as the approval flow of OA, the Filter mechanism in java web development. To give an example in life, when I was renting a house, I met a so-called black intermediary. When I rented a house, I felt that I was a god, but when I asked him to fix something broken, I was like a grandson. The customer service asked me to find the landlord again, and the landlord asked me to find her husband. In the end, the matter was dealt with in the end (you must find a regular agent for renting a house).

Experience

The business I currently do is aggregate payment for campus group meals. The business process is very simple. 1. The student opens the mobile payment code to pay, 2. The cafeteria aunt uses the machine to scan the payment code to collect the payment. The background of the university canteen is like this. The canteen is subsidized and the dishes are relatively cheap. Therefore, the school is not willing to let the public go to the school canteen to consume. In view of this, we have added a set of logic to check whether the payment is allowed before paying. as follows: 

  1. A certain stall allows only certain types of users to consume. For example, the teacher stall only allows teachers to consume, and the student stall does not allow outside users to consume;

  2. A certain stall only allows certain types of users to consume a few times a day, for example, a teacher’s cafeteria only allows students to consume once a day;

  3. Whether non-halal students are allowed to consume, such as certain halal restaurants, non-halal students are not allowed to consume;

For these types of situations, I have established three types of filters, namely:

SpecificCardUserConsumeLimitFilter: Determine whether to allow consumption according to user type 

DayConsumeTimesConsumeLimitFilter: Determine whether to allow consumption according to the number of daily consumption 

MuslimConsumeLimitFilter: Whether non-halal users are allowed to consume 

The judgment logic is to first judge whether the current user can consume at this stall through SpecificCardUserConsumeLimitFilter. If allowed to continue, DayConsumeTimesConsumeLimitFilter will determine whether the number of consumptions of the day has been used up, if not used up, continue to use MuslimConsumeLimitFilter to determine whether the current user meets the dining conditions of a halal restaurant. Three judgments, as long as one is not satisfied, return early.

Part of the code is as follows:

public boolean canConsume(String uid,String shopId,String supplierId){
    //获取用户信息,用户信息包含类型(student:学生,teacher:老师,unknown:未知用户)、名族(han:汉族,mg:蒙古族)
    UserInfo userInfo = getUserInfo(uid);
    //获取消费限制信息,限制信息包含是否允许非清真消费、每种类型的用户是否允许消费以及允许消费的次数
   ConsumeConfigInfo consumeConfigInfo = getConsumeConfigInfo(shopId,supplierId) 

    // 构造消费限制过滤器链条
    ConsumeLimitFilterChain filterChain = new ConsumeLimitFilterChain();
    filterChain.addFilter(new SpecificCardUserConsumeLimitFilter());
    filterChain.addFilter(new DayConsumeTimesConsumeLimitFilter());
    filterChain.addFilter(new MuslimConsumeLimitFilter());
    boolean checkResult = filterChain.doFilter(filterChain, schoolMemberInfo, consumeConfigInfo);

    //filterChain.doFilter方法
   public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
    ConsumeConfigInfo consumeConfigInfo ){
  //迭代调用过滤器
  if(index<filters.size()){
      return filters.get(index++).doFilter(filterChain, userInfo, consumeConfigInfo);
  }
    }

    //SpecificCardUserConsumeLimitFilter.doFilter方法
     public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
    ConsumeConfigInfo consumeConfigInfo ){
                //获取某一类型的消费限制,比如student允许消费,unknown不允许消费
  CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);

  // 判断当前卡用户是否允许消费
  if (consumeCardConfig != null) {
   if ((!CAN_PAY.equals(cardConsumeConfig .getEnabledPay()))) {
       return false;
   }
  }

                //其余情况,继续往后传递
            return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
        }

    //DayConsumeTimesConsumeLimitFilter.doFilter方法
     public boolean doFilter(ConsumeLimitFilterChain filterChain,UserInfo userInfo,
    ConsumeConfigInfo consumeConfigInfo ){
                //获取某一类型的消费限制,比如student可以消费2次
  CardConsumeConfig cardConsumeConfig = findSuitCardConfig(userInfo, consumeConfigInfo);

                //获取当前用户今天的消费次数
                int consumeCnt = getConsumeCnt(userInfo)  
  if(consumeCnt >= cardConsumeConfig.getDayConsumeTimesLimit()){
                    return false;
                }

                //其余情况,继续往后传递
                return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
        }

to sum up

The judgment logic of each restriction condition is encapsulated in a specific Filter. If the logic of a certain restriction condition is modified, other conditions will not be affected. If you need to add a new restriction condition, you only need to re-construct a Filter and weave it into the FilterChain.

Strategy design pattern

definition

Define a series of algorithms, encapsulate each algorithm, and make them interchangeable

Applicable scene

The main purpose is to eliminate a large number of if else codes and extract the algorithm logic behind each judgment into a specific strategy object. When the algorithm logic is modified, the user will not perceive the user and only need to modify the internal logic of the strategy object. Such policy objects generally implement a common interface and can achieve the purpose of interchange.

Experience

The author has a requirement before that after the user scans the QR code and pays, pushes a payment message to the cashier device at the stall. The cashier device will broadcast the message after receiving the message. The logic is very simple, just call the push platform to push a message to the device, but Due to historical reasons, the push platform for certain devices is different. Class A devices prefer to use pigeon push. If it fails, it needs to be downgraded to the long polling mechanism. Class B devices can directly use the self-developed push platform. The current situation is that the message formats of Type A and Type B are different (developed by different teams and integrated later). In view of this, I abstracted the PushStrategy interface. The specific implementations are IotPushStrategy and XingePushStrategy, which correspond to self-developed For the push strategy of the push platform and the push strategy of the pigeon platform, users can use different push strategies for different device types. Part of the code is as follows:

/**
 * 推送策略
 * /
public interface PushStrategy {
 /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */
 public CallResult push(AppDeviceVO deviceVO, Object content);
}

IotPushStrategy implements PushStrategy{
        /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */
 public CallResult push(AppDeviceVO deviceVO, Object content){
            //创建自研推送平台需要的推送报文
            Message message = createPushMsg(deviceVO,content);

            //调用推送平台推送接口
            IotMessageService.pushMsg(message);
        }
}

XingePushStrategy implements PushStrategy{
        /**
         @param deviceVO设备对象,包扣设备sn,信鸽pushid
         @param content,推送内容,一般为json
        */
 public CallResult push(AppDeviceVO deviceVO, Object content){
            //创建信鸽平台需要的推送报文
            JSONObject jsonObject = createPushMsg(content);

            //调用推送平台推送接口
            if(!XinggePush.pushMsg(message)){
                //降级到长轮询
                ...
            }
        }
}

/**
消息推送Service
*/
MessagePushService{
    pushMsg(AppDeviceVO deviceVO, Object content){
        if(A设备){
            XingePushStrategy.push(deviceVO,content);
        } else if(B设备){
            IotPushStrategy.push(deviceVO,content);
        }
    }
}

to sum up

The push logic of each channel is encapsulated into a specific strategy. The change of a certain strategy will not affect other strategies. Because the common interface is implemented, the strategies can be replaced with each other, which is user-friendly, such as the task rejection strategy in java ThreadPoolExecutor , When the thread pool is saturated, the rejection strategy will be executed, and the specific rejection logic is encapsulated in the rejectedExecution of the RejectedExecutionHandler.

Template design pattern

definition

The value of the template lies in the definition of the skeleton. The process of problem processing within the skeleton has been defined. The general processing logic is generally implemented by the parent class, and the personalized processing logic is implemented by the subclass. For example, fried potato shreds and fried mapo tofu, the general logic is 1. cut vegetables, 2. put oil, 3. stir-fry, 4. cook, 1, 2, 4 are similar, but the third step is different. Stir-fried potato shreds have to be stir-fried with a shovel, but fried Mapo tofu has to be nudged with a spoon, otherwise the tofu will be rotten.

scenes to be used

In the processing flow of different scenarios, part of the logic is universal and can be placed in the parent class as a universal implementation, and part of the logic is personalized and needs to be implemented by the subclass.

Experience

Following the example of the previous voice broadcast, we added two new requirements later: 1. Message push needs to add trace 2. Some channel push fails and needs to be retried, so the current process becomes like this: 1. Trace starts 2. Channel Start push 3. Whether to allow retry, if the execution of retry logic is allowed 4. Trace ends where 1 and 4 are universal, 2 and 3 are personalized, in view of this, I added a parent class before the specific push strategy The strategy of putting the general logic in the parent class, the modified code is as follows:

abstract class AbstractPushStrategy implements PushStrategy{
    @Override
    public CallResult push(AppDeviceVO deviceVO, Object content) {
        //1.构造span
        Span span = buildSpan();
        //2.具体通道推送逻辑由子类实现
        CallResult callResult = doPush(deviceVO, content);

        //3.是否允许重试逻辑由子类实现,如果允许执行重试逻辑
        if(!callResult.isSuccess() && canRetry()){
            doPush(deviceVO, content);
        }

        //4.trace结束
        span.finish() 
    }

    //具体推送逻辑由子类实现
    protected abstract CallResult doPush(AppDeviceVO deviceDO, Object content) ;

    //是否允许重试由子类实现,有些通道之前没有做消息排重,所有不能重试
    protected abstract boolean canRetry(CallResult callResult);

}

XingePushStrategy extends AbstractPushStrategy{
    @Override
    protected CallResult doPush(AppDeviceVO deviceDO, Object content) {
        //执行推送逻辑
    }

    @Override
    protected boolean canRetry(CallResult callResult){
        return false
    }
}

to sum up

The process is defined through the template, and the common logic is implemented in the parent class, which reduces the repetitive code. The personalized logic is implemented by the subclass itself, and the modification of the code between the subclasses does not interfere with each other or destroy the process.

Observer design pattern

Mode definition

As the name suggests, this mode requires two roles: Observer and Observable. When the status of the Observable changes, it will notify the Observer. The Observer generally implements a common interface, such as java.util.Observer, which is required by Observable. When notifying the Observer, just call the update method of the Observer one by one. The success or failure of the Observer processing should not affect the Observable process.

scenes to be used

An object (Observable) status change needs to notify other objects. The existence of Observer does not affect the processing result of Observable. The addition and deletion of Observer is not aware of Observable, such as Kafka's message subscription, producer sends a message to topic, as for whether it is 1 or 10 Consumers subscribe to this topic, producers do not need to pay attention.

Experience

In the responsibility chain design model, I solved the consumption restriction inspection problem through three filters. One of the filters is used to check the number of consumptions. Here I just read the consumption times of the user, so how does the accumulation of consumption times complete? What about? In fact, the observer mode is used for the accumulation. Specifically, when the transaction system receives the payment success callback, it will publish the "payment success event" through the Spring event mechanism, which is responsible for accumulating the number of consumption and the subscription of the voice broadcast The person will receive the "payment success event", and then do their own business logic, draw a simple diagram to describe:

In order not to make the code look like a bunch of*, I used this repeatedly in my work

The code structure is roughly as follows:

/**
支付回调处理者
*/
PayCallBackController implements ApplicationContextAware {
     private ApplicationContext applicationContext;

    //如果想获取applicationContext需要实现ApplicationContextAware接口,Spring容器会回调setApplicationContext方法将applicationContext注入进来
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }
     @RequestMapping(value = "/pay/callback.do")
     public View callback(HttpServletRequest request){
        if(paySuccess(request){
            //构造支付成功事件
            PaySuccessEvent event = buildPaySuccessEvent(...);
            //通过applicationContext发布事件,从而达到通知观察者的目的
            this.applicationContext.publishEvent(event);
        } 
    }
}
/**
 * 语音播报处理者
 *
 */
public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent>{
    @Override
    public void onApplicationEvent(PaySuccessEvent event) {
        //语音播报逻辑
    }
}

//其他处理者的逻辑类似

to sum up

The observer mode decouples the observer and the observer, and the presence or absence of the observer will not affect the existing logic of the observer.

Decorator design pattern

definition

The decorator is used to wrap the original class and enhance the function while being transparent to the user. For example, the BufferedInputStream in java can enhance the InputStream it wraps to provide a buffering function.

scenes to be used

When you want to enhance the functions of the original class, but do not want to add too many subclasses, you can use the decorator mode to achieve the same effect.

Experience

The author was promoting the entire company to access the trace system before, so I also provided some tools to solve the automatic weaving of trace and the automatic transmission of context. If you are interested, you can read my other blog jaeger using preliminary exploration, in order to support inter-thread For context transfer, I added the decorative class TraceRunnableWrapper to transparently transfer the context of the parent thread to the child thread, which is completely transparent to the user. The code is as follows:

/**
可以自动携带trace上下文的Runnable装饰器
*/
public class TraceRunnableWrapper implements Runnable{
    //被包装的目标对象
    private Runnable task;
    private Span parentSpan = null;

    public TraceRunnableWrapper(Runnable task) {
        //1.获取当前线程的上下文(因为new的时候还没有发生线程切换,所以需要在这里将上下文获取)
        //对这块代码感兴趣的可以查看opentracing API
        io.opentracing.Scope currentScope = GlobalTracer.get().scopeManager().active();
        //2.保存父上下文
        parentSpan = currentScope.span();
        this.task = task;
    }

    @Override
    public void run() {
        //run的时候将父线程的上下文绑定到当前线程
        io.opentracing.Scope scope = GlobalTracer.get().scopeManager().activate(parentSpan,false);
        task.run();
    }
}

//使用者
new Thread(new Runnable(){run(...)}).start()替换为new TraceRunnableWrapper(new Runnable(){run(...)}).start()

to sum up

Use the decorator mode to enhance the function, for users only need to make a simple combination to continue to use the original function.

Appearance design mode

definition

What is appearance is to provide a unified entrance to the outside world. One is to hide the details of the system, and the other is to reduce the complexity of users. For example, the DispaterServlet in SpringMvc, all Controllers are exposed through DispaterServlet.

scenes to be used

Reduce the complexity of users and simplify client access costs.

Experience

The author’s company provides some open capabilities to third-party ISVs, such as equipment management and control, unified payment, and statement downloading capabilities. Since they belong to different teams, the external interfaces provided are in different forms. If there are more, ISVs can also accept it. However, when there are more interfaces, ISVs began to complain about the high access cost. To solve this problem, we added a front-end controller GatewayController in front of the open interface, which is actually the prototype of our later open platform. , GatewayController uniformly exposes an interface gateway.do to the outside world. The request parameters and response parameters of the external interface are unified in the GatewayController for convergence. The GatewayController also uses a unified interface when routing to the back-end service. The comparison before and after the transformation is as follows:

In order not to make the code look like a bunch of*, I used this repeatedly in my work

Probably the code is as follows:

使用者:
HttpClient.doPost("/gateway.do","{'method':'trade.create','sign':'wxxaaa','timestamp':'15311111111'},'bizContent':'业务参数'")

GatewayController:
@RequestMapping("/gateway.do")
JSON gateway(HttpServletRequest req){
   //1.组装开放请求
   OpenRequest openRequest = buildOpenRequest(req);

   OpenResponse openResponse = null;
   //2.请求路由
   if("trade.create".equals(openRequest.getMethod()){
       //proxy to trade service by dubbo
       openResponse = TradeFacade.execute(genericParam);
   } else if("iot.message.push".equals(openRequest.getMethod()){
       //proxy to iot service by httpclient
        openResponse = HttpClient.doPost('http://iot.service/generic/execute'genericParam);
   }

   if(openResponse.isSuccess()){
        return {"code":"10000","bizContent":openResponse.getResult()};
   }else{
        return {"code":"20000","bizCode":openResponse.getCode()};
   }

}

to sum up

The appearance mode is used to shield some details inside the system and reduce the user's access cost. Take GatewayController as an example. The ISV authentication, interface verification and other repetitive tasks are unified by it. ISVs only need to connect different interfaces Concerned about a set of interface protocol interfaces, convergence is done by the GatewayController layer.

Guess you like

Origin blog.51cto.com/14570694/2540472