溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java設(shè)計(jì)模式中模板方法模式的示例分析

發(fā)布時(shí)間:2021-09-15 16:49:13 來源:億速云 閱讀:120 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)Java設(shè)計(jì)模式中模板方法模式的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過來看看吧。

模板方法模式

在程序開發(fā)中,經(jīng)常會(huì)遇到這種情況:某個(gè)方法要實(shí)現(xiàn)的算法需要多個(gè)步驟,但其中有一些步驟是固定不變的,而另一些步驟則是不固定的。為了提高代碼的可擴(kuò)展性和可維護(hù)性,模板方法模式在這種場(chǎng)景下就派上了用場(chǎng)。

譬如制作一節(jié)網(wǎng)課的步驟可以簡(jiǎn)化為4個(gè)步驟:

1.制作PPT

2.錄制視頻

3.編寫筆記

4.提供課程資料

其中1、2、3的動(dòng)作在所有課程中的固定不變的,步驟3可有可無,步驟4在每個(gè)課程都不同(有些課程需要提供源代碼,有些需要提供圖片文件等)

我們可以在父類中確定整個(gè)流程的循序,并實(shí)現(xiàn)固定不變的步驟,而把不固定的步驟留給子類實(shí)現(xiàn)。甚至可以通過一個(gè)鉤子方法,讓子類來決定流程中某個(gè)方法的執(zhí)行與否

介紹

模板方法模式:定義一個(gè)操作中算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。模板方法模式是一種基于繼承的代碼復(fù)用技術(shù),它是一種類行為型模式。

角色

AbstractClass(抽象類):在抽象類中定義了一系列基本操作(PrimitiveOperations),這些基本操作可以是具體的,也可以是抽象的,每一個(gè)基本操作對(duì)應(yīng)算法的一個(gè)步驟,在其子類中可以重定義或?qū)崿F(xiàn)這些步驟。同時(shí),在抽象類中實(shí)現(xiàn)了一個(gè)模板方法(Template Method),用于定義一個(gè)算法的框架,模板方法不僅可以調(diào)用在抽象類中實(shí)現(xiàn)的基本方法,也可以調(diào)用在抽象類的子類中實(shí)現(xiàn)的基本方法,還可以調(diào)用其他對(duì)象中的方法。

ConcreteClass(具體子類):它是抽象類的子類,用于實(shí)現(xiàn)在父類中聲明的抽象基本操作以完成子類特定算法的步驟,也可以覆蓋在父類中已經(jīng)實(shí)現(xiàn)的具體基本操作。

一個(gè)模板方法是定義在抽象類中的、把基本操作方法組合在一起形成一個(gè)總算法或一個(gè)總行為的方法。這個(gè)模板方法定義在抽象類中,并由子類不加以修改地完全繼承下來。模板方法是一個(gè)具體方法,它給出了一個(gè)頂層邏輯框架,而邏輯的組成步驟在抽象類中可以是具體方法,也可以是抽象方法。

基本方法是實(shí)現(xiàn)算法各個(gè)步驟的方法,是模板方法的組成部分?;痉椒ㄓ挚梢苑譃槿N:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。

  • 抽象方法:一個(gè)抽象方法由抽象類聲明、由其具體子類實(shí)現(xiàn)。

  • 具體方法:一個(gè)具體方法由一個(gè)抽象類或具體類聲明并實(shí)現(xiàn),其子類可以進(jìn)行覆蓋也可以直接繼承。

  • 鉤子方法:可以與一些具體步驟 “掛鉤” ,以實(shí)現(xiàn)在不同條件下執(zhí)行模板方法中的不同步驟

代碼演示

所以我們通過模板方法模式,在抽象類中把整個(gè)流程固定下來,其中1、2、3的實(shí)現(xiàn)在抽象類中完成,3的執(zhí)行與否則由子類通過鉤子方法來控制,4則由子類來實(shí)現(xiàn)

抽象類定義如下:

//抽象課程類
public abstract class Course
{
    protected final void makeCourse() {
        this.makePPT();
        this.makeVideo();
        if (needWriteArticle()) {
            this.writeArticle();
        }
        this.packageCourse();
    }
    //final表示子類繼承后,無法修改父類該方法
    final void makePPT() {
        System.out.println("1. 制作PPT");
    }
    final void makeVideo() {
        System.out.println("2. 制作視頻");
    }
    final void writeArticle() {
        System.out.println("3. 編寫課程筆記");
    }
    //鉤子方法
    protected boolean needWriteArticle() {
        return false;
    }
   //抽象方法,留給子類去實(shí)現(xiàn)
    abstract void packageCourse();
}

