[設計模式]模板模式

 

【示例一】回家過春節

原理

使用繼承,子類中不須要實現那些重複的訂票和慶祝團圓的代碼了,避免了代碼的重複;子類實現了不一樣方式的回家方法,把它栓入(hook)到父類中去,實現了完整的回家過年的邏輯。html

架構圖

template.pattern

源代碼

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語言裏使用內部匿名類實現回調。java

架構圖

template.pattern.02

源代碼

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();
    }
}

運行結果

正常結束sql

總結

這一章咱們在介紹模板方法模式以前介紹了DRY原則。重複的代碼會帶來維護的噩夢,DRY是一名優秀的軟件開發人員必須恪守的原則之一。此原則看上去很簡單,其實實現起來一點也不簡單。在如下章節裏,咱們將繼續碰到重複代碼的「臭味」,咱們會介紹更多的模式來防止代碼重複。架構


在本章,爲了解決回家過年的問題,咱們使用了模板方法模式。模板方法模式很是簡單,相信很多人在學習模式以前早就開始使用了。模板方法模式能夠解決某些場景中的代碼冗餘問題,但也可能引入了類的泛濫問題,隨後咱們介紹瞭如何結合使用回調避免類的泛濫。使用回調能夠避免類的泛濫,這並非表示咱們將使用帶有回調的模板方法模式來替換全部的不帶回調的模板方法模式,若是回調實現的接口較多,代碼較爲複雜時,把這些代碼擠在一塊兒會引發閱讀問題。app


在如下章節讀者能夠繼續看到,若是需求變得更爲複雜,咱們就得須要更加靈活的設計,使用模板方法模式不可以成爲新的複雜需求的解決方案,咱們會在策略模式等相關章節繼續以該問題爲例展開深刻討論。ide

相關文章
相關標籤/搜索