3.4 典型應(yīng)用:排序##
模板方法模式的一個(gè)非常典型的應(yīng)用,就是實(shí)現(xiàn)排序的功能。至于有些朋友認(rèn)為排序是策略模式的體現(xiàn),這很值得商榷。先來看看在Java中排序功能的實(shí)現(xiàn),然后再來說明為什么排序的實(shí)現(xiàn)主要體現(xiàn)了模板方法模式,而非策略模式。
在java.util包中,有一個(gè)Collections類,它里面實(shí)現(xiàn)了對(duì)列表排序的功能,它提供了一個(gè)靜態(tài)的sort方法,接受一個(gè)列表和一個(gè)Comparator接口的實(shí)例,這個(gè)方法實(shí)現(xiàn)的大致步驟是:
先把列表轉(zhuǎn)換成為對(duì)象數(shù)組;
通過Arrays的sort方法來對(duì)數(shù)組進(jìn)行排序,傳入Comparator接口的實(shí)例;
然后再把排好序的數(shù)組的數(shù)據(jù)設(shè)置回到原來的列表對(duì)象中去;
這其中的算法步驟是固定的,也就是算法骨架是固定的了,只是其中具體比較數(shù)據(jù)大小的步驟,需要由外部來提供,也就是傳入的Comparator接口的實(shí)例,就是用來實(shí)現(xiàn)數(shù)據(jù)比較的,在算法內(nèi)部會(huì)通過這個(gè)接口來回調(diào)具體的實(shí)現(xiàn)。
如果Comparator接口的compare()方法返回一個(gè)小于0的數(shù),表示被比較的兩個(gè)對(duì)象中,前面的對(duì)象小于后面的對(duì)象;如果返回一個(gè)等于0的數(shù),表示被比較的兩個(gè)對(duì)象相等;如果
返回一個(gè)大于0的數(shù),表示被比較的兩個(gè)對(duì)象中,前面的對(duì)象大于后面的對(duì)象。
下面一起看看使用Collections來對(duì)列表排序的例子,假如現(xiàn)在要實(shí)現(xiàn)對(duì)一個(gè)擁有多個(gè)用戶數(shù)據(jù)模型的列表進(jìn)行排序。
- 當(dāng)然,先要定義出封裝用戶數(shù)據(jù)的對(duì)象模型來,示例代碼如下:
/**
* 用戶數(shù)據(jù)模型
*/
public class UserModel {
private String userId,name;
private int age;
public UserModel(String userId,String name,int age) {
this.userId = userId;
this.name = name;
this.age = age;
}
public String getUserId() {
return userId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String toString(){
return "userId="+userId+",name="+name+",age="+age;
}
}
- 直接使用Collections來排序,寫個(gè)客戶端來測(cè)試一下,示例代碼如下:
public class Client {
public static void main(String[] args) {
//準(zhǔn)備要測(cè)試的數(shù)據(jù)
UserModel um1 = new UserModel("u1","user1",23);
UserModel um2 = new UserModel("u2","user2",22);
UserModel um3 = new UserModel("u3","user3",21);
UserModel um4 = new UserModel("u4","user4",24);
//添加到列表中
List<UserModel> list = new ArrayList<UserModel>();
list.add(um1);
list.add(um2);
list.add(um3);
list.add(um4);
System.out.println("排序前---------------------〉");
printList(list);
//實(shí)現(xiàn)比較器,也可以單獨(dú)用一個(gè)類來實(shí)現(xiàn)
Comparator c = new Comparator(){
public int compare(Object obj1, Object obj2) {
//假如實(shí)現(xiàn)按照年齡升序排序
UserModel tempUm1 = (UserModel)obj1;
UserModel tempUm2 = (UserModel)obj2;
if(tempUm1.getAge() > tempUm2.getAge()){
return 1;
}else if(tempUm1.getAge() == tempUm2.getAge()){
return 0;
}else if(tempUm1.getAge() < tempUm2.getAge()){
return -1;
}
return 0;
}
};
//排序
Collections.sort(list,c);
System.out.println("排序后---------------------〉");
printList(list);
}
private static void printList(List<UserModel> list){
for(UserModel um : list){
System.out.println(um);
}
}
}
運(yùn)行一下,結(jié)果如下所示:
排序前---------------------〉
userId=u1,name=user1,age=23
userId=u2,name=user2,age=22
userId=u3,name=user3,age=21
userId=u4,name=user4,age=24
排序后---------------------〉
userId=u3,name=user3,age=21
userId=u2,name=user2,age=22
userId=u1,name=user1,age=23
userId=u4,name=user4,age=24
- 小結(jié)一下
看了上面的示例,你會(huì)發(fā)現(xiàn),究竟列表會(huì)按照什么標(biāo)準(zhǔn)來排序,完全是依靠Comparator的具體實(shí)現(xiàn)
,上面實(shí)現(xiàn)的是按照年齡升序排列,你可以嘗試修改這個(gè)排序的比較器,那么得到的結(jié)果就會(huì)不一樣了。
也就是說,排序的算法是已經(jīng)固定了的,只是進(jìn)行排序比較的這一個(gè)步驟,由外部來實(shí)現(xiàn),我們就可以通過修改這個(gè)步驟的實(shí)現(xiàn),從而實(shí)現(xiàn)不同的排序方式
。因此從排序比較這個(gè)功能來看,是策略模式的體現(xiàn)。
但是請(qǐng)注意一點(diǎn),你只是修改的排序的比較方式,并不是修改了整個(gè)排序的算法
,事實(shí)上,現(xiàn)在Collections的sort()方法使用的是合并排序的算法,無論你怎么修改比較器的實(shí)現(xiàn),sort()方法實(shí)現(xiàn)的算法是不會(huì)改變的,不可能變成了冒泡排序或是其它的排序算法。
- 排序,到底是模板方法模式的實(shí)例,還是策略模式的實(shí)例,到底哪個(gè)說法更合適?
認(rèn)為是策略模式的實(shí)例的理由:
首先上面的排序?qū)崿F(xiàn),并沒有如同標(biāo)準(zhǔn)的模板方法模式那樣,使用子類來擴(kuò)展父類,至少從表面上看不太像模板方法模式;
其次排序使用的Comparator的實(shí)例,可以看成是不同的算法實(shí)現(xiàn),在具體排序時(shí),會(huì)選擇使用不同的Comparator實(shí)現(xiàn),就相當(dāng)于是在切換算法的實(shí)現(xiàn)。
因此認(rèn)為排序是策略模式的實(shí)例。
認(rèn)為是模板方法模式的實(shí)例的理由:
首先,模板方法模式的本質(zhì)是固定算法骨架,雖然使用繼承是標(biāo)準(zhǔn)的實(shí)現(xiàn)方式,但是通過回調(diào)來實(shí)現(xiàn),也不能說這就不是模板方法模式;
其次,從整體程序上看,排序的算法并沒有改變,不過是某些步驟的實(shí)現(xiàn)發(fā)生了變化,也就是說通過Comparator來切換的是不同的比較大小的實(shí)現(xiàn),相對(duì)于整個(gè)排序算法而言,它不過是其中的一個(gè)步驟而已。
因此認(rèn)為是模板方法模式的實(shí)例。
- 總結(jié)語:
排序的實(shí)現(xiàn),實(shí)際上組合使用了模板方法模式和策略模式,從整體來看是模板方法模式,但到了局部,比如排序比較算法的實(shí)現(xiàn)上,就是使用的策略模式了。
至于排序具體屬于誰的實(shí)例,這或許是個(gè)仁者見仁、智者見智的事情,我們也不想做老好人,我們傾向于說:排序是模板方法模式的實(shí)例
。畢竟設(shè)計(jì)模式的東西,要從整體上、設(shè)計(jì)上、從本質(zhì)上去看待問題,而不能從表面上或者是局部來看待問題
。
3.5 實(shí)現(xiàn)通用增刪改查##
對(duì)于實(shí)現(xiàn)通用的增刪改查的功能,基本上是每個(gè)做企業(yè)級(jí)應(yīng)用系統(tǒng)的公司都有的功能,實(shí)現(xiàn)的方式也是多種多樣,一種很常見的設(shè)計(jì)就是泛型加上模板方法模式,再加上使用Java回調(diào)技術(shù),尤其是在使用Spring和Hibernate等流行框架的應(yīng)用系統(tǒng)中很是常見
。
為了突出主題,以免分散大家的注意力,我們不去使用Spring和Hibernate這樣的流行框架,也不去使用泛型,只用模板方法模式來實(shí)現(xiàn)一個(gè)簡單的、用JDBC實(shí)現(xiàn)的通用增刪改查的功能。
先在數(shù)據(jù)庫中定義一個(gè)演示用的表,演示用的是Oracle數(shù)據(jù)庫,其實(shí)你可以用任意的數(shù)據(jù)庫,只是數(shù)據(jù)類型要做相應(yīng)的調(diào)整,簡單的數(shù)據(jù)字典如下:表名是tbl_user
- 定義相應(yīng)的數(shù)據(jù)對(duì)象來描述數(shù)據(jù),示例代碼如下:
/**
* 描述用戶的數(shù)據(jù)模型
*/
public class UserModel {
private String uuid;
private String name;
private int age;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "uuid="+uuid+",name="+name+",age="+age;
}
}
- 定義一個(gè)
用于封裝通用查詢數(shù)據(jù)的查詢用的數(shù)據(jù)模型
,由于這個(gè)查詢數(shù)據(jù)模型和上面定義的數(shù)據(jù)模型有很大一部分是相同的,因此讓這個(gè)查詢模型繼承上面的數(shù)據(jù)模型,然后添加上多出來的查詢條件。示例代碼如下:
/**
* 描述查詢用戶的條件數(shù)據(jù)的模型
*/
public class UserQueryModel extends UserModel{
/**
* 年齡是一個(gè)區(qū)間查詢,也就是年齡查詢的條件可以是:
* age >= 條件值1 and age <= 條件值2
* 把UserModel中的age當(dāng)作條件值1,
* 這里定義的age2當(dāng)作條件值2
*/
private int age2;
public int getAge2() {
return age2;
}
public void setAge2(int age2) {
this.age2 = age2;
}
}
- 為了讓大家能更好的理解這個(gè)通用的實(shí)現(xiàn),先不去使用模板方法模式,直接使用JDBC來實(shí)現(xiàn)增刪改查的功能。
所有的方法都需要和數(shù)據(jù)庫進(jìn)行連接,因此先把和數(shù)據(jù)庫連接的公共方法定義出來,沒有使用連接池,用最簡單的JDBC自己連接,示例代碼如下:
/**
* 獲取與數(shù)據(jù)庫的連接
* @return 數(shù)據(jù)庫連接
* @throws Exception
*/
private Connection getConnection()throws Exception{
Class.forName("你用的數(shù)據(jù)庫對(duì)應(yīng)的JDBC驅(qū)動(dòng)類");
return DriverManager.getConnection(
"連接數(shù)據(jù)庫的URL",
"用戶名","密碼");
}
使用純JDBC來實(shí)現(xiàn)新增的功能,示例代碼如下:
public void create(UserModel um) {
Connection conn = null;
try {
conn = this.getConnection();
String sql = "insert into tbl_user values(?,?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, um.getUuid());
pstmt.setString(2, um.getName());
pstmt.setInt(3, um.getAge());
pstmt.executeUpdate();
pstmt.close();
} catch (Exception err) {
err.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
修改和刪除的功能跟新增差不多,只是sql不同,還有設(shè)置sql中變量值不同,這里就不去寫了。
接下來看看查詢方面的功能,查詢方面只做一個(gè)通用的查詢實(shí)現(xiàn),其它查詢的實(shí)現(xiàn)基本上也差不多,示例代碼如下:
public Collection getByCondition(UserQueryModel uqm){
Collection col = new ArrayList();
Connection conn = null;
try{
conn = this.getConnection();
String sql = "select * from tbl_user where 1=1 ";
sql = this.prepareSql(sql, uqm);
PreparedStatement pstmt = conn.prepareStatement(sql);
this.setValue(pstmt, uqm);
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
col.add(this.rs2Object(rs));
}
rs.close();
pstmt.close();
}catch(Exception err){
err.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return col;
}
/**
* 為通用查詢動(dòng)態(tài)的拼接sql的條件部分,基本思路是:
* 如果用戶填寫了相應(yīng)的條件,那么才在sql中添加對(duì)應(yīng)的條件
* @param sql sql的主干部分
* @param uqm 封裝查詢條件的數(shù)據(jù)模型
* @return 拼接好的sql語句
*/
private String prepareSql(String sql,UserQueryModel uqm){
StringBuffer buffer = new StringBuffer();
buffer.append(sql);
//絕對(duì)匹配
if(uqm.getUuid()!=null&& uqm.getUuid().trim().length()>0){
buffer.append(" and uuid=? ");
}
//模糊匹配
if(uqm.getName()!=null&& uqm.getName().trim().length()>0){
buffer.append(" and name like ? ");
}
//區(qū)間匹配
if(uqm.getAge() > 0){
buffer.append(" and age >=? ");
}
if(uqm.getAge2() > 0){
buffer.append(" and age <=? ");
}
return buffer.toString();
}
/**
* 為通用查詢的sql動(dòng)態(tài)設(shè)置條件的值
* @param pstmt 預(yù)處理查詢sql的對(duì)象
* @param uqm 封裝查詢條件的數(shù)據(jù)模型
* @throws Exception
*/
private void setValue(PreparedStatement pstmt,UserQueryModel uqm)throws Exception{
int count = 1;
if(uqm.getUuid()!=null && uqm.getUuid().trim().length()>0){
pstmt.setString(count, uqm.getUuid());
count++;
}
if(uqm.getName()!=null && uqm.getName().trim().length()>0){
pstmt.setString(count, "%"+uqm.getName()+"%");
count++;
}
if(uqm.getAge() > 0){
pstmt.setInt(count, uqm.getAge());
count++;
}
if(uqm.getAge2() > 0){
pstmt.setInt(count, uqm.getAge2());
count++;
}
}
/**
* 把查詢返回的結(jié)果集轉(zhuǎn)換成為對(duì)象
* @param rs 查詢返回的結(jié)果集
* @return 查詢返回的結(jié)果集轉(zhuǎn)換成為對(duì)象
* @throws Exception
*/
private UserModel rs2Object(ResultSet rs)throws Exception{
UserModel um = new UserModel();
String uuid = rs.getString("uuid");
String name = rs.getString("name");
int age = rs.getInt("age");
um.setAge(age);
um.setName(name);
um.setUuid(uuid);
return um;
}
- 基本的JDBC實(shí)現(xiàn)寫完了,該來看看如何把模板方法模式用上了。
模板方法是要定義算法的骨架,而具體步驟的實(shí)現(xiàn)還是由子類來完成,因此把固定的算法骨架抽取出來,就成了使用模板方法模式的重點(diǎn)了
。
首先來觀察新增、修改、刪除的功能,發(fā)現(xiàn)哪些是固定的,哪些是變化的呢?分析發(fā)現(xiàn)變化的只有Sql語句,還有為Sql中的“?”設(shè)置值的語句,真正執(zhí)行sql的過程是差不多的,是不變化的。
再來觀察查詢的方法,查詢的過程是固定的,變化的除了有Sql語句、為Sql中的“?”設(shè)置值的語句之外,還多了一個(gè)如何把查詢回來的結(jié)果集轉(zhuǎn)換成對(duì)象集的實(shí)現(xiàn)。
好了,找到變與不變之處,就可以來設(shè)計(jì)模板了,先定義出增刪改查各自的實(shí)現(xiàn)步驟來
,也就是定義好各自的算法骨架,然后把變化的部分定義成為原語操作或鉤子操作
,如果一定要子類實(shí)現(xiàn)的那就定義成為原語操作;在模板中提供默認(rèn)實(shí)現(xiàn),且不強(qiáng)制子類實(shí)現(xiàn)的功能定義成為鉤子操作就可以了
。
另外,來回需要傳遞數(shù)據(jù),由于是通用的方法,就不能用具體的類型了,又不考慮泛型,那么就定義成Object類型好了。
根據(jù)上面的思路,一個(gè)簡單的、能實(shí)現(xiàn)對(duì)數(shù)據(jù)進(jìn)行增刪改查的模板就可以實(shí)現(xiàn)出來了,完整的示例代碼如下:
/**
* 一個(gè)簡單的實(shí)現(xiàn)JDBC增刪改查功能的模板
*/
public abstract class JDBCTemplate {
/**
* 定義當(dāng)前的操作類型是新增
*/
protected final static int TYPE_CREATE = 1;
/**
* 定義當(dāng)前的操作類型是修改
*/
protected final static int TYPE_UPDATE = 2;
/**
* 定義當(dāng)前的操作類型是刪除
*/
protected final static int TYPE_DELETE = 3;
/**
* 定義當(dāng)前的操作類型是按條件查詢
*/
protected final static int TYPE_CONDITION = 4;
/*---------------------模板方法---------------------*/
/**
* 實(shí)現(xiàn)新增的功能
* @param obj 需要被新增的數(shù)據(jù)對(duì)象
*/
public final void create(Object obj){
//1:獲取新增的sql
String sql = this.getMainSql(TYPE_CREATE);
//2:調(diào)用通用的更新實(shí)現(xiàn)
this.executeUpdate(sql, TYPE_CREATE,obj);
}
/**
* 實(shí)現(xiàn)修改的功能
* @param obj 需要被修改的數(shù)據(jù)對(duì)象
*/
public final void update(Object obj){
//1:獲取修改的sql
String sql = this.getMainSql(TYPE_UPDATE);
//2:調(diào)用通用的更新實(shí)現(xiàn)
this.executeUpdate(sql, TYPE_UPDATE,obj);
}
/**
* 實(shí)現(xiàn)刪除的功能
* @param obj 需要被刪除的數(shù)據(jù)對(duì)象
*/
public final void delete(Object obj){
//1:獲取刪除的sql
String sql = this.getMainSql(TYPE_DELETE);
//2:調(diào)用通用的更新實(shí)現(xiàn)
this.executeUpdate(sql, TYPE_DELETE,obj);
}
/**
* 實(shí)現(xiàn)按照條件查詢的功能
* @param qm 封裝查詢條件的數(shù)據(jù)對(duì)象
* @return 符合條件的數(shù)據(jù)對(duì)象集合
*/
public final Collection getByCondition(Object qm){
//1:獲取查詢的sql
String sql = this.getMainSql(TYPE_CONDITION);
//2:調(diào)用通用的查詢實(shí)現(xiàn)
return this.getByCondition(sql, qm);
}
/*---------------------原語操作---------------------*/
/**
* 獲取操作需要的主干sql
* @param type 操作類型
* @return 操作對(duì)應(yīng)的主干sql
*/
protected abstract String getMainSql(int type);
/**
* 為更新操作的sql中的"?"設(shè)置值
* @param type 操作類型
* @param pstmt PreparedStatement對(duì)象
* @param obj 操作的數(shù)據(jù)對(duì)象
* @throws Exception
*/
protected abstract void setUpdateSqlValue(int type, PreparedStatement pstmt,Object obj) throws Exception;
/**
* 為通用查詢動(dòng)態(tài)的拼接sql的條件部分,基本思路是:
* 只有用戶填寫了相應(yīng)的條件,那么才在sql中添加對(duì)應(yīng)的條件
* @param sql sql的主干部分
* @param qm 封裝查詢條件的數(shù)據(jù)模型
* @return 拼接好的sql語句
*/
protected abstract String prepareQuerySql(String sql,Object qm);
/**
* 為通用查詢的sql動(dòng)態(tài)設(shè)置條件的值
* @param pstmt 預(yù)處理查詢sql的對(duì)象
* @param qm 封裝查詢條件的數(shù)據(jù)模型
* @throws Exception
*/
protected abstract void setQuerySqlValue(PreparedStatement pstmt,Object qm)throws Exception;
/**
* 把查詢返回的結(jié)果集轉(zhuǎn)換成為數(shù)據(jù)對(duì)象
* @param rs 查詢返回的結(jié)果集
* @return 查詢返回的結(jié)果集轉(zhuǎn)換成為數(shù)據(jù)對(duì)象
* @throws Exception
*/
protected abstract Object rs2Object(ResultSet rs)throws Exception;
/*---------------------鉤子操作---------------------*/
/**
* 連接數(shù)據(jù)庫的默認(rèn)實(shí)現(xiàn),可以被子類覆蓋
* @return 數(shù)據(jù)庫連接
* @throws Exception
*/
protected Connection getConnection()throws Exception{
Class.forName("你用的數(shù)據(jù)庫對(duì)應(yīng)的JDBC驅(qū)動(dòng)類");
return DriverManager.getConnection(
"連接數(shù)據(jù)庫的URL",
"用戶名","密碼");
}
/**
* 執(zhí)行查詢
* @param sql 查詢的主干sql語句
* @param qm 封裝查詢條件的數(shù)據(jù)模型
* @return 查詢后的結(jié)果對(duì)象集合
*/
protected Collection getByCondition(String sql,Object qm){
Collection col = new ArrayList();
Connection conn = null;
try{
//調(diào)用鉤子方法
conn = this.getConnection();
//調(diào)用原語操作
sql = this.prepareQuerySql(sql, qm);
PreparedStatement pstmt = conn.prepareStatement(sql);
//調(diào)用原語操作
this.setQuerySqlValue(pstmt, qm);
ResultSet rs = pstmt.executeQuery();
while(rs.next()){
//調(diào)用原語操作
col.add(this.rs2Object(rs));
}
rs.close();
pstmt.close();
}catch(Exception err){
err.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return col;
}
/**
* 執(zhí)行更改數(shù)據(jù)的sql語句,包括增刪改的功能
* @param sql 需要執(zhí)行的sql語句
* @param callback 回調(diào)接口,回調(diào)為sql語句賦值的方法
*/
protected void executeUpdate(String sql,int type,Object obj){
Connection conn = null;
try{
//調(diào)用鉤子方法
conn = this.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
//調(diào)用原語操作
this.setUpdateSqlValue(type,pstmt,obj);
pstmt.executeUpdate();
pstmt.close();
}catch(Exception err){
err.printStackTrace();
}finally{
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 簡單但是可以通用的JDBC模板做好了,看看如何使用這個(gè)模板來實(shí)現(xiàn)具體的增刪改查功能,示例代碼如下:
/**
* 具體的實(shí)現(xiàn)用戶管理的增刪改查功能
*/
public class UserJDBC extends JDBCTemplate{
protected String getMainSql(int type) {
//根據(jù)操作類型,返回相應(yīng)的主干sql語句
String sql = "";
if(type == TYPE_CREATE){
sql = "insert into tbl_user values(?,?,?)";
}else if(type == TYPE_DELETE){
sql = "delete from tbl_user where uuid=?";
}else if(type == TYPE_UPDATE){
sql = "update tbl_user set name=?,age=? where uuid=?";
}else if(type == TYPE_CONDITION){
sql = "select * from tbl_user where 1=1 ";
}
return sql;
}
protected void setUpdateSqlValue(int type, PreparedStatement pstmt,Object obj) throws Exception{
//設(shè)置增、刪、改操作的sql中"?"對(duì)應(yīng)的值
if(type == TYPE_CREATE){
this.setCreateValue(pstmt, (UserModel)obj);
}else if(type == TYPE_DELETE){
this.setDeleteValue(pstmt, (UserModel)obj);
}else if(type == TYPE_UPDATE){
this.setUpdateValue(pstmt, (UserModel)obj);
}
}
protected Object rs2Object(ResultSet rs)throws Exception{
UserModel um = new UserModel();
String uuid = rs.getString("uuid");
String name = rs.getString("name");
int age = rs.getInt("age");
um.setAge(age);
um.setName(name);
um.setUuid(uuid);
return um;
}
protected String prepareQuerySql(String sql,Object qm){
UserQueryModel uqm = (UserQueryModel)qm;
StringBuffer buffer = new StringBuffer();
buffer.append(sql);
if(uqm.getUuid()!=null&& uqm.getUuid().trim().length()>0){
buffer.append(" and uuid=? ");
}
if(uqm.getName()!=null&& uqm.getName().trim().length()>0){
buffer.append(" and name like ? ");
}
if(uqm.getAge() > 0){
buffer.append(" and age >=? ");
}
if(uqm.getAge2() > 0){
buffer.append(" and age <=? ");
}
return buffer.toString();
}
protected void setQuerySqlValue(PreparedStatement pstmt, Object qm)throws Exception{
UserQueryModel uqm = (UserQueryModel)qm;
int count = 1;
if(uqm.getUuid()!=null&& uqm.getUuid().trim().length()>0){
pstmt.setString(count, uqm.getUuid());
count++;
}
if(uqm.getName()!=null&& uqm.getName().trim().length()>0){
pstmt.setString(count, "%"+uqm.getName()+"%");
count++;
}
if(uqm.getAge() > 0){
pstmt.setInt(count, uqm.getAge());
count++;
}
if(uqm.getAge2() > 0){
pstmt.setInt(count, uqm.getAge2());
count++;
}
}
private void setCreateValue(PreparedStatement pstmt,UserModel um)throws Exception{
pstmt.setString(1, um.getUuid());
pstmt.setString(2, um.getName());
pstmt.setInt(3, um.getAge());
}
private void setUpdateValue(PreparedStatement pstmt,UserModel um)throws Exception{
pstmt.setString(1, um.getName());
pstmt.setInt(2, um.getAge());
pstmt.setString(3, um.getUuid());
}
private void setDeleteValue(PreparedStatement pstmt,UserModel um)throws Exception{
pstmt.setString(1, um.getUuid());
}
}
看到這里,可能有些朋友會(huì)想,為何不把準(zhǔn)備sql的方法、為sql中“?”賦值的方法、還有結(jié)果集映射成為對(duì)象的方法也做成公共的呢?
其實(shí)這些方法是可以考慮做成公共的,用反射機(jī)制就可以實(shí)現(xiàn),但是這里為了突出模板方法模式的使用,免得加的東西太多,把大家搞迷惑了。
事實(shí)上,用模板方法加上泛型再加上反射的技術(shù),就可以實(shí)現(xiàn)可重用的,使用模板時(shí)幾乎不用再寫代碼的數(shù)據(jù)層實(shí)現(xiàn),這里就不去展開了。
- 享受的時(shí)刻到了,來寫個(gè)客戶端,使用UserJDBC的實(shí)現(xiàn),示例代碼如下:
public class Client {
public static void main(String[] args) {
UserJDBC uj = new UserJDBC();
//先新增幾條
UserModel um1 = new UserModel();
um1.setUuid("u1");
um1.setName("張三");
um1.setAge(22);
uj.create(um1);
UserModel um2 = new UserModel();
um2.setUuid("u2");
um2.setName("李四");
um2.setAge(25);
uj.create(um2);
UserModel um3 = new UserModel();
um3.setUuid("u3");
um3.setName("王五");
um3.setAge(32);
uj.create(um3);
//測(cè)試修改
um3.setName("王五被改了");
um3.setAge(35);
uj.update(um3);
//測(cè)試查詢
UserQueryModel uqm = new UserQueryModel();
uqm.setAge(20);
uqm.setAge2(36);
Collection<UserModel> col = uj.getByCondition(uqm);
for(UserModel tempUm : col){
System.out.println(tempUm);
}
}
}
3.6 模板方法模式的優(yōu)缺點(diǎn)##
- 實(shí)現(xiàn)代碼復(fù)用
模板方法模式是一種實(shí)現(xiàn)代碼復(fù)用的很好的手段。通過把子類的公共功能提煉和抽取,把公共部分放到模板里面去實(shí)現(xiàn)。
- 算法骨架不容易升級(jí)
模板方法模式最基本的功能就是通過模板的制定,把算法骨架完全固定下來。事實(shí)上模板和子類是非常耦合的,如果要對(duì)模板中的算法骨架進(jìn)行變更,可能就會(huì)要求所有相關(guān)的子類進(jìn)行相應(yīng)的變化
。所以抽取算法骨架的時(shí)候要特別小心,盡量確保是不會(huì)變化的部分才放到模板中。
3.7 思考模板方法模式##
- 模板方法模式的本質(zhì)
模板方法模式的本質(zhì):固定算法骨架。
模板方法模式主要是通過制定模板,把算法步驟固定下來,至于誰來實(shí)現(xiàn),模板可以自己提供實(shí)現(xiàn),也可以由子類去實(shí)現(xiàn),還可以通過回調(diào)機(jī)制讓其它類來實(shí)現(xiàn)。
通過固定算法骨架,來約束子類的行為,并在特定的擴(kuò)展點(diǎn),來讓子類進(jìn)行功能擴(kuò)展,從而讓程序既有很好的復(fù)用性,又有較好的擴(kuò)展性。
- 對(duì)設(shè)計(jì)原則的體現(xiàn)
模板方法很好的體現(xiàn)了開閉原則和里氏替換原則。
首先從設(shè)計(jì)上,先分離變與不變,然后把不變的部分抽取出來
,定義到父類里面,比如算法骨架,比如一些公共的、固定的實(shí)現(xiàn)等等。這些不變的部分被封閉起來,盡量不去修改它了,要擴(kuò)展新的功能,那就使用子類來擴(kuò)展,通過子類來實(shí)現(xiàn)可變化的步驟,對(duì)于這種新增功能的做法是開放的。
其次,能夠?qū)崿F(xiàn)統(tǒng)一的算法骨架,通過切換不同的具體實(shí)現(xiàn)來切換不同的功能
,一個(gè)根本原因就是里氏替換原則,遵循這個(gè)原則,保證所有的子類實(shí)現(xiàn)的是同一個(gè)算法模板,并能在使用模板的地方,根據(jù)需要,切換不同的具體實(shí)現(xiàn)。
- 何時(shí)選用模板方法模式
建議在如下情況中,選用模板方法模式:
需要固定定義算法骨架,實(shí)現(xiàn)一個(gè)算法的不變的部分,并把可變的行為留給子類來實(shí)現(xiàn)的情況;
各個(gè)子類中具有公共行為,應(yīng)該抽取出來,集中在一個(gè)公共類中去實(shí)現(xiàn),從而避免代碼重復(fù);
需要控制子類擴(kuò)展的情況。模板方法模式會(huì)在特定的點(diǎn)來調(diào)用子類的方法,這樣只允許在這些點(diǎn)進(jìn)行擴(kuò)展;
3.8 相關(guān)模式##
- 模板方法模式和工廠方法模式
這兩個(gè)模式可以配合使用。
模板方法模式可以通過工廠方法來獲取需要調(diào)用的對(duì)象。
- 模板方法模式和策略模式
這兩個(gè)模式的功能有些相似,但是是有區(qū)別的。
從表面上看,兩個(gè)模式都能實(shí)現(xiàn)算法的封裝,但是模板方法封裝的是算法的骨架,這個(gè)算法骨架是不變的,變化的是算法中某些步驟的具體實(shí)現(xiàn);而策略模式是把某個(gè)步驟的具體實(shí)現(xiàn)算法封裝起來,所有封裝的算法對(duì)象是等價(jià)的,可以相互替換
。
因此,可以在模板方法中使用策略模式,就是把那些變化的算法步驟通過使用策略模式來實(shí)現(xiàn)
,但是具體選取哪個(gè)策略還是要由外部來確定,而整體的算法步驟,也就是算法骨架就由模板方法來定義了
。