La Belleza de los Patrones de Diseño 58-Modo Plantilla (Parte 1): Análisis de la Aplicación del Modo Plantilla en JDK, Servlet, JUnit, etc.

58 | Modo plantilla (Parte 1): Análisis de la aplicación del modo plantilla en JDK, Servlet, JUnit, etc.

En las últimas dos clases, aprendimos el primer patrón de diseño de comportamiento, el patrón del observador. Para diferentes escenarios de aplicación, explicamos diferentes métodos de implementación, incluido el bloqueo síncrono, las implementaciones sin bloqueo asíncrono y las implementaciones dentro y entre procesos. Además, también lo llevé a implementar un marco simple de EventBus.

Hoy aprenderemos otro patrón de diseño de comportamiento, el patrón de plantilla. Hemos enfatizado repetidamente que los principios y la implementación de la mayoría de los patrones de diseño son muy simples. La dificultad es comprender los escenarios de aplicación y descubrir qué problemas se pueden resolver. El patrón de la plantilla no es una excepción. El modo de plantilla se utiliza principalmente para resolver dos problemas de reutilización y extensión. Hoy combinaremos cuatro ejemplos de Java Servlet, JUnit TestCase, Java InputStream y Java AbstractList para explicar estas dos funciones en detalle.

Sin más preámbulos, ¡comencemos oficialmente el estudio de hoy!

Principio e implementación del modo de plantilla

Modo de plantilla, el nombre completo es modo de diseño de método de plantilla, inglés es patrón de diseño de método de plantilla. En el libro "Patrones de diseño" de GoF, se define de la siguiente manera:

Defina el esqueleto de un algoritmo en una operación, delegando algunos pasos a las subclases. Template Method permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar la estructura del algoritmo.

Traducido al chino es: el patrón del método de plantilla define un esqueleto de algoritmo en un método y difiere ciertos pasos a las subclases. El patrón del método de plantilla permite que las subclases redefinan ciertos pasos en un algoritmo sin cambiar la estructura general del algoritmo.

El "algoritmo" aquí puede entenderse como "lógica comercial" en un sentido amplio y no se refiere específicamente al "algoritmo" en estructuras de datos y algoritmos. El esqueleto del algoritmo aquí es la "plantilla", y el método que contiene el esqueleto del algoritmo es el "método de plantilla", que también es el origen del nombre del patrón del método de plantilla.

El principio es muy simple, y la implementación del código es aún más simple. Escribí un código de muestra, como se muestra a continuación. La función templateMethod() se define como final para evitar que las subclases la anulen. method1() y method2() se definen como abstractos para forzar la implementación de las subclases. Sin embargo, estos no son necesarios.En el desarrollo del proyecto real, la implementación del código del modo de plantilla es más flexible.Cuando hablemos de los escenarios de aplicación más adelante, tendremos una reflexión específica.

public abstract class AbstractClass {
  public final void templateMethod() {
    //...
    method1();
    //...
    method2();
    //...
  }

  protected abstract void method1();
  protected abstract void method2();
}

public class ConcreteClass1 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }

  @Override
  protected void method2() {
    //...
  }
}

public class ConcreteClass2 extends AbstractClass {
  @Override
  protected void method1() {
    //...
  }

  @Override
  protected void method2() {
    //...
  }
}

AbstractClass demo = ConcreteClass1();
demo.templateMethod();

Rol uno del modo de plantilla: reutilizar

Al comienzo del artículo, mencionamos que el modo de plantilla tiene dos funciones principales: reutilización y extensión. Veamos primero su primera función: reutilizar.

El modo de plantilla abstrae el proceso invariable de un algoritmo en el método de plantilla templateMethod() de la clase principal, y deja las partes variables method1() y method2() a las subclases ContreteClass1 y ContreteClass2 para que las implementen. Todas las subclases pueden reutilizar el código de proceso definido por el método de plantilla en la clase principal. Entendámoslo de forma más intuitiva a través de dos pequeños ejemplos.

1. Flujo de entrada de Java

En la biblioteca de clases Java IO, muchas clases se diseñan utilizando el patrón de plantilla, como InputStream, OutputStream, Reader y Writer. Tomemos InputStream como ejemplo.

Publiqué el código relevante de la parte InputStream a continuación. En el código, la función read() es un método de plantilla que define todo el proceso de lectura de datos y expone un método abstracto que las subclases pueden personalizar. Sin embargo, este método también se llama read(), pero los parámetros son diferentes del método de plantilla.

