Welcome to my GitHub repository Star/fork:

Java-Interview-Tutorial

Github.com/Wasabi1234/…

This article will teach you how to elegantly eliminate duplicate code and change your mind about business code being untechnical.

1 “Pain” for CRUD Engineers

Many CRUD engineers complain about the lack of technical content in business development, the lack of design patterns, high concurrency, and heap CRUD. When asked in every interview, “Tell me about common design patterns?” “, singletons can only be described as mastery. Other design patterns, if heard, are simply described because they are not actually used. For reflection and annotation, I just know that many of them are used in the framework, but I don’t write the framework myself, and I don’t know how to use them.

  • Design patterns are the experience of world-class software masters on large projects and are proven to be beneficial for maintaining large projects.
  • Advanced features such as reflection, annotations, and generics are used in frameworks because they often require the same set of algorithms to deal with different data structures, and these features can help reduce code duplication and maintenance.

Each coder must pay attention to improve the maintainability of the project. A very important method is to reduce code duplication, because excessive duplication will lead to:

  • Easy to modify one place forget to modify another place, causing bugs
  • Some codes are not identical, but have high similarity, and it is easy to change (CV) errors to change the original differences into the same

Factory + template method pattern eliminates multiple Ifs and duplicate code

2.1 requirements

Develop shopping cart orders and handle them differently for different users:

  • General users need to charge freight, which is 10% of the price of the goods, no discount
  • VIP users will also be charged 10 percent of the price of the item, but when they buy more than two identical items, they will receive a discount starting on the third item
  • Internal users can free shipping, no merchandise discounts

Implement three types of shopping Cart business logic, convert the input parameter Map object (K: ID of goods, V: quantity of goods) into the output parameter shopping Cart type Cart.

2.2 Rookie Implementation

  • The shopping cart

  • Items in the shopping cart

2.2.1 Common Users

2.2.2 VIP user

VIP users can enjoy discounts for multiple purchases of similar products. Just deal with the extra buy discount portion.

2.2.3 Internal Users

Free shipping, no discount, only deal with merchandise discounts and freight logic difference.

More than half of the three shopping carts have duplicate code. Although different types of users calculate shipping rates and offers differently, the logic for initializing, counting total prices, total shipping, total offers, and paying prices is the same for the entire shopping cart.

Code repetition itself is not terrible, what is terrible is missing changes or errors. For example, the student who wrote the shopping cart for VIP users found a Bug in the calculation of the total price of goods. Instead of adding the prices of all items together, he should add the price*quantity of all items. He may have only fixed the shopping cart code for VIP users, missing the same Bug in the implementation of repeated logic in the shopping cart for ordinary and internal users.

With three shopping carts, you need to use different shopping carts for different user types.

  • Use multiple Ifs to call different shopping cart processes for different types of users

Can we just keep adding more and more shopping cart classes, writing repetitive shopping cart logic, and writing more if logic? Of course not, the same code should only appear in one place!

2.3 Refactoring Secret technique – template method pattern

You can define repetitive logic in abstract classes, as long as the three shopping carts implement different parts of the logic. This is actually the template method pattern. Implement the process template for shopping cart handling in the parent class, and leave the abstract method definitions that require special handling to be implemented by subclasses. Because the superclass logic does not work alone, it needs to be defined as an abstract class.

As shown in the following code, the AbstractCart abstract class implements the general logic for shopping carts, with two additional abstract methods defined for subclasses to implement. The processCouponPrice method is used to calculate the discount of goods and the processDeliveryPrice method is used to calculate the freight.

With abstract classes, the implementation of the three subclasses is simple.

  • NormalUserCart for ordinary users, 0 off and 10% shipping

  • VipUserCart, the shopping cart for VIP users, inherits NormalUserCart directly by modifying the multiple buy discount policy

  • Internal user shopping cart InternalUserCart is the simplest, directly set 0 freight, 0 discount

An implementation diagram of the abstract class and three subclasses

2.4 Refactoring factory mode – eliminates multiple Ifs

Since all three shopping carts are called XXXUserCart, we can concatenate UserCart with user type strings to form the name of the shopping cart Bean, and then use IoC container to get AbstractCart directly from the Bean name and call its process method to achieve universality.

This is the factory pattern, implemented with the Spring container:

If you have a new user type and user logic, just add an XXXUserCart class that extends AbstractCart and implements special discount and shipping logic.

