设计模式之三模板方法

设计模式之模板方法

两个核心关键字

abstract

abstract用来定义抽象类,一般位于继承关系的顶层或者上层;ArrayList就继承一个抽象类AbstractList,如下所示:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
//AbstractList 就是一个抽象类
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> 

关于抽象类需要注意的问题:
①有抽象方法的类必须是抽象类,反之不成立:抽象类不一定有抽象方法
②抽象类可以有普通方法和静态方法
③抽象类可以有构造方法: 接口不可以
④抽象类可以有普通属性:接口不可以,接口的属性都是常量
⑤一个类可以继承一个抽象类实现多个接口
⑥抽象方法没有方法体,直接以分号结束

final

①final修饰属性表示常量,必须赋值,不能修改
②final修饰方法表示最终方法,子类不能重写
③final修饰类表示最终类,不能被继承, String,System都是final类不能被基础

模板方法简介

模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。 这种类型的设计模式属于行为型模式。定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。

模板模式,其主要的的思想就是做一个模板,提供给客户端进行调用。除去生活中我们经常用到的简历模板、合同模板等等,Java中也有很经典的模板使用,那就是Servlet,HttpService类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。这些do方法需要由HttpServlet的具体则由子类提供。

模板模式主要由抽象模板(Abstract Template)角色和具体模板(Concrete Template)角色组成。

  • 抽象模板(Abstract Template): 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤;定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
  • 具体模板(Concrete Template): 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤;每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。

案例之一

package com.hanker.oop6;
//抽象角色
abstract class  Game{
    //启动游戏
    protected abstract void  runGame();
    //选择人物
    protected  void choosePerson() {};
    //开始玩游戏
    protected abstract void startPlayGame();
    //结束游戏
    protected abstract void endPlayGame();
    
    //玩游戏的模板方法 ,final表示这个方法不能被重写
    public final void play() {
        runGame();//第一步:启动游戏
        choosePerson();//第二步:选择人物
        startPlayGame();//第三步:开始玩游戏
        endPlayGame();//第四步:结束游戏
    }
}
//吃鸡游戏
class Pubg extends Game{

	@Override
	protected void runGame() {
		System.out.println("启动和平精英游戏....");
	}
	@Override
	protected void startPlayGame() {
		System.out.println("开始玩平精英游戏....");
	}

	@Override
	protected void endPlayGame() {
		System.out.println("结束和平精英游戏....");
	}
	@Override
	protected void choosePerson() {
		System.out.println("选择游戏人物....");
	}
	
}
//第1个具体角色
class ContraGame extends Game{
   @Override
   protected void runGame() {
       System.out.println("启动魂斗罗II...");
   }
   @Override
   protected void startPlayGame() {
       System.out.println("1P正在使用S弹打aircraft...");
   }
   @Override
   protected void endPlayGame() {
       System.out.println("1P被流弹打死了,游戏结束!");
   }
}
//第2个具体角色
class TMNTGame extends Game{

   @Override
   protected void runGame() {
       System.out.println("启动忍者神龟III...");
   }

   @Override
   protected void choosePerson() {
       System.out.println("1P选择了Raph !");
   }

   @Override
   protected void startPlayGame() {
       System.out.println("Raph正在使用绝技 “火箭头槌” ");
   }

   @Override
   protected void endPlayGame() {
       System.out.println("Raph 掉进井盖里死了,游戏结束了! ");
   }
}
public class TemplateMethodPattern {
	//问:把大象放到冰箱里分几步?
	public static void main(String[] args) {
		Game game1 = new Pubg();
		game1.play();
		//注意的问题是play是父类定义好的模板
		Game game2 = new TMNTGame();
		game2.play();
	}

}

案例之二

package com.hanker.oop6;
//抽象类
abstract class RereshBaverage {
    /**
     * 制备饮料的模板方法
     * 封装了所有子类共同遵守的算法骨架
     */
    public final void prepreBvergeTemplage(){
        //步骤一 将水煮沸
        boilWater();
        //步骤二 炮制饮料
        brew();
        //步骤三 将饮料倒入杯中
        pourInCup();
        //步骤四 加入调味料
        addCondiments();
    }
    /**
     * 基本方法,将水煮沸 这对所有子类而言是一个共同的行为,所以声明为private,无需向子类开放
     */
    private void boilWater(){
        System.out.println("将水煮沸");
    }
    /**
     * 抽象的基本方法 泡制饮料
     * 在算法框架中并不知道具体实现是什么样子的,所以做成了抽象方法,并且由于我们需要在子类中可见,便于复写而提供具体的实现所以将
     * 权限设置为protected
     */
    protected abstract void brew();

