1. 模板模式介绍
模板模式的核心设计思路是通过在抽象类中定义抽象方法的执行顺序,并将抽象方法设定为只有子类实现,但不设计独立访问的方法。
只定义了执行顺序和基本策略,具体由开发者自己安排。
2. 案例场景模拟
在本案例中我们模拟爬虫各类电商商品,生成营销推广海报场景。
关于模板模式的核心点在于由抽象类定义抽象方法执行策略,也就是父类规定了一系列的执行标准,这些标准的串联成一整套业务流程。
这个场景中我们模拟爬虫爬取各类商家的商品信息,生成推广海报(海报中带有个人邀请码),赚取商品返利。而整个爬取过程分为:模拟登录、爬取信息、生成海报。另外:
- 因为有些商品只有登录后才可以爬取,并且登录可以看到一些特定的价格,这与未登录用户看到的价格不同。
- 不同的电商网站爬取方式不同,解析方式也不同,因此可以作为每一个实现类中的特定实现。
- 生成海报的步骤基本一样,但会有特定的商品来源标识。
所以这样三个步骤可以使用模板模式来设计,并由具体的场景做子类实现。
3. 模板模式搭建工程
模板模式的业务场景可能在平时不多,主要因为这个设计模式会在抽象类中定义逻辑行为的执行顺序。一般情况下,我们用的抽象类定义的逻辑行为都比较轻量级,只是提供一些基本方法公共调用和实现。
但如果遇到适合的场景使用这种设计模式也是非常方便的,因为它可以控制整套逻辑的执行顺序和统一的输入、输出,而对于实现方只需要关心好自己的业务逻辑即可。
而在我们这个场景中,只需要记住这三步的实现:模拟登录**、爬取信息、**生成海报。
工程结构
org.itstack.demo.design
----group
----DangdangNetMall.java
----JDNetMall.java
----TaobaoNetMall.java
----HttpClient.java
----NetMall.java
模板模式模型结构
- 代码结构比较简单,一个定义了抽象方法执行顺序的核心抽象类,以及三个模拟具体的实现(当当、京东、淘宝)的电商服务。
代码实现
定义了执行顺序的抽象类
/**
* 基础电商推广服务
* 1. 生成最优价商品海报
* 2. 海报含有推广邀请码
*/
public abstract class NetMall {
protected Logger logger = LoggerFactory.getLogger(NetMall.class);
String uId; // 用户ID
String uPwd; // 用户密码
public NetMall(String uId, String uPwd) {
this.uId = uId;
this.uPwd = uPwd;
}
/**
* 生成商品推广服务
*
* @param skuUrl 商品地址(当当、京东、淘宝)
* @return 海报图片base64位信息
*/
public String generateGoodsPoster(String skuUrl) {
if (!login(uId, uPwd)) return null; // 1. 验证登录
Map<String, String> reptile = reptile(skuUrl); // 2. 爬虫商品
return createBase64(reptile); // 3. 组装海报
}
// 模拟登录
protected abstract Boolean login(String uId, String uPwd);
// 爬虫提取商品信息
protected abstract Map<String, String> reptile(String skuUrl);
// 生成商品海报信息
protected abstract String createBase64(Map<String, String> goodsInfo);
}
- 这个抽象类是模板模式的灵魂。
- 定义可别外部访问的方法
generateGoodsPoster
,用于生成商品推广海报。 generateGoodsPoster
在方法中定义抽象方法的执行顺序。- 提供三个具体的抽象方法,让外部继承来实现:模拟登录(login)、爬取信息(reptile)、生成海报(createBase64)
模拟爬虫京东
public class JDNetMall extends NetMall {
public JDNetMall(String uId, String uPwd) {
super(uId, uPwd);
}
public Boolean login(String uId, String uPwd) {
logger.info("模拟京东用户登录 uId:{} uPwd:{}", uId, uPwd);
return true;
}
public 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<String, String>();
if (m9.find()) {
map.put("name", m9.group());
}
map.put("price", "5999.00");
logger.info("模拟京东商品爬虫解析:{} | {} 元 {}", map.get("name"),
map.get("price"), skuUrl);
return map;
}
public String createBase64(Map<String, String> goodsInfo) {
BASE64Encoder encoder = new BASE64Encoder();
logger.info("模拟生成京东商品base64海报");
return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
}
}
- 模拟登录。
- 爬取信息,这里只是把title的信息爬取后的结果截取出来。
- 模拟创建base64图片的方法。
模拟爬虫淘宝
public class TaoBaoNetMall extends NetMall {
public TaoBaoNetMall(String uId, String uPwd) {
super(uId, uPwd);
}
@Override
public Boolean login(String uId, String uPwd) {
logger.info("模拟淘宝用户登录 uId:{} uPwd:{}", uId, uPwd);
return true;
}
@Override
public 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<String, String>();
if (m9.find()) {
map.put("name", m9.group());
}
map.put("price", "4799.00");
logger.info("模拟淘宝商品爬虫解析:{} | {} 元 {}", map.get("name"),
map.get("price"), skuUrl);
return map;
}
@Override
public String createBase64(Map<String, String> goodsInfo) {
BASE64Encoder encoder = new BASE64Encoder();
logger.info("模拟生成淘宝商品base64海报");
return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
}
}
模拟爬虫当当
public class DangDangNetMall extends NetMall {
public DangDangNetMall(String uId, String uPwd) {
super(uId, uPwd);
}
@Override
public Boolean login(String uId, String uPwd) {
logger.info("模拟当当用户登录 uId:{} uPwd:{}", uId, uPwd);
return true;
}
@Override
public 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<String, String>();
if (m9.find()) {
map.put("name", m9.group());
}
map.put("price", "4548.00");
logger.info("模拟当当商品爬虫解析:{} | {} 元 {}", map.get("name"),
map.get("price"), skuUrl);
return map;
}
@Override
public String createBase64(Map<String, String> goodsInfo) {
BASE64Encoder encoder = new BASE64Encoder();
logger.info("模拟生成当当商品base64海报");
return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
}
}
测试验证
编写测试类
/**
* 测试连接
* 京东:https://item.jd.com/100008348542.html
* 淘宝:https://detail.tmall.com/item.htm
* 当当:http://product.dangdang.com/1509704171.html
*/
@Test
public void test_NetMall() {
NetMall netMall = new JDNetMall("1000001","*******");
String base64 =
netMall.generateGoodsPoster("https://item.jd.com/100008348542.html");
logger.info("测试结果:{}", base64);
}
- 测试类提供了三个商品链接,也可以是其他商品链接。
- 爬取过程模拟爬取京东商品,可以替换为其他商品服务
new JDNetMall/new TaobaoNetMall/new DangdangNetMall
。
测试结果
23:33:13.616 [main] INFO org.itstack.demo.design.NetMall - 模拟京东用户登录
uId:1000001 uPwd:*******
23:33:15.038 [main] INFO org.itstack.demo.design.NetMall - 模拟京东商品爬虫解析:【AppleiPhone 11】Apple iPhone 11 (A2223) 128GB 黑色 移动联通电信4G手机 双卡双待
【行情 报价 价格 评测】 -京东 | 5999.00 元
https://item.jd.com/100008348542.html
23:33:15.038 [main] INFO org.itstack.demo.design.NetMall - 模拟生成京东商品base64海报
23:33:15.086 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:
eyJwcmljZSI6IjU5OTkuMDAiLCJuYW1lIjoi44CQQXBwbGVpUGhvbmUgMTHjgJFBcHBsZSBpUGh
v
bmUgMTEgKEEyMjIzKSAxMjhHQiDpu5HoibIg56e75Yqo6IGU6YCa55S15L+hNEfmiYvmnLog5Y+
M
5Y2h5Y+M5b6F44CQ6KGM5oOFIOaKpeS7tyDku7fmoLwg6K+E5rWL44CRLeS6rOS4nCJ9
Process finished with exit code 0
4. 总结
- 通过上述的实现可以看到模板模式在定义统一结构也就是执行标准上非常方便,也就很好的控制了后续的实现者不用关心调用逻辑,按照统一方式执行,那么类的继承者只需要关心具体的业务逻辑实现。
- 另外模板模式也是为了解决子类通用方法,放到父类中设计的优化。让每一个子类只做子类需要完成的内容,而不需要关心其他逻辑。这样提取共用代码,行为由父类管理,扩展可变部分,也就非常利于拓展和迭代。