【示例一】回家过春节
原理
使用继承,子类中不需要实现那些重复的订票和庆祝团圆的代码了,避免了代码的重复;子类实现了不同方式的回家方法,把它栓入(hook)到父类中去,实现了完整的回家过年的逻辑。
架构图
源代码
HappyPeople.java
package pattern.part1.chapter2.template;
public abstract class HappyPeople {
public void celebrateSpringFestival() {
subscribeTicket();
travel();
celebrate();
}
protected final void subscribeTicket() {
//Buying ticket...
System.out.println("Buying ticket...");
}
protected abstract void travel();
protected final void celebrate() {
//Celebrating Chinese New Year...
System.out.println("Happy Chinese New Year!");
}
}
PassengerByTrain.java
package pattern.part1.chapter2.template;
public class PassengerByTrain extends HappyPeople {
@Override
protected void travel() {
//Travel by Train...
System.out.println("Travelling by Train...");
}
}
PassengerByCoach.java
package pattern.part1.chapter2.template;
public class PassengerByCoach extends HappyPeople {
@Override
protected void travel() {
// Travel by Coach...
System.out.println("Travelling by Coach...");
}
}
PassengerByAir.java
package pattern.part1.chapter2.template;
public class PassengerByAir extends HappyPeople {
@Override
protected void travel() {
//Traveling by Air...
System.out.println("Travelling by Air...");
}
}
HappyPeopleTestDrive.java
package pattern.part1.chapter2.template;
public class HappyPeopleTestDrive {
public static void main(String[] args) {
HappyPeople passengerByAir = new PassengerByAir();
HappyPeople passengerByCoach = new PassengerByCoach();
HappyPeople passengerByTrain = new PassengerByTrain();
System.out.println("Let's Go Home For A Grand Family Reunion...\n");
System.out.println("Tom is going home:");
passengerByAir.celebrateSpringFestival();
System.out.println("\nRoss is going home:");
passengerByCoach.celebrateSpringFestival();
System.out.println("\nCatherine is going home:");
passengerByTrain.celebrateSpringFestival();
}
}
运行结果
Let's Go Home For A Grand Family Reunion...
Tom is going home:
Buying ticket...
Travelling by Air...
Happy Chinese New Year!
Ross is going home:
Buying ticket...
Travelling by Coach...
Happy Chinese New Year!
Catherine is going home:
Buying ticket...
Travelling by Train...
Happy Chinese New Year!
示例二:引入回调(Callback)
原理
回调表示一段可执行逻辑的引用(或者指针),我们把该引用(或者指针)传递到另外一段逻辑(或者方法)里供这段逻辑适时调用。
回调在不同语言有不同的实现,例如,在C语言里经常使用函数指针实现回调,在C#语言里使用代理(delegate)实现,而在Java语言里使用内部匿名类实现回调。
架构图
源代码
ConnectionUtils.java
package pattern.part1.chapter2.callback;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
public class ConnectionUtils {
//we create a mock connection and a preparedStatement using easymock to show on how callback works
public static Connection getConnection() throws SQLException {
Connection conn = createMock(Connection.class);
PreparedStatement statement = createMock(PreparedStatement.class);
expect(statement.executeQuery()).andReturn(null);
expect(conn.prepareStatement((String) anyObject())).andReturn(statement);
replay(conn);
replay(statement);
return conn;
}
}
ResultSetHandler.java
package pattern.part1.chapter2.callback;
import java.sql.ResultSet;
public interface ResultSetHandler<T> {
public T handle(ResultSet rs);
}
SimpleJdbcQueryTemplate.java
package pattern.part1.chapter2.callback;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SimpleJdbcQueryTemplate {
public <T> T query(String queryString, ResultSetHandler<T> rsHandler) {
Connection connection = null;
PreparedStatement stmt = null;
try {
connection = ConnectionUtils.getConnection();//get a db connection.
stmt = connection.prepareStatement(queryString);
ResultSet rs = stmt.executeQuery();
return rsHandler.handle(rs);
} catch (SQLException ex) {
closeStatement(stmt); //close the statement
stmt = null;
releaseConnection(connection); //release connection
connection = null;
throw new RuntimeException("An sql exception occurred.", ex); //rethrow a runtime exception
} finally {
closeStatement(stmt); //close the statement
releaseConnection(connection); //release connection
}
}
private void releaseConnection(Connection connection) {
if (connection != null) {
try {
connection.close(); //close the connection or put it back to the connection pool
}
catch (SQLException ex) {
//todo handle SQLException
}
catch (Throwable ex) {
//todo handle other exception
}
}
}
private void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
}
catch (SQLException ex) {
//todo handle SQLException
}
catch (Throwable ex) {
//todo handle other exception
}
}
}
}
TemplateTestDrive.java
package pattern.part1.chapter2.callback;
import java.sql.ResultSet;
import static pattern.Asserts.assertTrue;
public class TemplateTestDrive {
public void testTemplate() {
boolean called = new SimpleJdbcQueryTemplate().
query("select * from db",
new ResultSetHandler<Boolean>() {
public Boolean handle(ResultSet rs) {
//logical to resolve query result...
return Boolean.TRUE;
}
});
//to verify result...
assertTrue(called);
}
public static void main(String[] args) {
new TemplateTestDrive().testTemplate();
}
}
运行结果
正常结束
总结
这一章我们在介绍模板方法模式之前介绍了DRY原则。重复的代码会带来维护的噩梦,DRY是一名优秀的软件开发人员必须恪守的原则之一。此原则看上去很简单,其实实现起来一点也不简单。在以下章节里,我们将继续碰到重复代码的“臭味”,我们会介绍更多的模式来防止代码重复。
在本章,为了解决回家过年的问题,我们使用了模板方法模式。模板方法模式非常简单,相信不少人在学习模式之前早就开始使用了。模板方法模式可以解决某些场景中的代码冗余问题,但也可能引入了类的泛滥问题,随后我们介绍了如何结合使用回调避免类的泛滥。使用回调可以避免类的泛滥,这并不是表示我们将使用带有回调的模板方法模式来替换所有的不带回调的模板方法模式,如果回调实现的接口较多,代码较为复杂时,把这些代码挤在一起会引起阅读问题。
在以下章节读者可以继续看到,如果需求变得更为复杂,我们就得需要更加灵活的设计,使用模板方法模式不能够成为新的复杂需求的解决方案,我们会在策略模式等相关章节继续以该问题为例展开深入讨论。