This article is excerpted from This is How Design Patterns should Be Learned

Chain writing of builder mode

Taking the construction of a course as an example, a complete course consists of PPT courseware, playback videos, class notes and homework. However, the setting order of these contents can be adjusted at will. Let’s use the Builder mode to understand. Start by creating a product class, Course.


@Data
public class Course {

    private String name;
    private String ppt;
    private String video;
    private String note;

    private String homework;

    @Override
    public String toString(a) {
        return "CourseBuilder{" +
                "name='" + name + '\' ' +
                ", ppt='" + ppt + '\' ' +
                ", video='" + video + '\' ' +
                ", note='" + note + '\' ' +
                ", homework='" + homework + '\' ' +
                '} '; }}Copy the code

The Builder class CourseBuilder was then created to encapsulate the complex creation process, with the steps determined by the user.


public class CourseBuilder {

    private Course course = new Course();

    public CourseBuilder addName(String name){
        course.setName(name);
        return this;
    }

    public CourseBuilder addPpt(String ppt){
        course.setPpt(ppt);
        return this;
    }

    public CourseBuilder addVideo(String video){
        course.setVideo(video);
        return this;
    }

    public CourseBuilder addNote(String note){
        course.setNote(note);
        return this;
    }

    public CourseBuilder addHomework(String homework){
        course.setHomework(homework);
        return this;
    }

    public Course builder(a){
        returncourse; }}Copy the code

Finally write the client test code.



    public static void main(String[] args) {
        CourseBuilder builder = new CourseBuilder()
                    .addName("Design Patterns")
                    .addPPT(【PPT课件】)
                    .addVideo("[Replay video]")
                    .addNote([Lecture notes])
                    .addHomework("[Homework]");

        System.out.println(builder.build());
}

Copy the code

Does this look familiar? We will see this later when we analyze the application of the Builder pattern in framework source code. Take a look at the changes to the class diagram, as shown below.

Implement the Builder pattern using static inner classes

In fact, in normal coding, we tend to ignore the complexity of objects in favor of using the factory pattern to create objects rather than the builder pattern. Because both factory mode and Builder mode create a product object, factory mode is more often used because it has a cleaner and more direct structure (no Builder or Director). In general, we prefer to use static inner classes to implement the Builder pattern, that is, a product class automatically has a concrete Builder within it, which is responsible for the assembly and creation of the product, without the need for Builder and Director, so that the product representation and creation are more closely related, and the structure is more compact. It also makes the form of builder mode more concise. If the builder pattern is implemented in static inner class form, the previous example can be rewritten as follows.



@Data
public class Course {
    private String name;
    private String ppt;
    private String video;
    private String note;

    private String homework;

    @Override
    public String toString(a) {
        return "Course{" +
                "name='" + name + '\' ' +
                ", ppt='" + ppt + '\' ' +
                ", video='" + video + '\' ' +
                ", note='" + note + '\' ' +
                ", homework='" + homework + '\' ' +
                '} ';
    }

    public static class Builder {

        private Course course = new Course();

        public Builder addName(String name){
            course.setName(name);
            return this;
        }

        public Builder addPpt(String ppt){
            course.setPpt(ppt);
            return this;
        }

        public Builder addVideo(String video){
            course.setVideo(video);
            return this;
        }

        public Builder addNote(String note){
            course.setNote(note);
            return this;
        }

        public Builder addHomework(String homework){
            course.setHomework(homework);
            return this;
        }

        public Course builder(a){
            returncourse; }}}Copy the code

The client test code is as follows.



    public static void main(String[] args) {
         Course course = new Course.Builder()
                .addName("Design Patterns")
                .addPpt(【PPT课件】)
                .addVideo("[Recording and playing video]")
                 .builder();

        System.out.println(course);
    }
		
Copy the code

The code also looks cleaner and doesn’t feel like an extra class.

Build SQL statements dynamically using builder pattern

Let’s take a look at a practical example that references the SQL construction pattern of the open source framework JPA. When constructing SQL query conditions, we need to concatenate SQL strings according to different conditions. If the query conditions are complex, the process of SQL concatenation also becomes very complex, which brings great difficulties to code maintenance. Therefore, we use the builder class QueryRuleSqlBuilder to encapsulate the complex SQL construction process, and use QueryRule objects to store the conditions of SQL queries. Finally, according to the query conditions, SQL statements are automatically generated. Start by creating the QueryRule class as follows.


import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/** * QueryRule@author Tom
 */