The factory + template method pattern eliminates duplicate code and avoids modifying existing code. This is the open and closed principle of design pattern: closed for modification, open for extension.

3 comments + reflection eliminates duplicate code

3.1 requirements

The bank provides apis for serializing parameters without using JSON, requiring us to string them together to form a large string.

  • Form all the parameters into fixed-length data in the order of the API documentation provided by the bank, and then concatenate them together as the entire string
  • Because each parameter has a fixed length, it needs to be filled if the length is not reached:
    • If the length of a string parameter is shorter than the length of the string, the string content should be left
    • The length of a numeric parameter is left to 0, that is, the actual number is to the right
    • The representation of the currency type requires the amount to be rounded down by 2 bits to the minute and left populated as a number type.

MD5 operation for all parameters as signature (for easy understanding, the Demo does not involve salt processing).

For example, the definition of creating user methods and payment methods looks like this:

3.2 Rookie Implementation

Implement fill, add signature, request call directly according to the interface definition:

public class BankService {

    // Create a user
    public static String createUser(String name, String identity, String mobile, int age) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        // The string is left to the left, with any extra padding _
        stringBuilder.append(String.format("%-10s", name).replace(' '.'_'));
        stringBuilder.append(String.format("%-18s", identity).replace(' '.'_'));
        // The numbers are to the right, and any extra space is filled with zeros
        stringBuilder.append(String.format("%05d", age));
        // String to the left
        stringBuilder.append(String.format("%-11s", mobile).replace(' '.'_'));
        / / the MD5 signatures
        stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
        return Request.Post("http://localhost:45678/reflection/bank/createUser")
                .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON)
                .execute().returnContent().asString();
    }
    
    / / pay
    public static String pay(long userId, BigDecimal amount) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        // The number is to the right
        stringBuilder.append(String.format("%020d", userId));
        // The amount is rounded down 2 places to the minute, in minute units, as a number to the right, with the extra space filled with zeros
        stringBuilder.append(String.format("%010d", amount.setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));
        / / the MD5 signatures
        stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
        return Request.Post("http://localhost:45678/reflection/bank/pay") .bodyString(stringBuilder.toString(), ContentType.APPLICATION_JSON) .execute().returnContent().asString(); }}Copy the code

This code has a finer granularity of repetition:

  • There is duplication of processing logic for the three standard data types
  • The logic of string concatenation, checkmarking, and making requests in the process is repeated in all methods
  • The input parameter types and order of the actual method may not be consistent with the interface requirements, which may cause errors
  • The code level is hard coded for each parameter and cannot be checked clearly. If the parameters reach dozens or hundreds, the probability of error is very high.

3.3 Refactoring Hacks annotation & Reflection

All logic for bank requests is implemented using one set of code and there is no duplication.

To achieve the separation of interface logic and logical implementation, first define all interface parameters as POJO classes.

  • Parameters to create the user API
@Data
public class CreateUserAPI {
    private String name;
    private String identity;
    private String mobile;
    private int age;
}
Copy the code

With interface parameter definitions, you can add some metadata to the interface and all the parameters through custom annotations.

  • The following annotation BankAPI defines an interface API, including the interface URL and interface description

Define a custom annotation@BankAPIField, describing each field specification of the interface, including the order, type, and length of the parameters:

  • defineCreateUserAPIClass description Creates information about the user interface, adding @bankAPI annotations to the interface to supplement metadata such as the URL and description of the interface. Add metadata about the order, type, and length of arguments by annotating @bankapifield for each field:

  • Similarly, the PayAPI class

The AbstractAPI class that these two classes inherit is an empty implementation because the interface in this case has no common data.

With these classes, you can check with the API checklist in a matter of seconds. If our core translation process (the process of serializing annotations and interface apis into the requested string) is fine, as long as the annotations are consistent with the table, the API request translation should be fine.

The description of API parameters is implemented through annotations. See how reflection works with annotations to achieve dynamic interface parameter assembly:

