Welcome to pay attention to the public account “JAVA Front” to view more wonderful sharing articles, mainly including source code analysis, practical application, architecture thinking, workplace sharing, product thinking and so on, at the same time, welcome to add my personal wechat “JAVA_front” to communicate and learn together

1 complex, complex, complex

In development work, we often hear: the business is complex, the system is complex, the logic is complex, as long as the processing of difficult scenarios, seems to use the word complex to describe.

However, I think the reasons why difficulties are difficult are different, and it is necessary to distinguish between them instead of using the word complex to generalize them. In general, I think it can be divided into complex, complex, complex three types.

Complexity and complexity both mean branching and logic, but the difference is that complex scenes can be sorted out and elegant systems can be designed properly. But the complex scene is difficult to sort out, in order to compatibility can only play a variety of patches, the final system reconstruction.

There is also a type of complexity, which can be called complexity. When the number reaches a certain scale, complexity and complexity can evolve into complexity. Although the same is complex, but also complex and complex complex distinction. As long as this paper discusses the complexity and complexity clearly, as long as the addition of quantitative dimension is complex.

We can write complex code during development, but we should avoid complex code as much as possible. Code coupling is a typical complex scenario where highly coupled code between modules leads to no maintenance at all. In this article, we discuss seven types of code coupling.


Code coupling type

The seven code coupling types are in descending order of coupling degree: content coupling, common coupling, external coupling, control coupling, marker coupling, data coupling and indirect coupling.



2.1 Content Coupling

The ability of one module to directly access another module’s internal data is called content coupling, and it is the most coupling type, and it is one that we should try to avoid.



Assuming that module A is the order module and module B is the payment module, if the payment module has direct access to the order data table, at least the following problems will arise.

The first problem is that there is duplicate data access layer code, with both the payment and order modules writing order data access code. The second problem is that if the order business changes, the order data field needs to be changed. If the payment module does not change in time, it may cause business errors.

The third problem is that if the order business changes, it is necessary to split the data by database and table. If the payment module does not follow the timely change, for example, shardingKey is not used for query or the old library table stops writing, serious errors may be caused to the payment module.

The fourth problem is that the service entrance does not converge, and the access entrance is scattered everywhere. If you want to change the service, you need to modify several places, which is very bad for maintenance.


2.2 Common Coupling

Common coupling is referred to when multiple modules access the same common data environment, such as global data structures, shared communication areas, and memory common coverage areas.



For example, Apollo dynamic configuration is used in the project. The content of configuration item A is A piece of JSON. Both the order module and the payment module read and parse this data structure for business processing.


public class ApolloConfig {
    @Value("${apollo.json.config}")
    private String jsonConfig;
}

public class JsonConfig {
    public int type;
    public boolean switchOpen;
}

public class OrderServiceImpl {
    public void createOrder(a) {
        String jsonConfig = apolloConfig.getJsonConfig();
        JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);
        if(config.getType() == TypeEnum.ORDER.getCode() && config.isSwitchOpen()) { createBizOrder(); }}}public class PayServiceImpl {
    public void createPayOrder(a) {
        String jsonConfig = apolloConfig.getJsonConfig();
        JsonConfig config = JSONUtils.toBean(jsonConfig, JsonConfig.class);
        if(config.getType() == TypeEnum.PAY.getCode() && config.isSwitchOpen()) { createBizPayOrder(); }}}Copy the code


2.3 External Coupling

Multiple modules accessing the same global simple variable (non-global data structure) and passing this global variable information without a parameter list is called external coupling.



For example, Apollo dynamic configuration is used in the project. The content of configuration item A is A simple variable, which is read by both the order module and the payment module for business processing.


public class ApolloConfig {
    @Value("${apollo.type.config}")
    private int typeConfig;
}

public class OrderServiceImpl {
    public void createOrder(a) {
        if(apolloConfig.getTypeConfig() == TypeEnum.ORDER.getCode()) { createBizOrder(); }}}public class PayServiceImpl {
    public void createPayOrder(a) {
        if(apolloConfig.getTypeConfig() == TypeEnum.PAY.getCode()) { createBizPayOrder(); }}}Copy the code


2.4 Control coupling

Information transmitted between modules that contains information used to control the interior of modules is called control coupling. Control coupling may lead to interweaving of control logic between modules, which is not conducive to code maintenance.



The control coupling code example is as follows. We can see that the code logic of module B heavily depends on the type of module A. If the type of A changes, it is likely to affect the logic of B:


public class ModuleA {
    private int type;
}

public class A {
    private B b = new B();
    public void methondA(int type) {
        ModuleA moduleA = newModuleA(type); b.methondB(moduleA); }}public class B {
    public void methondB(ModuleA moduleA) {
        if(moduleA.getType() == 1) {
            action1();
        } else if(moduleA.getType() == 2) { action2(); }}}Copy the code