public final class QueryRule implements Serializable
{
	private static final long serialVersionUID = 1L;
	public static final int ASC_ORDER = 101;
	public static final int DESC_ORDER = 102;
	public static final int LIKE = 1;
	public static final int IN = 2;
	public static final int NOTIN = 3;
	public static final int BETWEEN = 4;
	public static final int EQ = 5;
	public static final int NOTEQ = 6;
	public static final int GT = 7;
	public static final int GE = 8;
	public static final int LT = 9;
	public static final int LE = 10;
	public static final int ISNULL = 11;
	public static final int ISNOTNULL = 12;
	public static final int ISEMPTY = 13;
	public static final int ISNOTEMPTY = 14;
	public static final int AND = 201;
	public static final int OR = 202;
	private List<Rule> ruleList = new ArrayList<Rule>();
	private List<QueryRule> queryRuleList = new ArrayList<QueryRule>();
	private String propertyName;

	private QueryRule(a) {}

	private QueryRule(String propertyName) {
		this.propertyName = propertyName;
	}

	public static QueryRule getInstance(a) {
		return new QueryRule();
	}
	
	/** * Add ascending rule *@param propertyName
	 * @return* /
	public QueryRule addAscOrder(String propertyName) {
		this.ruleList.add(new Rule(ASC_ORDER, propertyName));
		return this;
	}

	/** * Add descending rule *@param propertyName
	 * @return* /
	public QueryRule addDescOrder(String propertyName) {
		this.ruleList.add(new Rule(DESC_ORDER, propertyName));
		return this;
	}

	public QueryRule andIsNull(String propertyName) {
		this.ruleList.add(new Rule(ISNULL, propertyName).setAndOr(AND));
		return this;
	}

	public QueryRule andIsNotNull(String propertyName) {
		this.ruleList.add(new Rule(ISNOTNULL, propertyName).setAndOr(AND));
		return this;
	}

	public QueryRule andIsEmpty(String propertyName) {
		this.ruleList.add(new Rule(ISEMPTY, propertyName).setAndOr(AND));
		return this;
	}

	public QueryRule andIsNotEmpty(String propertyName) {
		this.ruleList.add(new Rule(ISNOTEMPTY, propertyName).setAndOr(AND));
		return this;
	}

	public QueryRule andLike(String propertyName, Object value) {
		this.ruleList.add(new Rule(LIKE, propertyName, new Object[] { value }).setAndOr(AND));
		return this;
	}

	public QueryRule andEqual(String propertyName, Object value) {
		this.ruleList.add(new Rule(EQ, propertyName, new Object[] { value }).setAndOr(AND));
		return this;
	}

	public QueryRule andBetween(String propertyName, Object... values) {
		this.ruleList.add(new Rule(BETWEEN, propertyName, values).setAndOr(AND));
		return this;
	}

	public QueryRule andIn(String propertyName, List<Object> values) {
		this.ruleList.add(new Rule(IN, propertyName, new Object[] { values }).setAndOr(AND));
		return this;
	}

	public QueryRule andIn(String propertyName, Object... values) {
		this.ruleList.add(new Rule(IN, propertyName, values).setAndOr(AND));
		return this;
	}
	
	public QueryRule andNotIn(String propertyName, List<Object> values) {
		this.ruleList.add(new Rule(NOTIN, 
									propertyName, 
									new Object[] { values }).setAndOr(AND));
		return this;
	}

	// Omit some code here
	

	public List<Rule> getRuleList(a) {
		return this.ruleList;
	}

	public List<QueryRule> getQueryRuleList(a) {
		return this.queryRuleList;
	}

	public String getPropertyName(a) {
		return this.propertyName;
	}

	protected class Rule implements Serializable {
		private static final long serialVersionUID = 1L;
		private int type;	// The type of rule
		private String property_name;
		private Object[] values;
		private int andOr = AND;

		public Rule(int paramInt, String paramString) {
			this.property_name = paramString;
			this.type = paramInt;
		}

		public Rule(int paramInt, String paramString,
				Object[] paramArrayOfObject) {
			this.property_name = paramString;
			this.values = paramArrayOfObject;
			this.type = paramInt;
		}
		
		public Rule setAndOr(int andOr){
			this.andOr = andOr;
			return this;
		}
		
		public int getAndOr(a){
			return this.andOr;
		}

		public Object[] getValues() {
			return this.values;
		}

		public int getType(a) {
			return this.type;
		}

		public String getPropertyName(a) {
			return this.property_name; }}}Copy the code

Then create the QueryRuleSqlBuilder class.


package com.tom.vip.pattern.builder.sql;


/** * Automatically builds SQL statements based on QueryRule *@author Tom
 *
 */
public class QueryRuleSqlBuilder {
	private int CURR_INDEX = 0; // Record the location of the parameter
	private List<String> properties; // Save the list of column names
	private List<Object> values; // Save the list of parameter values
	private List<Order> orders; // Save the collation list
	
