[Code structure design] Business code design using different implementation classes according to different conditions

Posted by servo on Fri, 10 May 2019 20:41:57 +0200

scene

At this time, there is a scenario, which needs to design a different business processing mode according to different states and conditions.

This may not be very understandable. For example, businesses on streets and lanes now use aggregated payment. Aggregated payment is a two-dimensional code supporting Alipay, WeChat, Jingdong wallet, UnionPay and so on, which can be paid by any APP payment.

Solutions

Train of thought

Define enumeration types for each payment channel

public enum PayWay {
    ALI_PAY,

    WECHAT_PAY;
}

Then a comment is defined on each corresponding service to indicate which payment method is appropriate.

@Pay(PayWay.ALI_PAY)
public class AliPayServiceImpl implements PayService  {}

But after careful consideration, there are still some problems.

  1. PayWay is an enumeration type that needs to be modified after adding a payment method
  2. In the procedure, we still need to judge PayWay according to different conditions. To increase the payment method, we have to modify the original judgment logic. The pseudocode is as follows
if("xxx" == "aliPay"){
    
} else if("xxx" == "wechatPay"){
    
}
//If the payment method is increased, else if will be added.

Train of thought II.

There are some problems in the train of thought. The first one is the judgment of if El se. First think about the role of this if else?

Answer: According to the train of thought, this if el se is used to determine which payment method to use.

We can extract this code, let the corresponding business implementation class implement its own logical implementation, and then decide whether to filter out the business implementation class based on the return value true or false. The interface is defined as follows: SupportBean is an encapsulated entity

boolean isSupport(SupportBean supportBean);

Then, each business implementation class implements its own isSupport method. The pseudocode is as follows

@Override
public boolean isSupport(SupportBean supportBean) {
    if (supportBean.getType() == "xxx"){
        return true;
    }
    
    return false;
}

Design

Note: Only one shelf is provided.

Interface definition

Service interface definition, a business execution method execute (parameter self-added), and an isSupport method (return true or false)

public interface Service {

    void execute();

    boolean isSupport(SupportBean supportBean);
}

Business Implementation Class

The execute method here simply prints strings on the console. The isSupport method takes the support Num in the SupportBean and returns true if the remainder is equal to 0.

There are two other similar implementations, which are not posted here.

@Component
public class AServiceImpl implements Service {
    @Override
    public void execute() {
        System.out.println("A execute");
    }

    @Override
    public boolean isSupport(SupportBean supportBean) {
        return supportBean.getSupportNum() % 3 == 0;
    }
}

Next, define a helper class

Helper class

@Component
public class Helper {

    @Autowired
    private List<Service> services;

    public void execute(SupportBean supportBean){

        Service s = services.stream()
                .filter((service) -> service.isSupport(supportBean))
                .findFirst()//NPE anomaly
                .orElse(null);


        if (s != null){
            s.execute();
        }
    }
}

The execute method of the tool class is used to obtain the execution results of the corresponding business implementation class, and to check the incoming parameters.

It should be noted that the findFirst() of the Lambda expression will have a NullPointException exception. Because filter filters lists, there is a length of 0 after filtering lists, and if findFirst is called at this time, NullPointException will be thrown. You can modify the above code to the following code so that NPE can be avoided

Service s = services.stream()
        .filter((service) -> service.isSupport(supportBean))
        .map(Optional::ofNullable)
        .findFirst()
        .flatMap(Function.identity())
        .orElse(null);

test

Add a spring boot test class and a test method.

Call the execute method of Helper in the context Loads test

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

    @Autowired
    private Helper Helper;

    @Test
    public void contextLoads() {
        Helper.execute(new SupportBean(3));
    }

}

test result

A execute

extend

In Lambda expression, the business implementation class is filtered first, then the first business implementation class is obtained and executed.

If there are multiple business implementation classes in the filter at this time, but the priority can not be determined, then how to extend it?

In fact, it's very simple to define a getPriority method in the Service interface first.

int getPriority();

Then their implementation classes implement the corresponding getPriority method

Then modify the Lambda expression, and add sorted method after filter to sort the business implementation classes.

Service s = services.stream()
        .filter((service) -> service.isSupport(supportBean))
        .sorted(Comparator.comparing(Service::getPriority))
        .map(Optional::ofNullable)
        .findFirst()
        .flatMap(Function.identity())
        .orElse(null);

summary

The whole framework is basically built. If it needs to be extended, only the corresponding business implementation classes need to be added, without modifying the code of other classes. Even the enumeration of the previous design can not be used, and the scalability is greatly improved. If you need to use it, you only need to modify the corresponding input parameters and the corresponding name.
Github address
If you have something to gain, welcome star, welcome fork
If you have similar experience, please join us and build together.

Topics: Java Lambda Spring github