其中的 makeCourse 方法是模板方法,它定義了制作網(wǎng)課的基本流程,makePPT、makeVideo、writeArticle 這三個(gè)步驟在所有課程中都是固定的,所以用 final 關(guān)鍵字修飾;packageCourse 方法在所有課程中都可能不一樣,所以聲明為抽象方法,由子類自行實(shí)現(xiàn);鉤子方法 needWriteArticle 返回一個(gè) boolean 類型的值,控制是否編寫課程筆記

子類 JavaCourse,實(shí)現(xiàn)了抽象方法 packageCourse,重寫了鉤子方法 needWriteArticle

public class JavaCourse extends Course {
    @Override
    void packageCourse() {
        System.out.println("4. 提供Java課程源代碼");
    }
    @Override
    protected boolean needWriteArticle() {
        return true;
    }
}

子類 FECourse,實(shí)現(xiàn)了抽象方法 packageCourse,重寫了鉤子方法 needWriteArticle,其中把鉤子方法的結(jié)果交給客戶端確定

public class JavaCourse extends Course {
    @Override
    void packageCourse() {
        System.out.println("4. 提供Java課程源代碼");
    }
    @Override
    protected boolean needWriteArticle() {
        return true;
    }
}

客戶端測(cè)試

public class TEST
{
    public static void main(String[] args) {
                System.out.println("Java課程start---");
                Course javaCourse = new JavaCourse();
                javaCourse.makeCourse();
                System.out.println("Java課程end---\n");
                System.out.println("前端課程start---");
                Course feCourse = new FECourse(false);
                feCourse.makeCourse();
                System.out.println("前端課程end---");
    }
}

輸出結(jié)果

Java課程start---
1. 制作PPT
2. 制作視頻
3. 編寫筆記
4. 提供Java課程源代碼
Java課程end---

前端課程start---
1. 制作PPT
2. 制作視頻
4.1 提供課程的前端代碼
4.2 提供課程的圖片等多媒體素材
前端課程end---

模板方法模式總結(jié)

優(yōu)點(diǎn)

  • 在父類中形式化地定義一個(gè)算法,而由它的子類來實(shí)現(xiàn)細(xì)節(jié)的處理,在子類實(shí)現(xiàn)詳細(xì)的處理算法時(shí)并不會(huì)改變算法中步驟的執(zhí)行次序。

  • 模板方法模式是一種代碼復(fù)用技術(shù),它在類庫設(shè)計(jì)中尤為重要,它提取了類庫中的公共行為,將公共行為放在父類中,而通過其子類來實(shí)現(xiàn)不同的行為,它鼓勵(lì)我們恰當(dāng)使用繼承來實(shí)現(xiàn)代碼復(fù)用

  • 可實(shí)現(xiàn)一種反向控制結(jié)構(gòu),通過子類覆蓋父類的鉤子方法來決定某一特定步驟是否需要執(zhí)行。

  • 在模板方法模式中可以通過子類來覆蓋父類的基本方法,不同的子類可以提供基本方法的不同實(shí)現(xiàn),更換和增加新的子類很方便,符合單一職責(zé)原則和開閉原則

缺點(diǎn)

需要為每一個(gè)基本方法的不同實(shí)現(xiàn)提供一個(gè)子類,如果父類中可變的基本方法太多,將會(huì)導(dǎo)致類的個(gè)數(shù)增加,系統(tǒng)更加龐大,設(shè)計(jì)也更加抽象,此時(shí),可結(jié)合橋接模式來進(jìn)行設(shè)計(jì)。

適用場(chǎng)景

  • 對(duì)一些復(fù)雜的算法進(jìn)行分割,將其算法中固定不變的部分設(shè)計(jì)為模板方法和父類具體方法,而一些可以改變的細(xì)節(jié)由其子類來實(shí)現(xiàn)。即:一次性實(shí)現(xiàn)一個(gè)算法的不變部分,并將可變的行為留給子類來實(shí)現(xiàn)。

  • 各子類中公共的行為應(yīng)被提取出來并集中到一個(gè)公共父類中以避免代碼重復(fù)。

  • 需要通過子類來決定父類算法中某個(gè)步驟是否執(zhí)行,實(shí)現(xiàn)子類對(duì)父類的反向控制。

  • 一般模板方法都會(huì)加上final關(guān)鍵字,防止子類重寫該方法

源碼分析模板方法模式的典型應(yīng)用

Servlet 中的模板方法模式

Servlet(Server Applet)是Java Servlet的簡(jiǎn)稱,用Java編寫的服務(wù)器端程序,主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動(dòng)態(tài)Web內(nèi)容。在每一個(gè) Servlet 都必須要實(shí)現(xiàn) Servlet 接口,GenericServlet 是個(gè)通用的、不特定于任何協(xié)議的Servlet,它實(shí)現(xiàn)了 Servlet 接口,而 HttpServlet 繼承于 GenericServlet,實(shí)現(xiàn)了 Servlet 接口,為 Servlet 接口提供了處理HTTP協(xié)議的通用實(shí)現(xiàn),所以我們定義的 Servlet 只需要繼承 HttpServlet 即可。

