代碼復用神器,模板模式實操分享
本文轉載自微信公眾號「 Java極客技術」,作者 鴨血粉絲。轉載本文請聯系 Java極客技術公眾號。
一、介紹
模板模式,顧名思義,定義一個模板,將部分邏輯以具體方法或者具體構造函數的形式實現,在抽象類中聲明一些抽象方法來迫使子類實現剩余的邏輯。
不同的子類可以以不同的方式實現這些抽象方法,從而對剩余的邏輯有不同的實現,這就是模板方法模式的用意。
模板模式涉及到三個角色:
- 抽象類(AbstractClass):實現了模板方法,定義了算法的骨架;
- 具體類(ConcreteClass):實現抽象類中的抽象方法,已完成完整的算法;
- 客戶角色:客戶類提出使用具體類的請求;
二、示例
舉個例子,以早上起床到上班所需要的操作為例,大致流程可以分為以下幾步:穿衣服、刷牙、洗臉、吃早餐等。男生和女生的操作可能有些區別。
我們創建一個抽象的類,定義好大致的操作流程,如下:
- /**
- * 抽象類
- */
- public abstract class AbstractPerson {
- /**
- * 定義操作流程
- */
- public void prepareGoWorking(){
- dressing();//穿衣服
- brushTeeth();//刷牙
- washFace();//洗臉
- eatBreakFast();//吃早餐
- }
- /**穿衣服*/
- protected abstract void dressing();
- /**刷牙*/
- protected void brushTeeth(){
- System.out.println("刷牙");
- }
- /**洗臉*/
- protected void washFace(){
- System.out.println("洗臉");
- }
- /**吃早餐*/
- protected abstract void eatBreakFast();
- }
因為男生和女生的行為不一樣,我們分別創建兩個具體類,如下:
- /**
- * 男生
- * 具體實現類
- */
- public class ManPerson extends AbstractPerson{
- @Override
- protected void dressing() {
- System.out.println("穿西裝");
- }
- @Override
- protected void eatBreakFast() {
- System.out.println("直接在公司吃早餐");
- }
- }
- /**
- * 女生
- * 具體實現類
- */
- public class WomanPerson extends AbstractPerson{
- @Override
- protected void dressing() {
- System.out.println("穿休閑衣服");
- }
- @Override
- protected void eatBreakFast() {
- System.out.println("在家弄點吃的,或者在外面買一點小吃");
- }
- }
創建一個客戶端,實現如下:
- public class TemplateClient {
- public static void main(String[] args) {
- //男生起床步驟
- ManPerson manPerson = new ManPerson();
- System.out.println("-----男生起床步驟----");
- manPerson.prepareGoWorking();
- System.out.println("-----女生起床步驟----");
- //女生起床步驟
- WomanPerson womanPerson = new WomanPerson();
- womanPerson.prepareGoWorking();
- }
- }
輸出結果:
- -----男生起床步驟----
- 穿西裝
- 刷牙
- 洗臉
- 直接在公司吃早餐
- -----女生起床步驟----
- 穿休閑衣服
- 刷牙
- 洗臉
- 在家弄點吃的,或者在外面買一點小吃
當然,模版模式的玩法,還不僅僅只有這些,還可以在模版模式中使用掛鉤(hook)。
什么是hook呢?存在一個空實現的方法,我們稱這種方法為hook。子類可以視情況來決定是否要覆蓋它。
還是以上面為例子,比如吃完早餐就要出門上班,選擇什么交通工具呢?
抽象類新增方法hook(),內容如下:
- /**
- * 抽象類
- */
- public abstract class AbstractPerson {
- /**
- * 定義操作流程
- */
- public void prepareGoWorking(){
- dressing();//穿衣服
- brushTeeth();//刷牙
- washFace();//洗臉
- eatBreakFast();//吃早餐
- hook();//掛鉤
- }
- /**穿衣服*/
- protected abstract void dressing();
- /**刷牙*/
- protected void brushTeeth(){
- System.out.println("刷牙");
- }
- /**洗臉*/
- protected void washFace(){
- System.out.println("洗臉");
- }
- /**吃早餐*/
- protected abstract void eatBreakFast();
- /**掛鉤*/
- protected void hook(){};
- }
男生具體實現類,重寫hook()方法,內容如下:
- /**
- * 男生
- * 具體實現類
- */
- public class ManPerson extends AbstractPerson{
- @Override
- protected void dressing() {
- System.out.println("穿西裝");
- }
- @Override
- protected void eatBreakFast() {
- System.out.println("直接在公司吃早餐");
- }
- @Override
- protected void hook() {
- System.out.println("乘地鐵上班");
- }
- }
運行測試類,男生具體實現類,輸出結果:
- -----男生起床步驟----
- 穿西裝
- 刷牙
- 洗臉
- 直接在公司吃早餐
- 乘地鐵上班
當然,還有其他的玩法,比如女生洗完臉之后,可能需要化妝,我們再次將抽象類進行處理,內容如下:
- /**
- * 抽象類
- */
- public abstract class AbstractPerson {
- /**
- * 定義操作流程
- */
- public void prepareGoWorking(){
- dressing();//穿衣服
- brushTeeth();//刷牙
- washFace();//洗臉
- //是否需要化妝,默認不化妝
- if(isMakeUp()){
- System.out.println("進行化妝");
- }
- eatBreakFast();//吃早餐
- hook();//掛鉤
- }
- /**是否需要化妝方法*/
- protected boolean isMakeUp(){
- return false;
- }
- /**穿衣服*/
- protected abstract void dressing();
- /**刷牙*/
- protected void brushTeeth(){
- System.out.println("刷牙");
- }
- /**洗臉*/
- protected void washFace(){
- System.out.println("洗臉");
- }
- /**吃早餐*/
- protected abstract void eatBreakFast();
- /**掛鉤*/
- protected void hook(){};
- }
女生具體實現類,重寫isMakeUp()方法,內容如下:
- /**
- * 女生
- * 具體實現類
- */
- public class WomanPerson extends AbstractPerson{
- @Override
- protected void dressing() {
- System.out.println("穿休閑衣服");
- }
- @Override
- protected void eatBreakFast() {
- System.out.println("在家弄點吃的,或者在外面買一點小吃");
- }
- @Override
- protected boolean isMakeUp() {
- return true;
- }
- }
運行測試類,女生具體實現類,輸出結果:
- -----女生起床步驟----
- 穿休閑衣服
- 刷牙
- 洗臉
- 進行化妝
- 在家弄點吃的,或者在外面買一點小吃
三、應用
模版設計模式,應用非常廣泛,比如javaEE中的servlet,當我們每創建一個servlet的時候,都會繼承HttpServlet,其實HttpServlet已經為我們提供一套操作流程,我們只需要重寫里面的方法即可!
HttpServlet 的部分源碼如下:
- public abstract class HttpServlet extends GenericServlet {
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- // ...
- }
- protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- String method = req.getMethod();
- if (method.equals(METHOD_GET)) {
- long lastModified = getLastModified(req);
- if (lastModified == -1) {
- // servlet doesn't support if-modified-since, no reason
- // to go through further expensive logic
- doGet(req, resp);
- } else {
- long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
- if (ifModifiedSince < lastModified) {
- // If the servlet mod time is later, call doGet()
- // Round down to the nearest second for a proper compare
- // A ifModifiedSince of -1 will always be less
- maybeSetLastModified(resp, lastModified);
- doGet(req, resp);
- } else {
- resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- }
- }
- } else if (method.equals(METHOD_HEAD)) {
- long lastModified = getLastModified(req);
- maybeSetLastModified(resp, lastModified);
- doHead(req, resp);
- } else if (method.equals(METHOD_POST)) {
- doPost(req, resp);
- } else if (method.equals(METHOD_PUT)) {
- doPut(req, resp);
- } else if (method.equals(METHOD_DELETE)) {
- doDelete(req, resp);
- } else if (method.equals(METHOD_OPTIONS)) {
- doOptions(req,resp);
- } else if (method.equals(METHOD_TRACE)) {
- doTrace(req,resp);
- } else {
- //
- // Note that this means NO servlet supports whatever
- // method was requested, anywhere on this server.
- //
- String errMsg = lStrings.getString("http.method_not_implemented");
- Object[] errArgs = new Object[1];
- errArgs[0] = method;
- errMsg = MessageFormat.format(errMsg, errArgs);
- resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
- }
- }
- // ...省略...
- }
自定義一個 HelloWorld 的 Servlet 類,如下:
- import java.io.*;
- import javax.servlet.*;
- import javax.servlet.http.*;
- public class HelloWorld extends HttpServlet {
- public void init() throws ServletException {
- // ...
- }
- public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- response.setContentType("text/html");
- PrintWriter out = response.getWriter();
- out.println("<h1>Hello World!</h1>");
- }
- public void destroy() {
- // ...
- }
- }
四、總結
模版模式有著許多的優點:
1、模板方法模式通過把不變的行為搬移到超類,去除了子類中的重復代碼;2、子類實現算法的某些細節,有助于算法的擴展;3、通過一個父類調用子類實現的操作,通過子類擴展增加新的行為,符合開放-封閉原則;
也有些缺點:1、每個不同的實現都需要定義一個子類,這會導致類的個數的增加,設計更加抽象
如果某些類有一些共同的行為,可以使用模版設計模式,創建一個抽象類,將共同的行為定義在抽象類中,可以有效的減少子類重復的代碼。