    private void pourInCup(){
        System.out.println("将饮料倒入杯中");
    }
    /**
     * 加入调味料,需要加什么让子类去实现
     */
    protected abstract void addCondiments();
}
//咖啡
class Coffee extends RereshBaverage {
    @Override
    protected void brew() {
        System.out.println("用沸水冲泡咖啡");
    }
    //加入调味料
    @Override
    protected void addCondiments() {
        System.out.println("加入糖和牛奶");
    }
}
//茶
class Tea extends RereshBaverage {
    @Override
    protected void brew() {
        System.out.println("用80度的热水浸泡茶叶5分钟");
    }
    @Override
    protected void addCondiments() {
        System.out.println("加入柠檬");
    }
}
public class TemplateMethodPattern2 {

	public static void main(String[] args) {
		System.out.println("咖啡制备中。。。");
        RereshBaverage coffee = new Coffee();
        coffee.prepreBvergeTemplage();
        System.out.println("咖啡制备完成! ");
        //必须用父类引用子类对象,为什么?
        System.out.println("\n*********************");
        System.out.println("茶制备中。。。");
        RereshBaverage tea = new Tea();
        tea.prepreBvergeTemplage();
        System.out.println("茶制备完成!");
	}

}

如果我们希望喝到一杯black coffee或者喝到一杯纯正的茶,就是不希望实现第四个步骤,那么将如何实现呢,如何做到个性化的扩展呢,这里就用到了一个钩子方法。

改造基类方法,添加钩子函数:

//抽象类
abstract class RereshBaverage {
    /**
     * 制备饮料的模板方法
     * 封装了所有子类共同遵守的算法骨架
     */
    public final void prepreBvergeTemplage(){
        //步骤一 将水煮沸
        boilWater();
        //步骤二 炮制饮料
        brew();
        //步骤三 将饮料倒入杯中
        pourInCup();
        if(isCustomerWantsCondments()) {
        	//步骤四 加入调味料
            addCondiments();
        }
    }
    /**
     * Hook钩子和函数,提供一个默认或空的实现
     * 具体的子类可以自行决定是否挂钩以及如何挂钩
     * 询问用户是否加入调料
     * @return ture 默认允许加入调料
     */
    protected boolean isCustomerWantsCondments() {
    	return true;
    }
    
    /**
     * 基本方法,将水煮沸 这对所有子类而言是一个共同的行为,所以声明为private,无需向子类开放
     */
    private void boilWater(){
        System.out.println("将水煮沸");
    }
    /**
     * 抽象的基本方法 泡制饮料
     * 在算法框架中并不知道具体实现是什么样子的,所以做成了抽象方法,并且由于我们需要在子类中可见,便于复写而提供具体的实现所以将
     * 权限设置为protected
     */
    protected abstract void brew();

    private void pourInCup(){
        System.out.println("将饮料倒入杯中");
    }
    /**
     * 加入调味料,需要加什么让子类去实现
     */
    protected abstract void addCondiments();
}

实现类:

//茶
class Tea extends RereshBaverage {
    @Override
    protected void brew() {
        System.out.println("用80度的热水浸泡茶叶5分钟");
    }
    /**
     * 子类通过覆盖的形式选择挂载钩子函数
     */
    @Override
    protected boolean isCustomerWantsCondments() {
    	return false;
    }
    @Override
    protected void addCondiments() {
        System.out.println("加入柠檬");
    }
}
咖啡制备中。。。
将水煮沸
用沸水冲泡咖啡
将饮料倒入杯中
加入糖和牛奶
咖啡制备完成! 

*********************
茶制备中。。。
将水煮沸
用80度的热水浸泡茶叶5分钟
将饮料倒入杯中  #第四步就没有执行
茶制备完成!

子类方法挂载钩子函数,默认返回true,执行对应的步骤方法,返回false,就不执行对应的步骤方法。使得子类方法,选择性地执行一个事件的处理过程。

案例之三—JDBC模板方法实现

