模板模式(Template Method Pattern)
定义一个算法的骨架,并允许子类为一个或者多个步骤提供实现。
模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
属于行为型设计模式。
适用场景:
1.一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2.各个子类中公共的行为被提取出来并且集中到一个公共的父类中,从而避免代码重复。
现实场景(坐飞机):
买票------进站安检-----找到飞机/临时有事,离开------飞行完成/原地返回
流程代码:
/**
* @Author Darker
* @Descrption 坐飞机的流程
* @Date : Created in 9:41 2020-3-13
*/
public abstract class TakeAPlane {
//坐飞机的流程是固定的
protected final void fly(){
//买票
this.buyTick();
//安检
this.SecurityCheck();
//实现流程的微调,如果流程不用微调,可以不用
if(isAnyThing()){
//返回
this.goBack();
}else{
//上飞机
this.selectPlane();
//飞行完成
this.CompletionOfFlight();
}
}
protected final void buyTick(){
System.out.println("买票");
}
protected final void SecurityCheck(){
System.out.println("安检");
}
//钩子方法
protected boolean isAnyThing(){return false;}
protected final void goBack(){
System.out.println("临时有事,原路回家");
}
protected abstract void selectPlane();
protected final void CompletionOfFlight(){
System.out.println("一路平安,飞行完成");
}
}
/**
* @Author Darker
* @Descrption
* @Date : Created in 10:07 2020-3-13
*/
public class TakeAPlaneToHaiHang extends TakeAPlane{
private boolean anyThing = false;
public TakeAPlaneToHaiHang(boolean anyThing){
this.anyThing = anyThing;
}
//留给子类的方法
@Override
protected void selectPlane() {
System.out.println("上了海航的飞机,海南航空空姐长得漂亮");
}
//重写钩子方法
@Override
protected boolean isAnyThing() {
return this.anyThing;
}
}
/**
* @Author Darker
* @Descrption
* @Date : Created in 10:07 2020-3-13
*/
public class TakeAPlaneToShanHang extends TakeAPlane{
private boolean anyThing = false;
public TakeAPlaneToShanHang(boolean anyThing){
this.anyThing = anyThing;
}
//留给子类的方法
@Override
protected void selectPlane() {
System.out.println("上了山航的飞机,山东航空空姐身材好");
}
//重写钩子方法
@Override
protected boolean isAnyThing() {
return this.anyThing;
}
}
测试代码:
/**
* @Author Darker
* @Descrption
* @Date : Created in 10:13 2020-3-13
*/
public class TakeAPlaneTest {
public static void main(String[] args) {
//坐海航
TakeAPlane haiHang = new TakeAPlaneToHaiHang(false);
haiHang.fly();
System.out.println("-------------------------------------------------------");
//坐山航,但临时有事情,老婆带着小姨子跑了
TakeAPlane shanHang = new TakeAPlaneToShanHang(true);
shanHang.fly();
}
}
这个例子把一些公用的逻辑设定好了,子类只需要按事先预定好的流程走就可以了。
JDBC模板:
我们经常用一些数据层的框架,只需要传个sql和参数就会返回list,那现在我们来看看这个流程具体是怎么操作的。
/**
* @Author Darker
* @Descrption
* @Date : Created in 10:57 2020-3-13
*/
@Data
public class User {
private String usename;
private String password;
private int age;
private String addr;
}
/**
* @Author Darker
* @Descrption 每行返回映射的处理方式接口
* @Date : Created in 10:31 2020-3-13
*/
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws Exception;
}
/**
* @Author Darker
* @Descrption jdbc模板
* @Date : Created in 10:33 2020-3-13
*/
public abstract class JdbcTemplate {
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource){
this.dataSource = dataSource;
}
public List<?> executeQuery(String sql,RowMapper<?> rowMapper,Object[] values){
try {
//1.获取连接
Connection connection = this.getConnection();
//2.创建语句集
PreparedStatement preparedStatement = this.createPrePareStatement(connection,sql);
//3.执行语句集
ResultSet resultSet = this.executeQuery(sql,preparedStatement,values);
//4.处理结果集
List<?> result = this.paresResultSet(resultSet,rowMapper);
//5.关闭结果集
this.closeResultSet(resultSet);
//6.关闭语句集
this.closeStatement(preparedStatement);
//7.关闭连接
this.closeConnection(connection);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void closeConnection(Connection connection) throws Exception {
connection.close();
}
protected void closeStatement(PreparedStatement preparedStatement) throws Exception {
preparedStatement.close();
}
protected void closeResultSet(ResultSet resultSet) throws Exception {
resultSet.close();
}
protected List<?> paresResultSet(ResultSet resultSet, RowMapper<?> rowMapper) throws Exception {
List<Object> result = new ArrayList<>();
int rowNum = 1;
while(resultSet.next()){
result.add(rowMapper.mapRow(resultSet,rowNum++));
}
return result;
}
protected ResultSet executeQuery(String sql,PreparedStatement preparedStatement, Object[] values) throws Exception {
for(int i=0,len =values.length;i<len;i++){
preparedStatement.setObject(i,values);
}
return preparedStatement.executeQuery();
}
protected PreparedStatement createPrePareStatement(Connection connection, String sql) throws SQLException {
return connection.prepareStatement(sql);
}
public Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
}
/**
* @Author Darker
* @Descrption
* @Date : Created in 10:58 2020-3-13
*/
public class UserDao extends JdbcTemplate {
public UserDao(DataSource dataSource){
super(dataSource);
}
public List<?> selectAll(){
String sql = "select * from t_user";
return super.executeQuery(sql, new RowMapper<Object>() {
@Override
public Object mapRow(ResultSet rs, int rowNum) throws Exception {
User user = new User();
//字段过多,可以用原型模式优化
user.setUsename(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setAge(rs.getInt("age"));
user.setAddr(rs.getString("addr"));
return user;
}
},null);
}
}
/**
* @Author Darker
* @Descrption
* @Date : Created in 11:11 2020-3-13
*/
public class UserDaoTest {
public static void main(String[] args) {
//有兴趣的小伙伴可以自己去测试一下,这里我就懒得去拿数据源了
UserDao userDao = new UserDao(null);
List<?> result = userDao.selectAll();
System.out.println(result);
}
}
源码中的应用 :
jdk源码中哪里使用了模板模式呢?
答案就是我们最长用的AbstractList了,我们找到它的get方法看看。
先看看子类对它抽象get方法的实现,看看是否有什么区别,第一个是ArrayList
它是先检查一下索引,然后提取元素,再看看ArrayQueue
显然,它要根据容量来取模,再来运算取值 ,但是他们有很多公共方法,这是很典型的模板模式。
再来看看我们每天都在用的HttpServlet。
它把大部分处理请求的逻辑都写好了,留了两个方法给子类实现,我们来看看它为什么能识别get,post方法。
跳到了它已经写好的公共逻辑里面了,对吧,这也是典型的模板模式。
继续,看看我们经常用的mybatis框架
它留了一些操作方法给我们,比如更新,查询等,它的子类实现也是不一样的,有兴趣的小伙伴也可以自己去看看。
总结:
优点:
1.提高了代码的复用性
2.提高了代码的扩展性
3.符合开闭原则
缺点:
1.类的数目增加
2.见解的增加了系统实现的复杂度
3.继承关系自身缺点,如果父类添加了新的抽象方法,所有子类都要改变