private static String remoteCall(AbstractAPI api) throws IOException {
    // Get the BankAPI annotation from the class, then get its URL property, then make a remote call
    BankAPI bankAPI = api.getClass().getAnnotation(BankAPI.class);
    bankAPI.url();
    StringBuilder stringBuilder = new StringBuilder();
    // Use the stream quick implementation to get all the fields in the class annotated with BankAPIField, sort the fields by the Order property, and then set the private field reflection accessible.
    Arrays.stream(api.getClass().getDeclaredFields()) // Get all the fields
	    	// Find the fields marked with annotations
            .filter(field -> field.isAnnotationPresent(BankAPIField.class))
            // Sort the fields according to the order in the annotation
            .sorted(Comparator.comparingInt(a -> a.getAnnotation(BankAPIField.class).order()))
            .peek(field -> field.setAccessible(true)) // Set access to private fields
            .forEach(field -> {
            	// We implement reflection to get the value of the annotation, and then format it according to three criteria based on the parameter type obtained by BankAPIField, centralizing the formatting logic for all parameters in this one place
                // Get comments
                BankAPIField bankAPIField = field.getAnnotation(BankAPIField.class);
                Object value = "";
                try {
                    // reflection gets field values
                    value = field.get(api);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                // Format the string with the correct padding according to the field type
                switch (bankAPIField.type()) {
                    case "S": {
                        stringBuilder.append(String.format("% -" + bankAPIField.length() + "s", value.toString()).replace(' '.'_'));
                        break;
                    }
                    case "N": {
                        stringBuilder.append(String.format("%" + bankAPIField.length() + "s", value.toString()).replace(' '.'0'));
                        break;
                    }
                    case "M": {
                        if(! (valueinstanceof BigDecimal))
                            throw new RuntimeException(String.format("{} of {} must be BigDecimal", api, field));
                        stringBuilder.append(String.format("% 0" + bankAPIField.length() + "d", ((BigDecimal) value).setScale(2, RoundingMode.DOWN).multiply(new BigDecimal("100")).longValue()));
                        break;
                    }
                    default:
                        break; }});// Implement parameter checkup and request invocation
    Append (digestutils.md2hex (stringBuilder.toString())));
    String param = stringBuilder.toString();
    long begin = System.currentTimeMillis();
    / / send the request
    String result = Request.Post("http://localhost:45678/reflection" + bankAPI.url())
            .bodyString(param, ContentType.APPLICATION_JSON)
            .execute().returnContent().asString();
    log.info("Call banking API {} URL :{} Parameter :{} Time :{}ms", bankAPI.desc(), bankAPI.url(), param, System.currentTimeMillis() - begin);
    return result;
}
Copy the code

All the core logic for sorting parameters, populating parameters, checking parameters, and invoking requests, all converge in one placeremoteCall. With this method, the implementation of each interface in BankService is very simple, just assembling the parameters and calling remoteCall.

Any generic process involving class structure can follow this pattern to reduce duplicate code.

  • Reflection allows us to treat class members with fixed logic when the class structure is unknown
  • Annotations give us the ability to supplement metadata for these members, allowing us to get more of the data we care about externally as we implement common logic with reflection

4 Attribute Copy

For a three-tier architecture system, each layer will have its own POJO entity due to the decoupling between the layers and the different data requirements of each layer. Writing the assignment code between these entities by hand is error prone. For complex business systems, it is not unusual for entities to have dozens or even hundreds of attributes. ComplicatedOrderDTO, for example, describes dozens of attributes in an order. If you converted to a similar DO, copied most of the fields, and then put the data into the library, you would have to DO a lot of attribute mapping assignments. Like this, is the code so thick that it makes your head spin?

