模板模式介绍
模板模式的核心设计思路是通过在抽象类中定义抽象方法的执行顺序,并将抽象方法设定为只有子类实现,但不设计独立访问的方法。简单的说就是安排的明明白白。
模板模式模拟爬虫
模板模式的核心在于又抽象类定义抽象方法并执行策略,也就是父类规定一系列的执行标准,这些标准串联成一整套的业务流程。
案例模拟爬取各类电商商品的商品信息,生成营销海报,整个爬取过程分为三步:模拟登陆、爬取信息和生成海报。因为有些商品只有登陆后才可以爬取,并且登录后可以看到一些特定的价格,不同的电商网站爬取的方式不同,解析方式也不同,因此可以作为每一个类中特定实现。生成海报的步骤基本一样的,所以这三步可以使用模板模式设定,并有具体的场景做子类实现。
代码结构比较简单,核心就是:模拟登陆、爬取信息、生成海报
定义执行顺序抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.bestrookie.design; import java.io.IOException; import java.util.HashMap; import java.util.Map; public abstract class NetMall { String uId; String uPwd; public NetMall(String uId,String uPwd){ this.uId = uId; this.uPwd = uPwd; } public String generateGoodsPoster(String skuUrl) throws IOException { if(!logIn(uId,uPwd)){ return null; } Map<String, String> reptile = reptile(skuUrl); return createBase64(reptile); } protected abstract Boolean logIn(String uId,String uPwd); protected abstract Map<String, String> reptile(String skuUrl) throws IOException; protected abstract String createBase64(Map<String, String> goodsInfo); }
|
这个类是模板模式的灵魂。定义可被外部访问的方法。定义可被外部访问的方法generateGoodsPoster
,用于生成商品推广海报。generateGoodsPoster
在方法中定义抽象方法的三步执行顺序。提供三个具体的抽象方法,让外部继承方实现:模拟登陆、爬取信息和生成海报(createBase64)
模拟爬取京东商品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.bestrookie.design.group; import com.alibaba.fastjson.JSON; import com.bestrookie.design.HttpClient; import com.bestrookie.design.NetMall; import lombok.SneakyThrows; import sun.misc.BASE64Encoder; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JDNetMall extends NetMall { public JDNetMall(String uId, String uPwd) { super(uId, uPwd); } @Override protected Boolean logIn(String uId, String uPwd) { System.out.println("京东登录:"+ uId); return true; } @SneakyThrows @Override protected Map<String, String> reptile(String skuUrl) { String str = HttpClient.doGet(skuUrl); Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)"); Matcher m9 = p9.matcher(str); Map<String, String> map = new ConcurrentHashMap<>(); if (m9.find()){ map.put("name",m9.group()); } map.put("price","5999.00"); System.out.println("京东商城解析:"+map.get("name")+":"+map.get("price")+":"+skuUrl); return map; } @Override protected String createBase64(Map<String, String> goodsInfo) { BASE64Encoder encoder = new BASE64Encoder(); System.out.println("生成海报"); return encoder.encode(JSON.toJSONString(goodsInfo).getBytes(StandardCharsets.UTF_8)); } }
|
模拟爬取淘宝商品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.bestrookie.design.group; import com.alibaba.fastjson.JSON; import com.bestrookie.design.HttpClient; import com.bestrookie.design.NetMall; import sun.misc.BASE64Encoder; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TaoBaoNetMall extends NetMall { public TaoBaoNetMall(String uId, String uPwd) { super(uId, uPwd); } @Override protected Boolean logIn(String uId, String uPwd) { System.out.println("模拟淘宝登录:"+uId); return true; } @Override protected Map<String, String> reptile(String skuUrl) throws IOException { String str = HttpClient.doGet(skuUrl); Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)"); Matcher m9 = p9.matcher(str); Map<String, String> map = new ConcurrentHashMap<>(); if (m9.find()){ map.put("name",m9.group()); } map.put("price","4799.00"); System.out.println("淘宝商城解析:"+map.get("name")+":"+map.get("price")+":"+skuUrl); return map; } @Override protected String createBase64(Map<String, String> goodsInfo) { BASE64Encoder encoder = new BASE64Encoder(); System.out.println("生成海报"); return encoder.encode(JSON.toJSONString(goodsInfo).getBytes(StandardCharsets.UTF_8)); } }
|
模拟爬取当当商品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| package com.bestrookie.design.group; import com.alibaba.fastjson.JSON; import com.bestrookie.design.HttpClient; import com.bestrookie.design.NetMall; import sun.misc.BASE64Encoder; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class DangDangNetMall extends NetMall { public DangDangNetMall(String uId, String uPwd) { super(uId, uPwd); } @Override protected Boolean logIn(String uId, String uPwd) { System.out.println("当当登录:"+uId); return true; } @Override protected Map<String, String> reptile(String skuUrl) throws IOException { String str = HttpClient.doGet(skuUrl); Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)"); Matcher m9 = p9.matcher(str); Map<String, String> map = new ConcurrentHashMap<>(); if (m9.find()){ map.put("name",m9.group()); } map.put("price","3799.00"); System.out.println("当当商城解析:"+map.get("name")+":"+map.get("price")+":"+skuUrl); return map; } @Override protected String createBase64(Map<String, String> goodsInfo) { BASE64Encoder encoder = new BASE64Encoder(); System.out.println("生成海报"); return encoder.encode(JSON.toJSONString(goodsInfo).getBytes(StandardCharsets.UTF_8)); } }
|
测试验证
1 2 3 4 5 6 7 8 9 10
| package com.bestrookie.design; import com.bestrookie.design.group.JDNetMall; import java.io.IOException; public class ApiTest { public static void main(String[] args) throws IOException { NetMall netMall = new JDNetMall("10001","******"); String base = netMall.generateGoodsPoster("https://item.jd.com/10024794770134.html"); System.out.println(base); } }
|
总结
可以看到模板模式在定义统一结构也就是执行标准方面非常方便,能很好地做到后续的实现者不用关心调用逻辑,按照统一方式执行即可。类的继承者也只需要关心具体的业务逻辑实现即可。另外,模板模式也是为了解决子类通用方法,放到父类中优化设计。让每一个子类只做子类需要完成的内容,而不需要关心其他逻辑。再提取公用代码,行为由父类管理,扩展可变部分,也就非常有利于拓展和迭代了。