模板模式
概述
模板模式的业务场景在平时开发中并不常见,这个设计模式的核心在于定义逻辑行为的执行顺序,他可以控制整套逻辑的执行顺序和统一的输入输出,而对于实现方只需要关心自己的业务逻辑即可。
但在日常生活中我们经常接触到模板,比如写周报的时候,会有一个周报模板,我们只需要按照模板填写工作内容即可。例如以下是小黄的周报模板,可以把他看作四大块,标题、本周工作、下周工作、总结。每个人填写的内容是不一样的,但是模板制约了填写的内容。
示例程序
在示例程序中我们实现从不同的网站上爬虫的场景,在此场景中,我们按照顺序实现三个模块:模拟登录,爬取信息,生成海报。
代码实现
NetMail抽象类
NetMail可以看作是我们的模版,它规定了流程的具体实现步骤,而不规定每个步骤之间的具体实现,具体实现交给它的子类完成
@Slf4j
public abstract class NetMail {
protected String username;
protected String password;
public NetMail(String username, String password) {
this.username = username;
this.password = password;
}
/**
* 生成商品推广海报
*
* @param skuUrl 商品地址(京东、淘宝、当当)
* @return 海报图片base64位信息
*/
public String generateGoodsPoster(String skuUrl) throws JsonProcessingException {
if (!login(username, password)) return null; // 1. 验证登录
Map<String, String> reptile = reptile(skuUrl); // 2. 爬虫商品
return createBase64(reptile); // 3. 生成海报
}
//验证登录
protected abstract boolean login(String username,String password);
//爬虫商品
protected abstract Map<String,String> reptile(String skuUrl);
//生成海报
protected abstract String createBase64(Map<String,String> reptile) throws JsonProcessingException;
}
具体的实现类
具体的实现类实现了验证登录、爬虫商品、生成海报三个方法,不同的实现类实现方法的方式不同
//京东
@Slf4j
public class JDNetMail extends NetMail {
public JDNetMail(String username, String password) {
super(username, password);
}
@Override
protected boolean login(String username, String password) {
log.info("模拟京东登录:username:{},password:{}",username,password);
return true;
}
@Override
protected Map<String, String> reptile(String skuUrl) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> entity = restTemplate.getForEntity(skuUrl, String.class);
String str = entity.getBody();
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");
log.info("模拟京东商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);
return map;
}
@Override
protected String createBase64(Map<String, String> reptile) throws JsonProcessingException {
BASE64Encoder encoder = new BASE64Encoder();
log.info("模拟京东商品生成海报");
return encoder.encode(new ObjectMapper().writeValueAsBytes(reptile));
}
}
//淘宝
@Slf4j
public class TBNetMail extends NetMail {
public TBNetMail(String username, String password) {
super(username, password);
}
@Override
protected boolean login(String username, String password) {
log.info("模拟淘宝登录:username:{},password:{}",username,password);
return true;
}
@Override
protected Map<String, String> reptile(String skuUrl) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> entity = restTemplate.getForEntity(skuUrl, String.class);
String str = entity.getBody();
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");
log.info("模拟淘宝商品爬虫解析:{} | {} 元 {}", map.get("name"), map.get("price"), skuUrl);
return map;
}
@Override
protected String createBase64(Map<String, String> reptile) throws JsonProcessingException {
BASE64Encoder encoder = new BASE64Encoder();
log.info("模拟淘宝商品生成海报");
return encoder.encode(new ObjectMapper().writeValueAsBytes(reptile));
}
}
测试
@SpringBootTest
class Practice2400ApplicationTests {
@Test
void contextLoads() throws JsonProcessingException {
NetMail netMail = new JDNetMail("yellowstar","123456");
String poster = netMail.generateGoodsPoster("https://item.jd.com/100026354913.html");
}
}
//结果
2022-06-29 21:50:40.581 INFO 10678 --- [ main] c.y.practice2400.design.impl.JDNetMail : 模拟京东登录:username:yellowstar,password:123456
2022-06-29 21:50:43.974 INFO 10678 --- [ main] c.y.practice2400.design.impl.JDNetMail : 模拟京东商品爬虫解析:【创维创维C2】创维(Skyworth)男士电动剃须刀C2刀头可水洗刮胡刀充电式胡须刀男孩子刮胡子刀男生智能剃胡子刀【行情 报价 价格 评测】-京东 | 5999.00 元 https://item.jd.com/100026354913.html
2022-06-29 21:50:43.975 INFO 10678 --- [ main] c.y.practice2400.design.impl.JDNetMail : 模拟京东商品生成海报
登场角色
-
AbstractClass(抽象类)
AbstractClass角色不仅负责实现模版方法,还负责声明在模版方法中所使用到的抽象方法,这些抽象方法由AbstractClass角色的子类实现,在示例程序中由NetMail扮演该角色
-
ConcreteClass(具体的实现类)
ConcreteClass角色负责实现AbstractClass角色定义的抽象方法,这里实现的方法将会在AbstractClass角色的模版方法中调用。在示例程序中由JDNetMail、TBNetMail扮演
适用场景
模板方法模式通常适用于以下场景。
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
- 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
总结
模版模式的优点
- 可以使逻辑处理通用化,由于在父类的模版方法中编写了逻辑算法,因此无需在每个字类中在编写算法
- 父类与子类的一致性,在示例程序中,无论是哪个实现类username、password属性都是保存在父类中的,使用父类保存子类实例的优点是,即是没有用instanceof等指定子类的种类,程序也能正常工作
模版模式的缺点
- 因为父类和子类是紧密联系、共同工作的,因此在子类中实现父类的抽象方法,必须要理解这些抽象方法被调用的时机,在看不到父类的源代码的情况下,想要编写子类是非常困难的