ComplicatedOrderDTO orderDTO = new ComplicatedOrderDTO();
ComplicatedOrderDO orderDO = new ComplicatedOrderDO();
orderDO.setAcceptDate(orderDTO.getAcceptDate());
orderDO.setAddress(orderDTO.getAddress());
orderDO.setAddressId(orderDTO.getAddressId());
orderDO.setCancelable(orderDTO.isCancelable());
orderDO.setCommentable(orderDTO.isComplainable()); // Attribute error
orderDO.setComplainable(orderDTO.isCommentable()); // Attribute error
orderDO.setCancelable(orderDTO.isCancelable());
orderDO.setCouponAmount(orderDTO.getCouponAmount());
orderDO.setCouponId(orderDTO.getCouponId());
orderDO.setCreateDate(orderDTO.getCreateDate());
orderDO.setDirectCancelable(orderDTO.isDirectCancelable());
orderDO.setDeliverDate(orderDTO.getDeliverDate());
orderDO.setDeliverGroup(orderDTO.getDeliverGroup());
orderDO.setDeliverGroupOrderStatus(orderDTO.getDeliverGroupOrderStatus());
orderDO.setDeliverMethod(orderDTO.getDeliverMethod());
orderDO.setDeliverPrice(orderDTO.getDeliverPrice());
orderDO.setDeliveryManId(orderDTO.getDeliveryManId());
orderDO.setDeliveryManMobile(orderDO.getDeliveryManMobile()); // Object error
orderDO.setDeliveryManName(orderDTO.getDeliveryManName());
orderDO.setDistance(orderDTO.getDistance());
orderDO.setExpectDate(orderDTO.getExpectDate());
orderDO.setFirstDeal(orderDTO.isFirstDeal());
orderDO.setHasPaid(orderDTO.isHasPaid());
orderDO.setHeadPic(orderDTO.getHeadPic());
orderDO.setLongitude(orderDTO.getLongitude());
orderDO.setLatitude(orderDTO.getLongitude()); // Attribute assignment error
orderDO.setMerchantAddress(orderDTO.getMerchantAddress());
orderDO.setMerchantHeadPic(orderDTO.getMerchantHeadPic());
orderDO.setMerchantId(orderDTO.getMerchantId());
orderDO.setMerchantAddress(orderDTO.getMerchantAddress());
orderDO.setMerchantName(orderDTO.getMerchantName());
orderDO.setMerchantPhone(orderDTO.getMerchantPhone());
orderDO.setOrderNo(orderDTO.getOrderNo());
orderDO.setOutDate(orderDTO.getOutDate());
orderDO.setPayable(orderDTO.isPayable());
orderDO.setPaymentAmount(orderDTO.getPaymentAmount());
orderDO.setPaymentDate(orderDTO.getPaymentDate());
orderDO.setPaymentMethod(orderDTO.getPaymentMethod());
orderDO.setPaymentTimeLimit(orderDTO.getPaymentTimeLimit());
orderDO.setPhone(orderDTO.getPhone());
orderDO.setRefundable(orderDTO.isRefundable());
orderDO.setRemark(orderDTO.getRemark());
orderDO.setStatus(orderDTO.getStatus());
orderDO.setTotalQuantity(orderDTO.getTotalQuantity());
orderDO.setUpdateTime(orderDTO.getUpdateTime());
orderDO.setName(orderDTO.getName());
orderDO.setUid(orderDTO.getUid());
Copy the code

If the original DTO has 100 fields, we need to copy 90 fields into DO and leave 10 fields unassigned. How should we check the correctness?

  • Count? Even if you count 90 lines of code, it may not be correct because the attribute may be assigned repeatedly
  • Sometimes fields with similar names, like complainable and Commentable, are easy to reverse
  • Assign the same source field repeatedly to both target fields
  • The value of DTO is assigned to DO, but the value of DTO is assigned to DO

Use a similarBeanUtilsThis Mapping tool is used to convert beans,copyPropertiesThe method also allows us to provide attributes that need to be ignored:

5 concludes

Too much repetitive code will eventually make a mistake.

  • There are multiple parallel classes that implement similar code logic

Consider extracting the same logic to be implemented in the parent class and leaving the differential logic to be implemented in the child class through abstract methods. Use similar template methods to template the same processes and logic, preserving differences while avoiding code duplication as much as possible. In the meantime, you can use Spring’s IoC feature to inject appropriate subclasses to avoid a lot of if… The else code.

  • Implement the same data processing algorithm repeatedly in a hard-coded manner

Consider converting rules into custom annotations that describe classes or fields or methods as metadata, and then separating rule parameters from rule definitions by dynamically reading those metadata or fields or calling methods through reflection. That is, the changing parts of the rule’s parameters are put into the annotations, and the definition of the rule is handled uniformly.

  • Manual assignment of a large number of fields during DO, DTO, and VO conversions is common in business code, and it is very, very error-prone to encounter complex types with hundreds of attributes

Instead of doing this manually, consider using a Bean mapping tool. In addition, you can consider unit tests to verify assignment correctness for all fields.

Code duplication is an important indicator of the quality of a project, and if a project has almost no duplicate code, then its internal abstraction must be very good. When refactoring, the first task is to eliminate duplication.

reference

  • “Refactoring”
  • Three tips for code duplication
  • Blog.csdn.net/qq_32447301…