2.5 Tag coupling

The passing of data structure information by multiple modules through parameter lists is called tag coupling and can be analogous to JAVA language reference passing.



2.6 Data Coupling

The passing of simple data information by multiple modules through parameter lists is called tag coupling and can be analogous to JAVA language value passing.



2.7 Indirect coupling

There is no direct connection between modules, and the connection through the control and invocation of the main module is called indirect coupling, which is also an ideal way of coupling.



Let’s focus on indirect coupling. An important reason for the complexity of complex businesses is that there are so many roles or types involved that it is difficult to design them in a straightforward way. If you have to tile, you’re going to have a lot of if else blocks.

Let’s start with an order scenario. Currently, there are three orders of ABC: THE price of order A is 10% off, the maximum weight of logistics cannot exceed 9kg, and refund is not supported. B order price 20% off, logistics maximum weight is not more than 8 kg, refund support. C the price of the order is 30% off. The maximum weight of the logistics cannot exceed 7kg. Refund is supported. It is also not difficult to write the code in a straightforward way as the requirements are written:


public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    @Override
    public void createOrder(OrderBO orderBO) {
        if (null == orderBO) {
            throw new RuntimeException("Abnormal parameter");
        }
        if (OrderTypeEnum.isNotValid(orderBO.getType())) {
            throw new RuntimeException("Abnormal parameter");
        }
        // Type A order
        if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.9);
            if (orderBO.getWeight() > 9) {
                throw new RuntimeException("Excess of logistics maximum weight");
            }
            orderBO.setRefundSupport(Boolean.FALSE);
        }
        // Type B order
        else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.8);
            if (orderBO.getWeight() > 8) {
                throw new RuntimeException("Excess of logistics maximum weight");
            }
            orderBO.setRefundSupport(Boolean.TRUE);
        }
        // Type C order
        else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {
            orderBO.setPrice(orderBO.getPrice() * 0.7);
            if (orderBO.getWeight() > 7) {
                throw new RuntimeException("Excess of logistics maximum weight");
            }
            orderBO.setRefundSupport(Boolean.TRUE);
        }
        // Save data
        OrderDO orderDO = newOrderDO(); BeanUtils.copyProperties(orderBO, orderDO); orderMapper.insert(orderDO); }}Copy the code


The above code is functionally sufficient to fulfill the business requirements, but programmers need to think about the maintainability of the code as well as the functionality. If we add an order type, or if we add an order attribute processing logic, we need to add code to that logic, and if we don’t handle it properly, it will affect the original logic.

To avoid this, the open and close principle of design patterns requires us to open for additions and close for modifications, which I think is the most important principle of design patterns.

Requirements change by extending rather than modifying existing code, thus ensuring code stability. Extensions are not arbitrary, either, because the algorithm is defined beforehand, extensions are based on the algorithm, frameworks are built with abstractions, and details are extended with implementations. In the final analysis, the 23 design modes in the standard sense are all following the open and closed principle.

How to change the way of thinking? We need to increase the analytical dimension. The most common one is to increase the horizontal and vertical dimensions. Generally speaking, the horizontal expansion is the breadth of thinking, and the vertical expansion is the depth of thinking. Corresponding to the system design, it can be summarized as: vertical isolation, horizontal arrangement.

At this point, we can add vertical and horizontal dimensions to the problem analysis, and choose the analysis matrix method, where vertical represents strategy and horizontal represents scene:



2.7.1 Vertical Isolation

The vertical dimension represents policies, and different policies should be logically and businesswise isolated. This example includes preferential policy, logistics policy and refund policy. As an abstraction, different order types extend this abstraction, and the policy mode is very suitable for this scenario. This paper analyzes preferential strategy, logistics strategy and refund strategy in detail.


// Discount strategy
public interface DiscountStrategy {
    public void discount(OrderBO orderBO);
}

// Type A preferential policy
@Component
public class TypeADiscountStrategy implements DiscountStrategy {

    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.9); }}// Type B preferential policy
@Component
public class TypeBDiscountStrategy implements DiscountStrategy {

    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.8); }}// Type C preference policy
@Component
public class TypeCDiscountStrategy implements DiscountStrategy {

    @Override
    public void discount(OrderBO orderBO) {
        orderBO.setPrice(orderBO.getPrice() * 0.7); }}// Discount strategy factory
@Component
public class DiscountStrategyFactory implements InitializingBean {
    private Map<String, DiscountStrategy> strategyMap = new HashMap<>();

    @Resource
    private TypeADiscountStrategy typeADiscountStrategy;
    @Resource
    private TypeBDiscountStrategy typeBDiscountStrategy;
    @Resource
    private TypeCDiscountStrategy typeCDiscountStrategy;

    public DiscountStrategy getStrategy(String type) {
        return strategyMap.get(type);
    }