public abstract class InputStream implements Closeable {
  //...省略其他代码...

  public int read(byte b[], int off, int len) throws IOException {
    if (b == null) {
      throw new NullPointerException();
    } else if (off < 0 || len < 0 || len > b.length - off) {
      throw new IndexOutOfBoundsException();
    } else if (len == 0) {
      return 0;
    }

    int c = read();
    if (c == -1) {
      return -1;
    }
    b[off] = (byte)c;

    int i = 1;
    try {
      for (; i < len ; i++) {
        c = read();
        if (c == -1) {
          break;
        }
        b[off + i] = (byte)c;
      }
    } catch (IOException ee) {
    }
    return i;
  }

  public abstract int read() throws IOException;
}

public class ByteArrayInputStream extends InputStream {
  //...省略其他代码...

  @Override
  public synchronized int read() {
    return (pos < count) ? (buf[pos++] & 0xff) : -1;
  }
}

2. Lista abstracta de Java

En la clase AbstractList de Java, la función addAll() puede considerarse como un método de plantilla. Add() es un método que las subclases necesitan reescribir. Aunque no se declara como abstracto, la implementación de la función lanza directamente una UnsupportedOperationException. La premisa es que no se puede usar si la subclase no la anula.

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    boolean modified = false;
    for (E e : c) {
        add(index++, e);
        modified = true;
    }
    return modified;
}

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

Función de modo de plantilla dos: extensión

La segunda función principal del modo de plantilla es expandir. La extensión mencionada aquí no se refiere a la extensibilidad del código, sino a la extensibilidad del framework, que es un poco similar a la inversión de control que mencionamos antes, puedes combinarla con la Sección . Basado en este rol, el modo de plantilla se usa a menudo en el desarrollo del marco, lo que permite a los usuarios del marco personalizar las funciones del marco sin modificar el código fuente del marco. Expliquémoslo a través de dos ejemplos de Junit TestCase y Java Servlet.

1.Servlet de Java

Para el desarrollo de proyectos web Java, el marco de desarrollo comúnmente utilizado es SpringMVC. Usándolo, solo necesitamos centrarnos en la escritura del código comercial, y los principios subyacentes apenas están involucrados. Sin embargo, si dejamos de lado estos frameworks avanzados para desarrollar proyectos Web, inevitablemente se utilizarán Servlets. De hecho, no es difícil desarrollar proyectos Web utilizando el Servlet de nivel inferior. Solo necesitamos definir una clase que herede HttpServlet y reescribir el método doGet() o doPost() en ella para manejar las solicitudes de obtención y publicación respectivamente. Un ejemplo de código específico es el siguiente:

public class HelloServlet extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doPost(req, resp);
  }

  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("Hello World.");
  }
}

Además, también necesitamos hacer la siguiente configuración en el archivo de configuración web.xml. Los contenedores de servlet como Tomcat y Jetty cargarán automáticamente la relación de mapeo entre la URL y el servlet en este archivo de configuración cuando se inicien.

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <servlet-class>com.xzg.cd.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

