内容简介:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tonny_guan/article/details/86474194
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tonny_guan/article/details/86474194
设计模式是在特定场景下对特定问题的解决方案,这些解决方案是经过反复论证和测试总结出来的。实际上,除了软件设计,设计模式也被广泛应用于其他领域,比如UI设计和建筑设计等。Java软件 设计模式 大都来源于GoF的23种设计模式。
这段时间一直在录制Java EE视频课程,其中在JDBC(Java数据库连接)中使用了模板方法设计(Template Method),下面给大家分享一下。
###1. 什么是模板方法设计模式?
在生活中完成一些“任务”有着固定的步骤,例如,我要完成“喝茶”任务,需要的步骤如下:
①烧水→②沏茶→③喝茶
而很多任务也有类似的步骤,例如,我要完成“喝咖啡”任务,当然是速溶咖啡那种。需要的步骤如下:
①烧水→②冲咖啡→③喝咖啡
对应两个任务他们有类似的3个步骤,步骤①和③是相同的,而步骤②是不同的。这样可以设计一个父类TaskTemplate代码如下:
public abstract class TaskTemplate {
public final void 任务() {
// 步骤①
烧水();
// 步骤②
冲泡();
// 步骤③
喝();
}
private void 烧水() {
System.out.println("烧水...");
}
protected abstract void 冲泡();
private void 喝() {
System.out.println("喝...");
}
}
TaskTemplate是一个抽象类,其中“任务()”方法中定义了执行“任务”的流程,其中“烧水()”和“喝()”是两个具体方法,由于父类中无法确定冲泡什么,因此“冲泡()”方法是抽象方法,留给子类实现。“任务()”就是模板方法。
“喝茶”任务实现类TeaTask代码如下:
public class TeaTask extends TaskTemplate {
@Override
protected void 冲泡() {
System.out.println("来壶铁观音。");
}
}
“喝咖啡”任务实现类CoffeeTask代码如下:
public class CoffeeTask extends TaskTemplate {
@Override
protected void 冲泡() {
System.out.println("冲卡布奇诺咖啡+糖+奶。");
}
}
他们的类图如图1所示。
这就是模板方法设计模式了,那么如何使用呢?示例代码如下:
public class Main {
public static void main(String[] args) {
System.out.println("------喝茶任务------");
TaskTemplate template = new TeaTask();
template.任务();
System.out.println("------喝咖啡任务------");
template = new CoffeeTask();
template.任务();
}
}
输出结果如下:
------喝茶任务------ 烧水... 来壶铁观音。 喝... ------喝咖啡任务------ 烧水... 冲卡布奇诺咖啡+糖+奶。 喝...
上述代码模板子类是有名类,而有时候子类个数太多,也可以采用匿名内部类作为模板子类。修改Main调用代码如下:
public class Main {
public static void main(String[] args) {
System.out.println("------喝茶任务------");
TaskTemplate template = new TaskTemplate() { ①
@Override
protected void 冲泡() {
System.out.println("来壶铁观音。");
}
};
template.任务();
System.out.println("------喝咖啡任务------");
template = new TaskTemplate() { ②
@Override
protected void 冲泡() {
System.out.println("冲卡布奇诺咖啡+糖+奶。");
}
};
template.任务();
}
}
上述代码第①行是实现了喝茶任务子类功能,代码第②行是实现了喝咖啡任务子类功能。
###2. 糟糕的JDBC代码
上面的介绍的设计模式或许很容易理解,但是又有什么用途呢?使用设计模式是学习的难点。下面先来看看糟糕的JDBC代码:
public class Main {
public static void main(String[] args) {
//查询数据
read();
//数据插入
create();
//数据更新
update();
//删除数据
delete();
}
/**
* 查数据
*/
private static void read() {
// 载数据库驱动
loadDBDriver();
String sql = "select name, userid from user where userid > ? order by userid";
Connection connection = null;
PreparedStatement ps = null;
try {
// 创建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 0);
ResultSet rs = ps.executeQuery();
//遍历结果集
while (rs.next()) {
System.out.printf("name: %s id: %d \n",
rs.getString("name"),
rs.getInt("userid"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 插入数据
*/
private static void create() {
// 载数据库驱动
loadDBDriver();
String sql = "insert into user (userid, name) values (?, ?)";
Connection connection = null;
PreparedStatement ps = null;
try {
// 建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 999);
ps.setString(2, "Tony999");
// 执行 SQL 语句
int count = ps.executeUpdate();
System.out.printf("成功插入%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 更新数据
*/
private static void update() {
// 载数据库驱动
loadDBDriver();
String sql = "update user set name=? where userid =?";
Connection connection = null;
PreparedStatement ps = null;
try {
// 创建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setString(1, "Tom999");
ps.setInt(2, 999);
// 执行SQL语句
int count = ps.executeUpdate();
System.out.printf("成功更新%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 删除数据
*/
private static void delete() {
// 载数据库驱动
loadDBDriver();
String sql = "delete from user where userid = ?";
Connection connection = null;
PreparedStatement ps = null;
try {
// 创建数据库连接
connection = getConnection();
// 创建语句对象
ps = connection.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 999);
// 执行SQL语句
int count = ps.executeUpdate();
System.out.printf("成功删除%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 建立数据库连接
*
* @return 返回数据库连接对象
* @throws SQLException
*/
private static Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb?verifyServerCertificate=false&useSSL=false";
String user = "root";
String password = "12345";
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 加载数据库驱动
*/
private static void loadDBDriver() {
// 1.
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上述代码中访问数据的方法有4个read()、create()、update()和delete()。其中create()、update()和delete()三个方法代码非常相似,只是SQL语句和绑定参数不同而已。虽然read()方法与create()、update()和delete()方法不同,但是差别也不大。
JDBC代码主要的问题是:大量的重复代码!!!
###3. 在JDBC中使用模板设计方法模式
从上一节代码总结数据库编程一般过程,如图2所示。
从图3中可见查询(Read)过程最多需要7个步骤。修改(C插入、U更新、D删除)过程最多需要6个步骤。其中有些步骤是不变的,而有些步骤是可变的。如图3所示,查询过程中1、2、5和7步是不可变的所有查询都是一样的,而3、4和6步不同,第3步在“创建语句对象”时需要指定SQL语句,这是“此查询”与“彼查询”的不同之处;由于SQL语句的不同绑定参数也可能不同,所以第4步也是不同的;另外,第6步是“遍历结果集”也会根据查询的不同字段,以及字段提取后处理的方式不同而有所不同。
使用代码模板方法模式,可以将1、2、5和7步定义在父类在,将3、4和6步定义在子类中。代码如下:
public abstract class JdbcTemplate {
public final void query() {
// 1、载数据库驱动
loadDBDriver();
Connection connection = null;
PreparedStatement ps = null;
try {
// 2、创建数据库连接
connection = getConnection();
// 3、创建语句对象 4、绑定参数
ps = createPreparedStatement(connection);
// 5、执行查询
ResultSet rs = ps.executeQuery();
// 6、遍历结果集
while (rs.next()) {
processRow(rs);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 7、释放资源
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 遍历结果集时,处理结果集
* @param rs 结果集
* @throws SQLException
*/
public abstract void processRow(ResultSet rs) throws SQLException; ③
/**
* 创建语句对象,其中包括指定SQL语句,绑定参数。
* @param conn 连接对象
* @return 语句对象
* @throws SQLException
*/
public abstract PreparedStatement
createPreparedStatement(Connection conn) throws SQLException; ④
/**
* 建立数据库连接
*
* @return 返回数据库连接对象
* @throws SQLException
*/
private static Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb?verifyServerCertificate=false&useSSL=false";
String user = "root";
String password = "12345";
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 加载数据库驱动
*/
private static void loadDBDriver() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在查询方法中代码第①行调用抽象方法createPreparedStatement(connection)创建预处理的语句对象,事实上在创建语句对象时,还可以为其绑定参数,所以代码第①行调用createPreparedStatement(connection)过程中实现“3、创建语句对象”和“4、绑定参数”。
代码第②行是在遍历结果集过程中调用抽象方法processRow(rs)处理结果集。一般而言所有遍历结果集都是while (rs.next()) {…}循环语句实现的,只是提取的字段不同,提取之后的处理过程不同。
那么调用read()方法代码如下:
/**
* 查数据
*/
private static void read() {
String sql = "select name, userid from user where userid > ? order by userid";
JdbcTemplate template = new JdbcTemplate() { ①
@Override
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
// 绑定参数
PreparedStatement ps = conn.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 0);
return ps;
}
@Override
public void processRow(ResultSet rs) throws SQLException {
System.out.printf("name: %s id: %d \n",
rs.getString("name"),
rs.getInt("userid"));
}
}; ②
template.query(); ③
}
上述代码第①行~第②行采用匿名内部类子类化JdbcTemplate类,并且实例化它,而没有采用有名类子类化JdbcTemplate类,这是因为每一次查询都需要一个JdbcTemplate子类,以及该子类的实例。这样会需要创建很多个JdbcTemplate子类。代码第③行调用模板方法query()执行查询。
图4所示是修改过程,其中1、2、5和6步是不可变的所有修改(插入、删除和更新)都是一样的,而3和4步是不同的。
使用代码模板方法模式,可以将1、2、5和7步定义在父类在,将3、4和6步定义在子类中。代码如下:
public abstract class JdbcTemplate {
public final void update() {
// 1、载数据库驱动
loadDBDriver();
Connection connection = null;
PreparedStatement ps = null;
try {
// 2、创建数据库连接
connection = getConnection();
// 3、创建语句对象 4、绑定参数
ps = createPreparedStatement(connection); ①
// 5、执行SQL语句
int count = ps.executeUpdate();
System.out.printf("成功修改%d条数据.\n", count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6、释放资源
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
代码第①行的createPreparedStatement(connection)方法与查询时共用该方法,当子类实现该方法时创建预编译语句对象和绑定参数。
那么调用create()方法的代码如下:
/**
* 插入数据
*/
private static void create() {
String sql = "insert into user (userid, name) values (?, ?)";
JdbcTemplate template = new JdbcTemplate() {
@Override
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
// 绑定参数
PreparedStatement ps = conn.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 999);
ps.setString(2, "Tony999");
return ps;
}
@Override
public void processRow(ResultSet rs) throws SQLException {} ①
};
template.update();
}
插入数据的模板也是采用匿名内部类子类化JdbcTemplate,由于插入过程不需要遍历结果集,所以抽象方法processRow()采用空实现,见代码第①行。另外update()也是模板方法。
更新数据和删除数据方法与插入数据方法是类似的,代码如下:
/**
* 更新数据
*/
private static void update() {
String sql = "update user set name=? where userid =?";
JdbcTemplate template = new JdbcTemplate() {
@Override
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
// 绑定参数
PreparedStatement ps = conn.prepareStatement(sql);
// 绑定参数
ps.setString(1, "Tom999");
ps.setInt(2, 999);
return ps;
}
@Override
public void processRow(ResultSet rs) throws SQLException {
}
};
template.update();
}
/**
* 删除数据
*/
private static void delete() {
String sql = "delete from user where userid = ?";
JdbcTemplate template = new JdbcTemplate() {
@Override
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
// 绑定参数
PreparedStatement ps = conn.prepareStatement(sql);
// 绑定参数
ps.setInt(1, 999);
return ps;
}
@Override
public void processRow(ResultSet rs) throws SQLException {
}
};
template.update();
}
读者可以比较一下,采用了模板设计方法后是不是代码变得很简单了呢!
###4. 后记
JDBC模板子类不要采用有名子类化JDBC模板父类,这会使我们为每一个查询和修改操作而编写一个子类,这个数量会很多。
再有,从上面的代码可见,模板设计方法还是可以进行优化的。事实上还可以更加抽象一下,即采用接口替代两个抽象方法,这样会更加灵活,而且可以使用Lambda表达式替代内部类。这种方式就Spring框架的实现Jdbc模板的实现方法,感兴趣的同学可以看看Spring的源代码。另外,可以通过关东升老师《Java Web从入门到实战》视频课程第5章JDBC技术了解具体细节。
代码下载地址: https://github.com/tonyguan/JdbcTemplate
《Java Web从入门到实战》视频课程:
1、进入CSDN学院该课程
-
Design Patterns: Elements of Reusable Object-Oriented
(中文版《设计模式》)一书由Erich Gamma、Richard Helm、Ralph Johnson 和John Vlissides 合著(Addison-Wesley,1995),这四位作者常被称为“四人组”(Gang of
Four,GoF)。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Master Switch
Tim Wu / Knopf / 2010-11-2 / USD 27.95
In this age of an open Internet, it is easy to forget that every American information industry, beginning with the telephone, has eventually been taken captive by some ruthless monopoly or cartel. Wit......一起来看看 《The Master Switch》 这本书的介绍吧!