创建一个模板类JdbcTemplate,封装所有的JDBC操作。以查询为例,每次查询的表不一样,返回的数据结构也就不一样。我们针对不同的数据,都要将其封装成不同的实体类。而每个实体类的封装逻辑是不一样的,有的有日期类型数据有的没有。但是封装数据之前和之后的处理流程是不变的,因此可以使用模板模式来进行设计。

创建封装实例类的接口RowMapper

package com.hanker.oop6;

import java.sql.ResultSet;

public interface RowMapper<T> {
	T mapRow(ResultSet rs,int rowNum) throws Exception;
}

编写JdbcTemplate类

package com.hanker.oop6;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public abstract class JdbcTemplate<T> {
	
	//再写一个方法实现增删改
	
	//模板方法sql="select * from user where id=?";
	public final List<T> executeQuery(String sql,RowMapper<T> rowMapper,Object[] params){
		try {
			//获取连接
			Connection conn = this.getConnection();
			//创建语句集
			PreparedStatement pstm = conn.prepareStatement(sql);
			//执行语句,设置参数
			ResultSet rs = this.executeQuery(pstm,params);
			//处理结果集
			List<T> result  = this.parseResultRet(rs,rowMapper);
			//关闭结果集
			this.closeResultSet(rs);
			//关闭语句集
			this.closeStatement(pstm);
			//关闭连接
			this.closeConnection(conn);
			//返回结果
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}	
	
	protected void closeConnection(Connection conn) throws SQLException {
		if(conn != null) {
			conn.close();
		}
		
	}
	protected void closeStatement(PreparedStatement pstm) throws SQLException{
		if(pstm != null) {
			pstm.close();
		}
		
	}
	protected void closeResultSet(ResultSet rs) throws SQLException {
		if(rs != null) {
			rs.close();
		}
		
	}
	//解析结果集
	private List<T> parseResultRet(ResultSet rs, RowMapper<T> rowMapper)throws Exception {
		List<T> result = new ArrayList<>();
		int rowNum = 1;
		while(rs.next()) {
			result.add(rowMapper.mapRow(rs, rowNum));
		}
		return result;
	}
	protected ResultSet executeQuery(PreparedStatement pstm, Object[] params) throws Exception {
		for(int i=0; i<params.length; i++) {
			pstm.setObject(i+1, params[i]);
		}
		return pstm.executeQuery();
	}
	protected Connection  getConnection() {
		Connection conn = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
			conn = DriverManager.getConnection("jdbc:mysql:///test?characterEncoding=utf8", 
					"root", "123456");
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
}

编写实体类

package com.hanker.oop6;

public class SysUser {

	private Integer id; //Long String UUID就是一个String的主键
	private String username;
	private String password;
	
	public SysUser() {
		super();
	}
	public SysUser(Integer id, String username, String password) {
		super();
		this.id = id;
		this.username = username;
		this.password = password;
	}
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	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;
	}
	@Override
	public String toString() {
		return "SysUser [id=" + id + ", username=" + username + ", password=" + password + "]";
	}
}

编写Dao类

package com.hanker.oop6;

import java.sql.ResultSet;
import java.util.List;

class SysUserRowMapper implements RowMapper<SysUser>{

	@Override
	public SysUser mapRow(ResultSet rs, int rowNum) throws Exception {
		SysUser user = new SysUser();
		user.setId(rs.getInt("id"));
		user.setUsername(rs.getString("username"));
		user.setPassword(rs.getString("password"));
		return user;
	}
}
public class UserDao extends JdbcTemplate<SysUser> {

	public List<SysUser> selectAll(){
		String sql  = "select * from sys_user where id>?";
		return super.executeQuery(sql, new SysUserRowMapper() , new Object[] {1});
	}
	public static void main(String[] args) {
		UserDao dao = new UserDao();
		List<SysUser> list = dao.selectAll();
		for (SysUser sysUser : list) {
			System.out.println(sysUser);
		}
	}
}

模板模式的优缺点

优点

①利用模板模式将相同处理逻辑的代码放到抽象类中,可以提高代码的复用性
②将不同的代码放到不同的子类中,通过对子类的扩展增加新的行为,可以提高代码的扩展性
③把不变的行为写在父类中,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则

缺点

①每个抽象类都需要一个子类来实现,导致了类的数量增加
②类数量的增加间接地增加了系统的复杂性
③因为继承关系自身的特点,如果父类添加新的抽象方法,所有子类都要改一遍。

发布了91 篇原创文章 · 获赞 43 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/kongfanyu/article/details/104951250
今日推荐