文章目录
- 一、定义:模板模式
- 二、模拟场景:模板模式
- 三、改善代码:模板模式
- 3.0 引入依赖
- 3.1 工程结构
- 3.2 模板模式结构图
- 3.3 爬取商品生成海报实现
- 3.3.1 HTTP获取连接类
- 3.3.2 定义执行顺序的抽象类
- 3.3.3 当当爬取抽象实现类
- 3.3.4 京东爬取抽象实现类
- 3.3.5 淘宝爬取抽象实现类
- 3.4 单元测试
- 四、总结:模板模式
一、定义:模板模式
- 模板模式:通过在抽象类中定义抽象方法的执行顺序,并将抽象方法设定为只有子类实现,但不涉及
独立访问
的方法。
二、模拟场景:模板模式
- 模拟爬虫各类电商商品,生成营销推广海报场景。
- 模板模式的核心点在于:
- 由抽象类定义抽象方法执行策略,也就是说父类规定好了 一系列的执行标准,这些标准串联成一整套业务流程。
- 在这个场景中模拟爬虫爬取各类商家的商品信息,生成推广海报,赚取商品返利。
- 整个爬取过程分为三个步骤:模拟登录、爬取信息、生成海报。
- 因为有些商品只有登录后才可以爬取,并且登录可以看到一些特定的价格,这与未登录用户看到的价格不同。
- 不同的电商网站爬取方式不同,解析方式也不同,因此可以作为每一个实现类中的特定实现。
- 生成海报的步骤基本一样,但会有特定的商品来源标识。所以这三个步骤可以使用模板模式来设定,并有具体的场景做子类实现。
三、改善代码:模板模式
💡 模板模式的业务场景可能在世的开发中并不是很多,主要因为这个设计模式会在抽象类中定义逻辑行为的执行顺序。
一般情况下,我们用的抽象类定义的逻辑行为都比较轻量级或者没有,只有提供一些基本方法公共调用和实现。
- 但如果遇到适合的场景使用这样的设计模式也是非常方便的,因为他可以控制整套逻辑的执行顺序和统一的输入、输出,而对于实现方只需要关心好自己的业务逻辑即可。
- 在模拟场景中,只需要记住三步实现:
模拟登录
、爬取信息
、生成海报
。
3.0 引入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- LOGGING begin -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.9</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
3.1 工程结构
design-step-22
|——src
|——main
|--java
|--com.lino.design
|--impl
| |--DangDangNetMall.java
| |--JDNetMall.java
| |--TaoBaoNetMall.java
|-HttpClient.java
|-NetMall.java
|--test
|--com.lino.design.test
|-ApiTest.java
3.2 模板模式结构图
- 一个定义了抽象方法执行顺序的核心抽象类,以及三个模拟具体的实现(
京东
、淘宝
、当当
)的电商服务。
3.3 爬取商品生成海报实现
3.3.1 HTTP获取连接类
HttpClient.java
package com.lino.design;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* @description: http请求类
*/
public class HttpClient {
public static String doGet(String httpUrl) {
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader br = null;
String result = null;
try {
// 创建远程url连接对象
URL url = new URL(httpUrl);
// 通过远程url连接对象打开一个连接,强转成HttpURLConnection
connection = (HttpURLConnection) url.openConnection();
// 设置连接方式:get
connection.setRequestMethod("GET");
// 设置连接主机服务器的超时时间:15000毫秒
connection.setConnectTimeout(15000);
// 设置读取远程返回的数据时间:60000毫秒
connection.setReadTimeout(60000);
// 发送请求
connection.connect();
// 通过connection连接,获取输入流
if (connection.getResponseCode() == 200) {
is = connection.getInputStream();
// 封装输入流is,并指定字符集
br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
// 存放数据
StringBuilder sbf = new StringBuilder();
String temp = null;
while ((temp = br.readLine()) != null) {
sbf.append(temp);
sbf.append("\r\n");
}
result = sbf.toString();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 关闭远程连接
assert connection != null;
connection.disconnect();
}
return result;
}
}
3.3.2 定义执行顺序的抽象类
NetMall.java
package com.lino.design;
import com.sun.org.apache.xpath.internal.operations.Bool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.regex.Pattern;
/**
* @description: 抽象模板
*/
public abstract class NetMall {
protected Logger logger = LoggerFactory.getLogger(NetMall.class);
protected final Pattern p9 = Pattern.compile("(?<=title\\>).*(?=</title)");
/**
* 用户ID
*/
String uId;
/**
* 用户密码
*/
String uPwd;
public NetMall(String uId, String uPwd) {
this.uId = uId;
this.uPwd = uPwd;
}
/**
* 生成商品推广海报
*
* @param skuUrl 商品url地址
* @return 推广海报
*/
public String generateGoodsPoster(String skuUrl) {
// 1.验证登录
if (!login(uId, uPwd)) {
return null;
}
// 2.爬虫商品
Map<String, String> reptile = reptile(skuUrl);
// 3.组装海报
return createBase64(reptile);
}
/**
* 模拟登录
*
* @param uId 用户ID
* @param uPwd 用户密码
* @return 登录结果
*/
protected abstract Boolean login(String uId, String uPwd);
/**
* 爬虫提取商品信息(登录后的优惠价格)
*
* @param skuUrl 商品Url地址
* @return 商品信息
*/
protected abstract Map<String, String> reptile(String skuUrl);
/**
* 生成商品推广海报
*
* @param goodsInfo 商品信息
* @return 推广海报
*/
protected abstract String createBase64(Map<String, String> goodsInfo);
}
- 这个类是模板模式的灵魂。
- 定义可外被外部访问的方法
generateGoodsPoster
,用于生成商品推广海报。 generateGoodsPoster
在方法中定义抽象方法的执行顺序1、2、3
步。- 提供三个具体的抽象方法,让外部继承实现。
login
:模拟登录。reptile
:爬虫提取商品信息(登录后的优惠价格)。createBase64
:生成商品推广海报。
3.3.3 当当爬取抽象实现类
DangDangNetMall.java
package com.lino.design.impl;
import com.alibaba.fastjson.JSON;
import com.lino.design.HttpClient;
import com.lino.design.NetMall;
import sun.misc.BASE64Encoder;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @description: 当当抽象实现类
*/
public class DangDangNetMall extends NetMall {
public DangDangNetMall(String uId, String uPwd) {
super(uId, uPwd);
}
@Override
protected Boolean login(String uId, String uPwd) {
logger.info("模拟当当用户登录 uId:{} uPwd:{}", uId, uPwd);
return true;
}
@Override
protected Map<String, String> reptile(String skuUrl) {
String str = HttpClient.doGet(skuUrl);
Matcher m9 = p9.matcher(str);
Map<String, String> map = new ConcurrentHashMap<>();
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
protected String createBase64(Map<String, String> goodsInfo) {
BASE64Encoder encoder = new BASE64Encoder();
logger.info("模拟生成当当商品base64海报");
return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
}
}
3.3.4 京东爬取抽象实现类
JDNetMall.java
package com.lino.design.impl;
import com.alibaba.fastjson.JSON;
import com.lino.design.HttpClient;
import com.lino.design.NetMall;
import sun.misc.BASE64Encoder;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @description: 京东抽象实现类
*/
public class JDNetMall extends NetMall {
public JDNetMall(String uId, String uPwd) {
super(uId, uPwd);
}
@Override
protected Boolean login(String uId, String uPwd) {
logger.info("模拟京东用户登录 uId:{} uPwd:{}", uId, uPwd);
return true;
}
@Override
protected Map<String, String> reptile(String skuUrl) {
String str = HttpClient.doGet(skuUrl);
Matcher m9 = p9.matcher(str);
Map<String, String> map = new ConcurrentHashMap<>();
if (m9.find()) {
map.put("name", m9.group());
}
map.put("price", "5999.00");
logger.info("模拟京东商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);
return map;
}
@Override
protected String createBase64(Map<String, String> goodsInfo) {
BASE64Encoder encoder = new BASE64Encoder();
logger.info("模拟生成京东商品base64海报");
return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
}
}
3.3.5 淘宝爬取抽象实现类
TaoBaoNetMall.java
package com.lino.design.impl;
import com.alibaba.fastjson.JSON;
import com.lino.design.HttpClient;
import com.lino.design.NetMall;
import sun.misc.BASE64Encoder;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
/**
* @description: 淘宝抽象实现类
*/
public class TaoBaoNetMall extends NetMall {
public TaoBaoNetMall(String uId, String uPwd) {
super(uId, uPwd);
}
@Override
protected Boolean login(String uId, String uPwd) {
logger.info("模拟淘宝用户登录 uId:{} uPwd:{}", uId, uPwd);
return true;
}
@Override
protected Map<String, String> reptile(String skuUrl) {
String str = HttpClient.doGet(skuUrl);
Matcher m9 = p9.matcher(str);
Map<String, String> map = new ConcurrentHashMap<>();
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
protected String createBase64(Map<String, String> goodsInfo) {
BASE64Encoder encoder = new BASE64Encoder();
logger.info("模拟生成淘宝商品base64海报");
return encoder.encode(JSON.toJSONString(goodsInfo).getBytes());
}
}
💡 模拟登录、爬取信息、生成海报由三个实现类分别实现。
3.4 单元测试
ApiTest.java
package com.lino.design.test;
import com.lino.design.NetMall;
import com.lino.design.impl.JDNetMall;
import jdk.nashorn.internal.scripts.JD;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @description: 单元测试
*/
public class ApiTest {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
private String JD_URL = "https://item.jd.com/100008348542.html";
private String TAO_BAO_URL = "https://detail.tmall.com/item.htm";
private String DANG_DANG_URL = "http://product.dangdang.com/1509704171.html";
@Test
public void test_NetMall() {
NetMall netMall = new JDNetMall("100001", "******");
String base64 = netMall.generateGoodsPoster(JD_URL);
logger.info("测试结果:{}", base64);
}
}
- 测试类提供了三个商品连接,也可以是其他商品的连接。
- 爬取的成功模拟爬取京东商品,可以替换为其他商品服务。
new JDNetMall
、new DangDangNetMall
、new TaoBaoNetMall
测试结果
10:36:08.491 [main] INFO com.lino.design.NetMall - 模拟京东用户登录 uId:100001 uPwd:******
10:36:09.582 [main] INFO com.lino.design.NetMall - 模拟京东商品爬虫解析:【AppleiPhone 11】Apple iPhone 11 (A2223) 128GB 黑色 移动联通电信4G手机 双卡双待【行情 报价 价格 评测】-京东 | 5999.00 元 https://item.jd.com/100008348542.html
10:36:09.582 [main] INFO com.lino.design.NetMall - 模拟生成京东商品base64海报
10:36:09.615 [main] INFO com.lino.design.test.ApiTest - 测试结果:eyJwcmljZSI6IjU5OTkuMDAiLCJuYW1lIjoi44CQQXBwbGVpUGhvbmUgMTHjgJFBcHBsZSBpUGhv
bmUgMTEgKEEyMjIzKSAxMjhHQiDpu5HoibIg56e75Yqo6IGU6YCa55S15L+hNEfmiYvmnLog5Y+M
5Y2h5Y+M5b6F44CQ6KGM5oOFIOaKpeS7tyDku7fmoLwg6K+E5rWL44CRLeS6rOS4nCJ9
四、总结:模板模式
- 通过上面的实现可以看到 模板模式 在定义统一结构也就是执行标准上非常方便。
- 也就很好的控制了后续的实现这不用关心调用逻辑,按照统一方式执行。那么类的继承者只需要关心具体的业务逻辑实现即可。
- 模板模式也是对了解决子类通用方法,放到父类中设计的优化。让每一个子类只做子类需要完成的内容,而不需要关心其他逻辑。
- 这样提取公共代码,行为由父类管理,扩展可变部分,也就非常有利于开发拓展和迭代。