	private String whereSql = ""; 
	private String orderSql = "";
	private Object [] valueArr = new Object[]{};
	private Map<Object,Object> valueMap = new HashMap<Object,Object>();
	
	/** * get query condition *@return* /
	private String getWhereSql(a){
		return this.whereSql;
	}
	
	/** * get the sort condition *@return* /
	private String getOrderSql(a){
		return this.orderSql;
	}
	
	/** * get the list of parameter values *@return* /
	public Object [] getValues(){
		return this.valueArr;
	}
	
	/** * get the argument list *@return* /
	private Map<Object,Object> getValueMap(a){
		return this.valueMap;
	}
	
	/** * create SQL constructor *@param queryRule
	 */
	public QueryRuleSqlBuilder(QueryRule queryRule) {
		CURR_INDEX = 0;
		properties = new ArrayList<String>();
		values = new ArrayList<Object>();
		orders = new ArrayList<Order>();
		for (QueryRule.Rule rule : queryRule.getRuleList()) {
			switch (rule.getType()) {
			case QueryRule.BETWEEN:
				processBetween(rule);
				break;
			case QueryRule.EQ:
				processEqual(rule);
				break;
			case QueryRule.LIKE:
				processLike(rule);
				break;
			case QueryRule.NOTEQ:
				processNotEqual(rule);
				break;
			case QueryRule.GT:
				processGreaterThen(rule);
				break;
			case QueryRule.GE:
				processGreaterEqual(rule);
				break;
			case QueryRule.LT:
				processLessThen(rule);
				break;
			case QueryRule.LE:
				processLessEqual(rule);
				break;
			case QueryRule.IN:
				processIN(rule);
				break;
			case QueryRule.NOTIN:
				processNotIN(rule);
				break;
			case QueryRule.ISNULL:
				processIsNull(rule);
				break;
			case QueryRule.ISNOTNULL:
				processIsNotNull(rule);
				break;
			case QueryRule.ISEMPTY:
				processIsEmpty(rule);
				break;
			case QueryRule.ISNOTEMPTY:
				processIsNotEmpty(rule);
				break;
			case QueryRule.ASC_ORDER:
				processOrder(rule);
				break;
			case QueryRule.DESC_ORDER:
				processOrder(rule);
				break;
			default:
				throw new IllegalArgumentException("type"+rule.getType()+"not supported."); }}// Assemble the WHERE statement
		appendWhereSql();
		// assemble sort statements
		appendOrderSql();
		// Assemble parameter values
		appendValues();
	}
	
	/** ** order **@param sql
	 * @return* /
	private String removeOrders(String sql) {
		Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(sql);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "");
		}
		m.appendTail(sb);
		return sb.toString();
	}
	
	/** * drop select **@param sql
	 * @return* /
	private String removeSelect(String sql) {
		if(sql.toLowerCase().matches("from\\s+")) {int beginPos = sql.toLowerCase().indexOf("from");
			return sql.substring(beginPos);
		}else{
			returnsql; }}/** * handle like *@param rule
	 */
	private  void processLike(QueryRule.Rule rule) {
		if (ArrayUtils.isEmpty(rule.getValues())) {
			return;
		}
		Object obj = rule.getValues()[0];

		if(obj ! =null) {
			String value = obj.toString();
			if(! StringUtils.isEmpty(value)) { value = value.replace(The '*'.The '%');
				obj = value;
			}
		}
		add(rule.getAndOr(),rule.getPropertyName(),"like"."%"+rule.getValues()[0] +"%");
	}