Cuando ingresamos la URL en el navegador (por ejemplo, http://127.0.0.1:8080/hello ), el contenedor de Servlet recibirá la solicitud correspondiente y encontrará el Servlet correspondiente de acuerdo con la relación de mapeo entre la URL y el Servlet (HelloServlet), y luego ejecute su método de servicio (). El método service() está definido en la clase principal HttpServlet, llamará al método doGet() o doPost() y luego enviará los datos ("Hello world") a la página web.

Veamos ahora cómo se ve la función service() de HttpServlet.

public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
{
    HttpServletRequest  request;
    HttpServletResponse response;
    if (!(req instanceof HttpServletRequest &&
            res instanceof HttpServletResponse)) {
        throw new ServletException("non-HTTP request or response");
    }
    request = (HttpServletRequest) req;
    response = (HttpServletResponse) res;
    service(request, response);
}

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

Del código anterior, podemos ver que el método service() de HttpServlet es un método de plantilla, que implementa el proceso de ejecución de toda la solicitud HTTP. doGet() y doPost() son partes de la plantilla que pueden personalizarse mediante subclases . De hecho, esto es equivalente a que el marco Servlet proporcione un punto de extensión (método doGet(), doPost()), lo que permite a los usuarios del marco incrustar código comercial en el marco a través del punto de extensión sin modificar el código fuente del marco Servlet.

2. Caso de prueba JUnit

De manera similar a Java Servlet, el marco JUnit también proporciona algunos puntos de extensión funcionales (setUp(), tearDown(), etc.) a través del modo de plantilla, lo que permite a los usuarios del marco ampliar funciones en estos puntos de extensión.

Cuando usamos el marco de prueba JUnit para escribir pruebas unitarias, las clases de prueba que escribimos deben heredar la clase TestCase proporcionada por el marco. En la clase TestCase, la función runBare() es un método de plantilla que define el proceso general de ejecución del caso de prueba: primero ejecute setUp() para hacer un trabajo preparatorio, luego ejecute runTest() para ejecutar el código de prueba real y finalmente ejecute tearDown() para hacer el trabajo de acabado.

El código específico de la clase TestCase es el siguiente. Aunque setUp() y tearDown() no son funciones abstractas, también proporcionan implementaciones predeterminadas y no obligan a las subclases a volver a implementarse, pero esta parte también se puede personalizar en subclases, por lo que también se ajusta a la definición del modo de plantilla.

public abstract class TestCase extends Assert implements Test {
  public void runBare() throws Throwable {
    Throwable exception = null;
    setUp();
    try {
      runTest();
    } catch (Throwable running) {
      exception = running;
    } finally {
      try {
        tearDown();
      } catch (Throwable tearingDown) {
        if (exception == null) exception = tearingDown;
      }
    }
    if (exception != null) throw exception;
  }

  /**
  * Sets up the fixture, for example, open a network connection.
  * This method is called before a test is executed.
  */
  protected void setUp() throws Exception {
  }

  /**
  * Tears down the fixture, for example, close a network connection.
  * This method is called after a test is executed.
  */
  protected void tearDown() throws Exception {
  }
}

revisión clave

Bueno, eso es todo por el contenido de hoy. Resumamos y revisemos juntos, en qué necesitas concentrarte.

El patrón de método de plantilla define un esqueleto de algoritmo en un método y difiere ciertos pasos para la implementación de la subclase. El patrón del método de plantilla permite que las subclases redefinan ciertos pasos en un algoritmo sin cambiar la estructura general del algoritmo. El "algoritmo" aquí puede entenderse como "lógica comercial" en un sentido amplio y no se refiere específicamente al "algoritmo" en estructuras de datos y algoritmos. El esqueleto del algoritmo aquí es la "plantilla", y el método que contiene el esqueleto del algoritmo es el "método de plantilla", que también es el origen del nombre del patrón del método de plantilla.

En la implementación clásica del patrón de plantilla, el método de plantilla se define como final para evitar que las subclases lo reescriban. Los métodos que necesitan ser reescritos por subclases se definen como abstractos, y las subclases pueden verse obligadas a implementarlos. Sin embargo, en el desarrollo del proyecto real, la implementación del modo plantilla es más flexible y los dos puntos anteriores no son necesarios.

El modo de plantilla tiene dos funciones: reutilización y extensión. Entre ellos, reutilizar significa que todas las subclases pueden reutilizar el código del método de plantilla proporcionado en la clase principal. Extensión significa que el marco proporciona puntos de extensión funcionales a través del modo de plantilla, lo que permite a los usuarios del marco personalizar las funciones del marco en función de los puntos de extensión sin modificar el código fuente del marco.

discusión en clase

Supongamos que una determinada clase en un marco expone dos métodos de plantilla y define un montón de métodos abstractos para que llamen los métodos de plantilla.El ejemplo de código es el siguiente. En el desarrollo de proyectos, incluso si solo usamos uno de los métodos de plantilla de esta clase, todavía tenemos que implementar todos los métodos abstractos en la subclase, lo que equivale a trabajo inválido. ¿Hay alguna otra forma de resolver este problema?

public abstract class AbstractClass {
  public final void templateMethod1() {
    //...
    method1();
    //...
    method2();
    //...
  }

  public final void templateMethod2() {
    //...
    method3();
    //...
    method4();
    //...
  }

  protected abstract void method1();
  protected abstract void method2();
  protected abstract void method3();
  protected abstract void method4();
}

Bienvenido a dejar un mensaje y compartir sus pensamientos conmigo. Si obtienes algo, puedes compartir este artículo con tus amigos.

Supongo que te gusta

Origin blog.csdn.net/fegus/article/details/130498831
Recomendado
Clasificación