模板模式
首先來看一下模板模式的簡介:模板模式(Template Pattern),一是指定義一個算法的骨架,并允許子類為一個或者多個步驟提供實現(xiàn)。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法的某些步驟,屬于行為性設(shè)計模式。模板方法適用于以下應(yīng)用場景:
1、一次性實現(xiàn)一個算法的不變的部分,并將可變的行為留給子類來實現(xiàn)。
2、各子類中公共的行為被提取出來并集中到一個公共的父類中,從而避免代碼重復(fù)。
生活中的很多小事都是模板模式的體現(xiàn),比如我們?nèi)メt(yī)院看病的流程:1.掛號 2.看病
3.做檢查 4.拿藥,下面我們通過代碼來體會一下。
首先寫一個看病的抽象類SeeDoctor,特別提一下,在這個類中有一個鉤子方法needChecked,關(guān)于鉤子方法的定義:通過一個方法來干涉另一個方法的行為,就是一個方法的返回結(jié)果或修改的變量是另一個方法執(zhí)行時的if判斷條件或for/while循環(huán)調(diào)用條件的。在模板模式中,鉤子方法和模板模式?jīng)]有必然的關(guān)系,也就是說模板模式中可以有鉤子方法也可以沒有鉤子方法,具體是否使用鉤子方法根據(jù)業(yè)務(wù)判斷。
/**
* @author: Winston
* @createTime: 2021/6/28
* <p>
* 模板會有一個或者多個未實現(xiàn)的方法
* 而且這些未實現(xiàn)的方法有固定的執(zhí)行順序
* 看病流程
*/
public abstract class SeeDoctor {
/**
* 去醫(yī)院看病的流程
*/
protected void goToHospital() {
// 1.掛號
this.register();
// 2.找醫(yī)生看病
this.lookingForDoctor();
// 3.做檢查,有些病人是需要做檢查的,而有些病人是不需要做檢查的
// 這里的needChecked()方法就是鉤子方法
if (needChecked()) {
// 檢查方法,病人做檢查的項目不一樣
this.checked();
}
// 4.拿藥
this.takeMedicine();
}
public final void takeMedicine() {
System.out.println("根據(jù)藥方拿藥");
}
protected abstract void checked();
// 鉤子方法,鉤子方法的定義:通過一個方法來干涉另一個方法的行為,就是一個方法的返回結(jié)果或修改的變量是另一個方法執(zhí)行時的if判斷條件或for/while循環(huán)調(diào)用條件的
protected boolean needChecked(){
return false;
}
public final void lookingForDoctor() {
System.out.println("找醫(yī)生診斷");
}
public final void register() {
System.out.println("掛號");
}
}
編寫張三看病類ZhangSanSeeDoctor
public class ZhangSanSeeDoctor extends SeeDoctor {
@Override
protected void checked() {
System.out.println("張三做的檢查項目:");
}
}
編寫李四看病類LiSiSeeDoctor
public class LiSiSeeDoctor extends SeeDoctor {
/**
* 是否需要檢查
*/
private boolean needChecked = false;
/**
* 通過構(gòu)造方法將needChecked的值改變
* @param needChecked
*/
public LiSiSeeDoctor(boolean needChecked) {
this.needChecked = needChecked;
}
@Override
protected boolean needChecked() {
return this.needChecked;
}
@Override
protected void checked() {
System.out.println("李四做的檢查項目:......");
}
}
測試類
public class SeeDoctorTest {
public static void main(String[] args) {
System.out.println("================張三去醫(yī)院看病========================");
SeeDoctor patient1 = new ZhangSanSeeDoctor();
patient1.goToHospital();
System.out.println("================李四去醫(yī)院看病========================");
SeeDoctor patient2 = new LiSiSeeDoctor(true);
patient2.goToHospital();
}
}
運行結(jié)果:
================張三去醫(yī)院看病========================
掛號
找醫(yī)生診斷
根據(jù)藥方拿藥
================李四去醫(yī)院看病========================
掛號
找醫(yī)生診斷
李四做的檢查項目:......
根據(jù)藥方拿藥
利用模板模式重構(gòu)JDBC操作業(yè)務(wù)場景
創(chuàng)建一個末班類JdbcTemplate,封裝所有的JDBC操作。以查詢?yōu)槔?,每次查詢的表不同,返回的?shù)據(jù)結(jié)構(gòu)也就不一樣。我們針對不同的數(shù)據(jù),都要封裝成不同的實體對象。而每個實體封裝的邏輯都是不一樣的,但封裝前和封裝后的處理流程是不變的,因此,我們可以使用模板方法模式來設(shè)計這樣的業(yè)務(wù)場景。
先創(chuàng)建約束ORM邏輯的接口RowMapper
/**
* @author: Winston
* @createTime: 2021/6/28
*
* Spring中RowMapper接口的作用,用來把數(shù)據(jù)庫中的列字段和java bean中屬性對應(yīng)上
*/
public interface RowMapper<T> {
public T mapRow(ResultSet resultSet, int rowNumber);
}
創(chuàng)建封裝了所有處理流程的抽象類JdbcTemplate
/**
* @author: Winston
* @createTime: 2021/6/28
*/
public abstract class JdbcTemplate {
/**
* 數(shù)據(jù)源
*/
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<?> executeQuery(String sql, RowMapper rowMapper, Object... args) throws Exception {
// 1.獲取連接
Connection connection = this.getConnection();
// 2.創(chuàng)建語句集
PreparedStatement preparedStatement = this.createPrepareStatement(connection, sql);
// 3.執(zhí)行語句集
ResultSet resultSet = this.executeQuery(preparedStatement, args);
// 4.處理結(jié)果集
List<?> result = this.parseResultSet(resultSet, rowMapper);
// 5.關(guān)閉結(jié)果集
this.closeResult(resultSet);
// 6.關(guān)閉語句集
this.closePreparedStatement(preparedStatement);
// 7.關(guān)閉連接
this.closeConnection(connection);
return result;
}
protected void closeConnection(Connection connection) throws SQLException {
connection.close();
}
protected void closePreparedStatement(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.close();
}
protected void closeResult(ResultSet resultSet) throws SQLException {
resultSet.close();
}
protected List<?> parseResultSet(ResultSet resultSet, RowMapper rowMapper) throws SQLException {
List<Object> result = new ArrayList<>();
int rowNum = 1;
while (resultSet.next()) {
result.add(rowMapper.mapRow(resultSet, rowNum++));
}
return result;
}
protected ResultSet executeQuery(PreparedStatement preparedStatement, Object[] args) throws SQLException {
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i, args[i]);
}
return preparedStatement.executeQuery();
}
protected PreparedStatement createPrepareStatement(Connection connection, String sql) throws SQLException {
return connection.prepareStatement(sql);
}
protected Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
}
創(chuàng)建實體類User
public class User {
private String username;
private String password;
private String mobile;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
創(chuàng)建數(shù)據(jù)庫操作類UserDao
/**
* @author: Winston
* @createTime: 2021/6/28
*/
public class UserDao extends JdbcTemplate{
public UserDao(DataSource dataSource) {
super(dataSource);
}
public List<?> selectAll() throws Exception {
String sql = "SELECT * FROM t_user";
return super.executeQuery(sql, new RowMapper() {
@Override
public Object mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
// 將實體類和數(shù)據(jù)庫字段對應(yīng)上
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String mobile = resultSet.getString("mobile");
Integer age = resultSet.getInt("age");
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setMobile(mobile);
user.setAge(age);
return user;
}
}, null);
}
}
測試類,這里由于沒有配置Spring環(huán)境和數(shù)據(jù)庫環(huán)境,僅做模擬。
public class UserTest {
public static void main(String[] args) {
DataSource dataSource = null;
UserDao userDao = new UserDao(dataSource);
try {
List<?> result = userDao.selectAll();
} catch (Exception e) {
e.printStackTrace();
}
}
}
模板模式在JDK源碼中的體現(xiàn)
先來看看JDK中AbstractList的get()方法,可以看到get()是一個抽象方法,我們經(jīng)常用的ArrayList就是AbstractList的子類,由于AbstractList不同的子類所實現(xiàn)的get()方法有所不同,所以這也是模板模式的一種體現(xiàn)。
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
abstract public E get(int index);
...
}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
...
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
...
}
模板模式小結(jié)
模板模式就好比是造房子,大致的步驟是規(guī)定好的,比如先打地基、然后買水泥黃沙等,至于房子蓋的形狀可以不同。
模板模式的優(yōu)缺點
優(yōu)點:
1、利用模板方法將相同處理邏輯的代碼放到抽象類中,可以提高代碼的復(fù)用性。
2、將不同的代碼在不同的子類中實現(xiàn),通過對子類的擴(kuò)展增加新的行為,提高代碼的擴(kuò)展性。
3、把不變的行為寫在父類上,去除子類的重復(fù)代碼,提供了一個很好的代碼復(fù)用平臺,符合開閉原則。
缺點:
1、類數(shù)目的增加,每一個抽象類都需要至少一個子類來實現(xiàn),這樣導(dǎo)致類的個數(shù)增加,從而導(dǎo)致系統(tǒng)復(fù)雜度的增加。
2、繼承關(guān)系自身的缺點,如果父類添加了新的抽象方法,所有子類都要增加該方法。