HttpServlet 的簡(jiǎn)要代碼如下所示

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);
        }
    }
    // ...省略...
}

HttpServletservice 方法中,首先獲得到請(qǐng)求的方法名,然后根據(jù)方法名調(diào)用對(duì)應(yīng)的 doXXX 方法,比如說請(qǐng)求方法為GET,那么就去調(diào)用 doGet 方法;請(qǐng)求方法為POST,那么就去調(diào)用 doPost 方法

HttpServlet 相當(dāng)于定義了一套處理 HTTP 請(qǐng)求的模板;service 方法為模板方法,定義了處理HTTP請(qǐng)求的基本流程;doXXX 等方法為基本方法,根據(jù)請(qǐng)求方法做相應(yīng)的處理,子類可重寫這些方法;HttpServletRequest 中的Method則起到鉤子方法的作用.

在開發(fā)javaWeb應(yīng)用時(shí),自定義的Servlet類一般都擴(kuò)展 HttpServlet 類,譬如我們實(shí)現(xiàn)一個(gè)輸出 Hello World! 的 Servlet 如下

// 擴(kuò)展 HttpServlet 類
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("<h2>Hello World!</h2>");
  }
  public void destroy() {
      // ...
  }
}

該自定義的 Servlet 重寫了 doGet 方法,當(dāng)客戶端發(fā)起 GET 請(qǐng)求時(shí)將得到

Hello World!

Spring中的IOC容器啟動(dòng)—refresh()方法

Spring IOC容器初始化時(shí)運(yùn)用到的模板方法模式

1、首先定義一個(gè)接口ConfigurableApplicationContext,聲明模板方法refresh

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
  /**聲明了一個(gè)模板方法*/
  void refresh() throws BeansException, IllegalStateException;
}

2、抽象類AbstractApplicationContext實(shí)現(xiàn)了接口,主要實(shí)現(xiàn)了模板方法refresh(這個(gè)方法很重要,是各種IOC容器初始化的入口)的邏輯

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean {
   /**模板方法的具體實(shí)現(xiàn)*/
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
        //注意這個(gè)方法是,里面調(diào)用了兩個(gè)抽象方法refreshBeanFactory、getBeanFactory
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
          //注意這個(gè)方法是鉤子方法
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                // Initialize message source for this context.
                initMessageSource();
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
          //注意這個(gè)方法是鉤子方法
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                // Check for listener beans and register them.
                registerListeners();
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
                // Last step: publish corresponding event.
                finishRefresh();
            }
            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();
                // Reset 'active' flag.
                cancelRefresh(ex);
                // Propagate exception to caller.
                throw ex;
            }
        }
    }

這里最主要有一個(gè)抽象方法obtainFreshBeanFactory、兩個(gè)鉤子方法postProcessBeanFactory和onRefresh,看看他們?cè)陬愔械亩x

兩個(gè)鉤子方法:

 protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
    }
    protected void onRefresh() throws BeansException {
        // For subclasses: do nothing by default.
    }

再看看獲取Spring容器的抽象方法:

/**其實(shí)他內(nèi)部只調(diào)用了兩個(gè)抽象方法**/    
  protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }
  protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
  public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

具體要取那種BeanFactory容器的決定權(quán)交給了子類!

3、具體實(shí)現(xiàn)的子類,實(shí)現(xiàn)了抽象方法getBeanFactory的子類有: AbstractRefreshableApplicationContext

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
    @Override
    public final ConfigurableListableBeanFactory getBeanFactory() {
        synchronized (this.beanFactoryMonitor) {
            if (this.beanFactory == null) {
                throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                        "call 'refresh' before accessing beans via the ApplicationContext");
            }
            //這里的this.beanFactory在另一個(gè)抽象方法refreshBeanFactory的設(shè)置的
            return this.beanFactory;
        }
    }
}    
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
    @Override
    public final ConfigurableListableBeanFactory getBeanFactory() {
    //同樣這里的this.beanFactory在另一個(gè)抽象方法中設(shè)置        
    return this.beanFactory;
    }
}

其實(shí)這里的差別還不是很大,我們可以看看另一個(gè)抽象方法refreshBeanFactory的實(shí)現(xiàn),兩個(gè)抽象方法的配合使用。

Java設(shè)計(jì)模式中模板方法模式的示例分析

感謝各位的閱讀!關(guān)于“Java設(shè)計(jì)模式中模板方法模式的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI