模板模式(Template)
简介
模板模式又叫模板方法模式,是指定一个算法的骨架,并允许子类为一个或多个步骤提供实现,
模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤,属于行为性设计模式.模板方法适用于以下应用场景:
1.一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
2.各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
例如jdbc的使用,一次jdbc的调用一般需要经历如下流程
public void save(Student stu){
String sql="INSERT INTO t_student(name,age) VALUES(?,?)";
Connection conn=null;
Statement st=null;
try{
// 1. 加载注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取数据库连接
conn=DriverManager.getConnection("jdbc:mysql:///jdbcdemo","root","root");
// 3. 创建语句对象
PreparedStatement ps=conn.prepareStatement(sql);
ps.setObject(1,stu.getName());
ps.setObject(2,stu.getAge());
// 4. 执行SQL语句
ps.executeUpdate();
// 5. 释放资源
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(st!=null)
st.close();
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
if(conn!=null)
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
如果这时候再来个删除
public void delete(Long id){
String sql="DELETE FROM t_student WHERE id=?";
Connection conn=null;
Statement st=null;
try{
// 1. 加载注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取数据库连接
conn=DriverManager.getConnection("jdbc:mysql:///jdbcdemo","root","root");
// 3. 创建语句对象
PreparedStatement ps=conn.prepareStatement(sql);
ps.setObject(1,id);
// 4. 执行SQL语句
ps.executeUpdate();
// 5. 释放资源
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(st!=null)
st.close();
}catch(SQLException e){
e.printStackTrace();
}finally{
try{
if(conn!=null)
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
可以发现这其中,都是同样的流程,只是执行的Sql语句不同
这种时候就可以用到模板模式,对流程进行包装.这样只需要编写不同的部分(Sql)就行了.
利用模板模式重构 JDBC 操作业务场景
创建一个模板类 JdbcTemplate,封装所有的 JDBC 操作。以查询为例,每次查询的表不同,返回的数据结构也就不一样。我们针对不同的数据,都要封装成不同的实体对象。 而每个实体封装的逻辑都是不一样的,但封装前和封装后的处理流程是不变的.因此,我们可以使用模板方法模式来设计这样的业务场景。先创建约束 ORM 逻辑的
接口 RowMapper:
public interface RowMapper<T> {
/**
* 通过结果集给指令类型实例赋值
*
* @param set 结果集
* @param rowNum 下标
* @param clazz 赋值类
* @return
* @throws SQLException
*/
T mapRow(ResultSet set, int rowNum, Class clazz) throws SQLException;
}
JdbcTemplate
public class JdbcTemplate {
private static DataSource dataSource = null;
private Connection connection = null;
static {
//初始化配置
MysqlDataSource mysql = new MysqlDataSource();
mysql.setURL("jdbc:mysql://localhost:3306/learn2020?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
mysql.setUser("root");
mysql.setPassword("123456");
dataSource = mysql;
}
public JdbcTemplate() {
try {//初始化连接
this.connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
}
public List<?> executeQuery(String sql, RowMapper<?> rowMapper, Object[] args, Class clazz) {
//try-with-resource
//对于实现了AutoClose接口的类自动处理
try (
//获取连接
Connection connection = this.connection;
//获取语句集
PreparedStatement prepareStatement = connection.prepareStatement(sql);
//执行语句集
ResultSet resultSet = this.executeQuery(prepareStatement, args);
) {
//处理结果集
return this.parseResultSet(resultSet, rowMapper, clazz);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
/**
* 赋值并查询
* @param ps
* @param args
* @return
* @throws SQLException
*/
private ResultSet executeQuery(PreparedStatement ps, Object[] args) throws SQLException {
if (!Objects.nonNull(args) || args.length == 0 || Objects.isNull(args[0])) return ps.executeQuery();
for (int i = 0; i < args.length; i++) {
ps.setObject(i, args[i]);
}
return ps.executeQuery();
}
/**
* 对结果集进行ORM映射
* @param resultSet
* @param rowMapper
* @param clazz
* @return
* @throws SQLException
*/
private List<?> parseResultSet(ResultSet resultSet, RowMapper rowMapper, Class clazz) throws SQLException {
List<Object> lists = new ArrayList<>();
int startIndex = 1;
while (resultSet.next()) {
lists.add(rowMapper.mapRow(resultSet, startIndex++, clazz));
}
return lists;
}
}
实体类
public class Member {
private String username;
private String password;
private int age;
@Override
public String toString() {
return "Member{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + 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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
测试方法
public static void main(String[] args) {
JdbcTemplate template = new JdbcTemplate();
List<?> objects = template.executeQuery("select * from member", Test::ORMResolve, new Object[0], Member.class);
System.out.println(objects);
}
//通过反射处理所有字段
public static Object ORMResolve(ResultSet rs, int num, Class clazz) {
try {
Object o = clazz.newInstance();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
field.set(o, rs.getObject(field.getName()));
}
return o;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
模板模式在源码中的体现
例如JDK中的abstractList
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
abstract public E get(int index);
...
}
这个get是一个抽象方法,不管子类的数据类型是如何实现的,具体如何取得,都有子类自己封装,调用者不用了解具体的取值过程.
可以看到有AbstractList extends AbstractCollection,所以同理也有AbstractSet和 AbstractMap.
同样的还有HttpServlet,有三个方法 service()和 doGet()、doPost()方法,都是模板方法的抽象实现.
像平常一般用的时候就可以看到
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
HttpServletRequest req和HttpServletResponse resp都是有值的,一般自身逻辑处理完后也是直接结束就可以了,剩下的流程也不用操心
总结
优点:
1、利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
2、将不同的代码不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。
3、把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则
缺点:
1、类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
2、类数量的增加,间接地增加了系统实现的复杂度。
3、继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。
适配器模式
适配器模式的目标
为一(调用接口)对多(目标接口)的不同接口之间作适配,消除不同接口之间的调用差异
具体工作
例如,客户端期望调用URI对应的方法
但是DispatcherServlet#doDispatch只调用支持期望(getHandlerAdapter)的适配器类的handle()方法,因为DispatcherServlet不可能对所有未知的Controller进行硬编码的方式,去判断调用
org.springframework.web.servlet.DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
...
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
所以通过注册解析(bean初始化的时候已经做了,存在ApplicationContext中 ,这里只是取值)
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
//根据上下文初始化
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
@Controller下所有被@RequestMapping(@GettingMapping和PostMapping只是RequestMapping的派生,可以理解为注解的继承 )注释的方法和对应参数(方法变量名-uri参数映射处理)
@RestController
public class HelloController {
//直接返回中文需要添加produces charset=utf8
//通过@RequestParam传递的中文却不需要,因为
//对于传递进来的参数在绑定过程中,已经进行了处理
@GetMapping(value = "/hello")
public String hello(){
return "hello";
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {...}
,之后HandlerAdapter根据uri调用对应的方法并根据uri的参数列表对照注册的变量映射赋值.