Requirement scenarios:

In a management system, there are multiple report export functions. Usually, the back-end developer handles all the export functions with one interface, judging which report export function is based on the type delivered by the front end. However, as the demand for exporting reports increases, the if-else or switch code block on the back end will become longer and longer. There are as many as 13 types of reports to export in this article, which are distinguished by the types passed by the front end.

This article discusses how to optimize the export function

Export the function condition parameter class:

Public class ExportParam{//1 for ExportParam, 2 for ExportParam... private Integer type; // Other conditional parameters... }Copy the code

Public export interface:

@postmapping ("/lib/export") @auth (value = "admin_adminExport_export", Public void export(@requestBody ExportParam ExportParam) public void export(@requestBody ExportParam, HttpServletResponse response) throws Exception { CurrentUser currentUser = UserUtil.getCurrentUser(); exportService.export(exportParam,response,currentUser); }Copy the code

The export tool used in this example is Alibaba’s EasyExcel tool:

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>2.2.6</version>
		</dependency>
Copy the code

Data is mapped to Excel classes: Public interface: BaseExcelModel

public interface BaseExcelModel {
}
Copy the code

This interface is designed only for later polymorphic design. The specific table mapping class is as follows:

I believe that friends who have done the export function will naturally understand the role of this class, this class will correspond to the excel table header, data, etc. The data from the database query needs to be turned into a collection of this class

Export function implementation:

Export implementation class:

@override public void export(Integer type, HttpServletResponse Response) throws Exception {// The data to be exported, ExportList =null; exportList=null; String title=""; String title=""; // Export the mapping excle class, BaseExcelModel obj=null; ExportList =getXXX(); switch (type) {case 1: exportList=getXXX(); Title =" XXX info export "; obj=new OriganizationStructureExcelModel(); break; ExportList =getXXX(); exportList=getXXX(); Title =" XXX info export "; obj=new EnterpriseExcelModel(); break; ExportList =getXXX(); exportList=getXXX(); Title =" XXX info export "; obj=new EnterpriseTownshipExcelModel(); break; Default: break; } if(exportList.size()>0 && obj! = null) {/ / call tools export function ExportExcelUtil. WriteExcel (exportList response, the title, "first page", obj); }}Copy the code

From the pseudo code above just shows several export function, along with the increase in export function, the switch will also has been very long, and query the database data method getXXX (), are all written in the implementation class, also increase as function method are many, many this kind of lines of code, though the function can be implemented, But it’s actually not very good for reading code.

Above code shortcomings (personal opinion) :

  • All methods of querying the data are implemented in a single class that implements export functions. The data query should be written in its corresponding data query implementation class (xxxImpl) (single responsibility).
  • It is better to use an enumerated type for cast values in switch
  • We also need to deal with the name of the table and the type of the exported excelModel in case, and may need to deal with other information in each case if needed.
  • The more the export function, the longer the switch, in a class a switch is long, the code is ugly

Export function optimized first edition:

Define an export abstract superclass:

Public abstract class AbstractExport {public abstract void doExport(CurrentUser) abstract class AbstractExport {public Abstract void doExport(CurrentUser) currentUser, ExportParam exportParam, HttpServletResponse response) throws Exception; }Copy the code

Its implementation is shown in the figure:

As you can see from the figure above, we have separated out the export functions. Each export function is implemented separately, and the code is not trapped in the exportService class.

Each exported implementation class inherits the overridden method of the parent class:

@Component public class ExpertAssessExport extends AbstractExport { @Resource DockMapper dockMapper; @Autowired private RequirementServiceImpl requirementService; @Override public void doExport(CurrentUser currentUser, ExportParam exportParam, HttpServletResponse Response) throws Exception {// Query data List expertAssess = getExpertAssess(currentUser, exportParam); ExcelWriter (Response,expertAssess," expertAssess ", New ExpertAssessExcelModel()); // Exportutils. excelWriter(response,expertAssess," expertAssess ",new ExpertAssessExcelModel()); }}Copy the code

Add a new ExportUtils tool class:

In this utility class, we declare the public methods to get the export implementation object and perform the export. The table name, the export data, and the Excel data map object are all passed in as parameters by the export implementation class.

Look at this getOjb method:

Public Object getObj(Integer type){switch (type){case 1: industryParkExport; Case 2: / / patent library export return intellectualPropertyExport; Case 3: // Return enterpriseExport; Case 4: // Return expertTeamExport; }}Copy the code

This getObj() is responsible for returning the objects that implement the export function according to type

The original export method is changed to:

@Override public void export(ExportParam exportParam,HttpServletResponse response,CurrentUser currentUser) throws Exception { /** * 1. The export has been optimized to inherit AbstractExport and rewrite its doExport() method. The @Component annotation * 2 needs to be added to the subclass. ExportUtils class getObj, */ exporacTexPort obj = (AbstractExport) exportutils.getobj (exportParam.getType()); if(obj! =null){ obj.doExport(currentUser,exportParam,response); }}}Copy the code

In the above optimization, the implementation of each export function is separated. When an export function changes, it only needs to modify its rewriting method, but when the export function is added in this version, it needs to add an export function class and inherit the abstract method of the parent class. Also, a case block needs to be added to the ExportUtils class getObj() method. Even though the switch is getting longer and longer, the code in the case block is very simple and concise, and only needs to return the object of the implementation class.

Export features optimized second edition

The above code may be much simpler than the first version, but as the export function increases the switch block will become longer and longer, still feel that the code is not perfect. In fact, we can use Map instead of Switch block, the implementation idea is to store all the export function implementation object in a Map, key is type, value is the corresponding export implementation object. Map.get (type) is enough to fetch objects later, but the question is when to store these objects into a map?

And it’s not hard to imagine,

  1. Add the @Component annotation to our ExportUtils class
  2. Provide a member variable of type Map in the class to store the object
  3. Write a method to put an object into the map and add an @bean annotation to the method so that when the container scans the @Bean annotation, the method will be executed and the exported objects will be stored in the map

Modify the code as follows:

@component public class ExportUtils {// Export map,key is the exported type, Value Specifies the Object that implements the export function. Public ConcurrentHashMap<Integer,Object> map=new ConcurrentHashMap<>(); @bean public void inputObjToMap(){map.put(1,industryParkExport); / / patent library map is derived. The put (2, intellectualPropertyExport); Map. Put (3,enterpriseExport); / /... } public Object getObj(Integer type){ return map.get(type); }}Copy the code

The above code uses a data structure of type ConcurrentHashMap, where HashMap is used to achieve the desired effect. The above optimization does indeed discredit the Switch code block. New export function, only need to import the implementation class in the ExportUtils class, and then put a type and the corresponding object in the inputObjToMap() method, also no longer see a Switch code and a dozen cases of the situation.

After the above code optimization, there is only one problem: the exported types should be managed by enumeration classes. I believe you are familiar with enumeration classes and I don’t need to demonstrate them here.

The final reflection

Is there an interface for each export function required in such a requirement scenario? Or do you use the same export interface as described in this article, using parameters to distinguish export data types? How else can the above code be simplified? Or is this optimization necessary?