    @Override
    public void afterPropertiesSet(a) throws Exception { strategyMap.put(OrderTypeEnum.A_TYPE.getCode(), typeADiscountStrategy); strategyMap.put(OrderTypeEnum.B_TYPE.getCode(), typeBDiscountStrategy); strategyMap.put(OrderTypeEnum.C_TYPE.getCode(), typeCDiscountStrategy); }}// Preferential policy execution
@Component
public class DiscountStrategyExecutor {
    private DiscountStrategyFactory discountStrategyFactory;

    public void discount(OrderBO orderBO) {
        DiscountStrategy discountStrategy = discountStrategyFactory.getStrategy(orderBO.getType());
        if (null == discountStrategy) {
            throw new RuntimeException("No preferential strategy"); } discountStrategy.discount(orderBO); }}Copy the code


2.7.2 Horizontal Arrangement

The horizontal dimension represents the scenario. In a broad sense, an order type can be considered as a business scenario in which independent strategies are connected in series. The template method design pattern is suitable for this scenario.

The template method pattern typically uses abstract classes to define the algorithm skeleton, along with some abstract methods that are deferred to subclasses so that they not only comply with the algorithm skeleton conventions, but also implement their own algorithms. Flexibility as well as specification is ensured by building the framework with abstractions and extending the details with implementations.


// Create the order service
public interface CreateOrderService {
    public void createOrder(OrderBO orderBO);
}

// Abstract the order creation process
public abstract class AbstractCreateOrderFlow {

    @Resource
    private OrderMapper orderMapper;

    public void createOrder(OrderBO orderBO) {
        // Check parameters
        if (null == orderBO) {
            throw new RuntimeException("Abnormal parameter");
        }
        if (OrderTypeEnum.isNotValid(orderBO.getType())) {
            throw new RuntimeException("Abnormal parameter");
        }
        // Calculate discount
        discount(orderBO);
        // Calculate the weight
        weighing(orderBO);
        // Refund support
        supportRefund(orderBO);
        // Save data
        OrderDO orderDO = new OrderDO();
        BeanUtils.copyProperties(orderBO, orderDO);
        orderMapper.insert(orderDO);
    }

    public abstract void discount(OrderBO orderBO);

    public abstract void weighing(OrderBO orderBO);

    public abstract void supportRefund(OrderBO orderBO);
}

// Implement the order creation process
@Service
public class CreateOrderFlow extends AbstractCreateOrderFlow {

    @Resource
    private DiscountStrategyExecutor discountStrategyExecutor;
    @Resource
    private ExpressStrategyExecutor expressStrategyExecutor;
    @Resource
    private RefundStrategyExecutor refundStrategyExecutor;

    @Override
    public void discount(OrderBO orderBO) {
        discountStrategyExecutor.discount(orderBO);
    }

    @Override
    public void weighing(OrderBO orderBO) {
        expressStrategyExecutor.weighing(orderBO);
    }

    @Override
    public void supportRefund(OrderBO orderBO) { refundStrategyExecutor.supportRefund(orderBO); }}Copy the code


2.7.3 Vertical and horizontal thinking

The above example business and code are not complicated. In fact, complex business scenarios are just the superposition, combination and interweaving of simple scenarios. It is nothing more than seeking answers through vertical isolation and horizontal arrangement.



The vertical dimension abstractions the concept of capability pool, which contains many capabilities. Different capabilities are aggregated according to different business dimensions, such as preferential capability pool, logistics capability pool and refund capability pool. We can see two degrees of isolation, capability pools are isolated from each other and capabilities are isolated from each other.

The horizontal dimension selects capabilities from the capability pool and links them together according to business requirements to form different business processes. Because the ability can be arbitrarily combined, it embodies great flexibility. In addition, different capabilities can be executed in serial. If there is no dependency between different capabilities, they can also be executed in parallel as in process Y to improve execution efficiency.


3 Article Summary

First, this paper distinguishes the concepts of complexity, complexity and complexity. Although complexity and complexity are difficult to deal with, complexity can be sorted out, and complexity will eventually accumulate. We should avoid cumbersome code as much as possible. Complexity and complexity add a quantitative dimension to the complexity.

Secondly, this paper introduces seven types of code coupling, which are content coupling, common coupling, external coupling, control coupling, marker coupling, data coupling and indirect coupling in descending order according to coupling degree. We should write code that is as uncoupled as possible.

Thirdly, this paper starts from an example of a complex order scenario and focuses on the indirect coupling type. It can be seen that even a complex scenario can be elegantly realized through reasonable design. I hope this paper will be helpful to you.

Welcome to pay attention to the public account “JAVA Front” to view more wonderful sharing articles, mainly including source code analysis, practical application, architecture thinking, workplace sharing, product thinking and so on, at the same time, welcome to add my personal wechat “JAVA_front” to communicate and learn together