	/** * handle between *@param rule
	 */
	private  void processBetween(QueryRule.Rule rule) {
		if ((ArrayUtils.isEmpty(rule.getValues()))
				|| (rule.getValues().length < 2)) {
			return;
		}
		add(rule.getAndOr(),rule.getPropertyName(),""."between",rule.getValues()[0]."and");
		add(0."".""."",rule.getValues()[1]."");
	}
	

// Omit some code here
	
	
	/** * Join SQL query rule queue *@paramAndOr and or *@paramThe key column name *@paramSplit The interval between column names and values *@paramThe value value * /
	private  void add(int andOr,String key,String split ,Object value){
		add(andOr,key,split,"",value,"");
	}
	
	/** * Join SQL query rule queue *@paramAndOr and or *@paramThe key column name *@paramSplit The interval between column names and values *@paramPrefix Indicates the prefix *@paramThe value value *@paramSuffix Indicates the suffix */
	private  void add(int andOr,String key,String split ,String prefix,Object value,String  	suffix){
		String andOrStr = (0 == andOr ? "" :(QueryRule.AND == andOr ? " and " : " or "));  
		properties.add(CURR_INDEX, 
		 andOrStr + key + "" + split + prefix + (null! = value ?"? " : "") + suffix);
		if(null != value){
			values.add(CURR_INDEX,value);
			CURR_INDEX ++;
		}
	}
	
	
	/** * assemble where statement */
	private void appendWhereSql(a){
		StringBuffer whereSql = new StringBuffer();
		for (String p : properties) {
			whereSql.append(p);
		}
		this.whereSql = removeSelect(removeOrders(whereSql.toString()));
	}
	
	/** * assemble sort statements */
	private void appendOrderSql(a){
		StringBuffer orderSql = new StringBuffer();
		for (int i = 0 ; i < orders.size(); i ++) {
			if(i > 0 && i < orders.size()){
				orderSql.append(",");
			}
			orderSql.append(orders.get(i).toString());
		}
		this.orderSql = removeSelect(removeOrders(orderSql.toString()));
	}
	
	/** * Assembler parameter value */
	private void appendValues(a){
		Object [] val = new Object[values.size()];
		for (int i = 0; i < values.size(); i ++) {
			val[i] = values.get(i);
			valueMap.put(i, values.get(i));
		}
		this.valueArr = val;
	}

	public String builder(String tableName){
		String ws = removeFirstAnd(this.getWhereSql());
		String whereSql = ("".equals(ws) ? ws : (" where " + ws));
		String sql = "select * from " + tableName + whereSql;
		Object [] values = this.getValues();
		String orderSql = this.getOrderSql();
		orderSql = (StringUtils.isEmpty(orderSql) ? "" : (" order by " + orderSql));
		sql += orderSql;
		return sql;
	}


	private String removeFirstAnd(String sql){
		if(StringUtils.isEmpty(sql)){returnsql; }return sql.trim().toLowerCase().replaceAll("^\\s*and"."") + ""; }}Copy the code

Next, create the Order class.


/** * SQL sort component *@author Tom
 */
public class Order {
	private boolean ascending; // In ascending or descending order
	private String propertyName; // Which field is in ascending order and which field is in descending order
	
	public String toString(a) {
		return propertyName + ' ' + (ascending ? "asc" : "desc");
	}

	/** * Constructor for Order. */
	protected Order(String propertyName, boolean ascending) {
		this.propertyName = propertyName;
		this.ascending = ascending;
	}

	/**
	 * Ascending order
	 *
	 * @param propertyName
	 * @return Order
	 */
	public static Order asc(String propertyName) {
		return new Order(propertyName, true);
	}

	/**
	 * Descending order
	 *
	 * @param propertyName
	 * @return Order
	 */
	public static Order desc(String propertyName) {
		return new Order(propertyName, false); }}Copy the code

Finally write the client test code.


public static void main(String[] args) {
        QueryRule queryRule = QueryRule.getInstance();
        queryRule.addAscOrder("age");
        queryRule.andEqual("addr"."Changsha");
        queryRule.andLike("name"."Tom");
        QueryRuleSqlBuilder builder = new QueryRuleSqlBuilder(queryRule);

        System.out.println(builder.builder("t_member"));

        System.out.println("Params: " + Arrays.toString(builder.getValues()));


}

Copy the code

This way, the client code is clear, and the result is shown below.

Pay attention to “Tom play architecture” reply to “design pattern” can obtain the complete source code.

Tom play architecture: 30 real cases of design patterns (attached source code), the challenge of annual salary 60W is not a dream

This article is “Tom play structure” original, reproduced please indicate the source. Technology is to share, I share my happiness! If this article is helpful to you, welcome to follow and like; If you have any suggestions can also leave a comment or private letter, your support is my motivation to adhere to the creation. Pay attention to “Tom bomb architecture” for more technical dry goods!