Tiempo inmediato: la belleza de los patrones de diseño en la práctica II (Parte 2): ¿Cómo hacer un análisis orientado a objetos sobre el desarrollo de una función como la autenticación de interfaz?

En la lección anterior, para el desarrollo de funciones de autenticación de interfaz, hablamos sobre cómo realizar análisis orientado a objetos (OOA), es decir, análisis de demanda. De hecho, después de que los requisitos están claramente definidos, este problema se ha resuelto más de la mitad, por lo que dediqué tanto espacio al análisis de requisitos. Hoy, echemos un vistazo a cómo realizar el diseño orientado a objetos (OOD) y la programación orientada a objetos (OOP) para las necesidades de resultados de análisis orientados a objetos.

¿Cómo realizar un diseño orientado a objetos?

Sabemos que el resultado del análisis orientado a objetos es una descripción detallada de los requisitos, y el resultado del diseño orientado a objetos es la clase. En el proceso de diseño orientado a objetos, transformamos la descripción del requisito en un diseño de clase específico. Desmontamos y perfeccionamos este enlace de diseño, que incluye principalmente las siguientes partes:

● Dividir las responsabilidades para identificar las categorías;

● Definir la clase y sus atributos y métodos;

● Definir la relación interactiva entre clases;

● Reunir clases y proporcionar puntos de entrada para la ejecución.

Para ser honesto, ya sea análisis orientado a objetos o diseño orientado a objetos, no hay muchas cosas teóricas, por lo que aún combinamos el ejemplo de autenticación para entender cómo hacer diseño orientado a objetos en un combate real. 1. Divida las responsabilidades e identifique los tipos

1. Divida las responsabilidades e identifique los tipos

A menudo se dice en los libros sobre la orientación a objetos que una clase es un modelo de cosas en el mundo real. Sin embargo, no todos los requisitos se pueden asignar al mundo real, y no todas las categorías tienen una correspondencia uno a uno con cosas del mundo real. Para algunos conceptos abstractos, no podemos definir clases mapeando cosas en el mundo real.

Por lo tanto, en la mayoría de los libros orientados a objetos, hay otra forma de identificar categorías, que consiste en enumerar los sustantivos en la descripción del requisito como posibles categorías candidatas y luego filtrarlas. Para los principiantes sin experiencia, este método es relativamente simple y claro, y puede seguirse directamente.

Sin embargo, personalmente prefiero otro método, que consiste en enumerar los puntos de función involucrados uno por uno de acuerdo con la descripción del requisito, y luego ver qué puntos de función tienen responsabilidades similares y operan los mismos atributos. ¿Deben agruparse en el mismo clase. Echemos un vistazo al ejemplo específico de autenticación.

En la lección anterior, brindamos una descripción detallada de los requisitos. Para su conveniencia, la volví a publicar a continuación.

● Cuando la persona que llama realiza una solicitud de interfaz, la URL, el AppID, la contraseña y la marca de tiempo se unen, el token se genera a través de un algoritmo de cifrado y el token, AppID y la marca de tiempo se empalman en la URL y se envían al microservidor.

● Después de recibir la solicitud de interfaz de la persona que llama, el microservidor desensambla el token, AppID y la marca de tiempo de la solicitud.

● El micro servidor primero verifica si la marca de tiempo aprobada y la hora actual están dentro de la ventana de tiempo de vencimiento del token. Si ha transcurrido el tiempo de caducidad, la autenticación de la llamada de interfaz se considera un error y la solicitud de llamada de interfaz se rechaza.

● Si la verificación del token no ha caducado, el microservidor sacará la contraseña correspondiente al AppID de su propio almacenamiento y usará el mismo algoritmo de generación de token para generar otro token que coincida con el token pasado por la persona que llama. Si son consistentes, la autenticación es exitosa y se permite la llamada de interfaz; de lo contrario, se rechaza la llamada de interfaz.

En primer lugar, lo que tenemos que hacer es leer la descripción del requisito anterior oración por oración, desarmarla en pequeños puntos de función y enumerarlos uno por uno. Tenga en cuenta que cada punto de función que se desmonte debe ser lo más pequeño posible. Cada punto de función solo es responsable de hacer una pequeña cosa (el nombre del profesional es "responsabilidad única", de eso hablaremos en capítulos posteriores). La siguiente es una lista de puntos de función que obtuve después de desmontar la descripción anterior de los requisitos frase por frase:

  1. Combine URL, AppID, contraseña y marca de tiempo en una sola cadena;

  2. Cifre la cadena mediante un algoritmo de cifrado para generar un token;

  3. Empalme el token, AppID y la marca de tiempo en la URL para formar una nueva URL;

  4. Analice la URL para obtener token, AppID, marca de tiempo y otra información;

  5. Recupere AppID y la contraseña correspondiente del almacenamiento;

  6. Determine si el token está caducado o no es válido según la marca de tiempo;

  7. Verifique que los dos tokens coincidan;

De la lista de funciones anterior, encontramos que 1, 2, 6 y 7 están relacionados con tokens, responsables de la generación y verificación de tokens; 3 y 4 están procesando URL, responsables del empalme y análisis de URL; 5 está operando AppID Y contraseña, responsable de leer AppID y contraseña del almacenamiento. Por lo tanto, podemos obtener aproximadamente las tres clases principales: AuthToken, Url, CredentialStorage. AuthToken es responsable de las cuatro operaciones 1, 2, 6 y 7; Url es responsable de las dos operaciones de 3 y 4; CredentialStorage es responsable de la operación de 5.

Por supuesto, esta es una división de clases preliminar. Es posible que no podamos pensar en las otras esquinas y esquinas sin importancia a la vez, pero no importa. El análisis, el diseño y la programación orientados a objetos son originalmente un ciclo. Proceso de optimización iterativo y continuo. De acuerdo con los requisitos, primero damos una versión aproximada del plan de diseño y luego, basándonos en dicha base, la optimización iterativa será más fácil y el pensamiento será más claro.

Sin embargo, me gustaría enfatizar que el requisito de desarrollo de la autenticación de llamadas de interfaz es relativamente simple, por lo tanto, el diseño orientado a objetos correspondiente al requisito no es complicado y no hay muchas clases reconocidas. Pero si estamos tratando con un desarrollo de software más grande y un desarrollo de requisitos más complejos, puede haber muchos puntos de función involucrados y habrá más clases correspondientes. El método de enumerar los puntos de función de acuerdo con las necesidades, como antes, finalmente Obtén una lista larga, será un poco desordenada e irregular. En respuesta a este tipo de desarrollo de demanda compleja, lo que tenemos que hacer primero es dividir el módulo, primero simplemente dividir la demanda en varios módulos funcionales pequeños e independientes, y luego aplicar el método del que acabamos de hablar dentro del módulo para orientado a objetos. diseño. La división y reconocimiento de módulos es similar a la división y reconocimiento de clases.

2. Definir la clase y sus atributos y métodos.

Acabamos de identificar tres clases principales analizando la descripción de los requisitos: AuthToken, Url y CredentialStorage. Ahora veamos qué atributos y métodos tiene cada clase. Todavía excavamos en la lista de puntos de función.

Hay cuatro funciones relacionadas con la clase AuthToken:

● Concatenar URL, AppID, contraseña y marca de tiempo en una sola cadena;

● Cifre la cadena mediante un algoritmo de cifrado para generar un token;

● Determine si el token ha vencido o no es válido según la marca de tiempo; verifique si los dos tokens coinciden.

Para la identificación de métodos, muchos libros relacionados con la orientación a objetos generalmente dicen esto, identificando verbos en descripciones de demanda como métodos candidatos y luego filtrando más. Por analogía con el reconocimiento de métodos, podemos usar los sustantivos involucrados en los puntos de función como atributos candidatos y luego filtrarlos también.

Podemos tomar prestada esta idea e identificar los atributos y métodos de la clase AuthToken de acuerdo con la descripción del punto de función, como se muestra a continuación:
Inserte la descripción de la imagen aquí
En el diagrama de clases anterior, podemos encontrar estos tres pequeños detalles.

● El primer detalle: no todos los sustantivos que aparecen están definidos como atributos de la clase, como URL, AppID, contraseña y marca de tiempo, los usamos como parámetros de método.

● El segundo detalle: también debemos buscar algunos atributos que no aparecen en la descripción del punto de función, como createTime y expireTimeInterval, que se utilizan en la función isExpired () para determinar si el token ha expirado.

● El tercer detalle: también agregamos un método getToken () que no se menciona en la descripción del punto de función a la clase AuthToken.

El primer detalle nos dice que a partir del modelo de negocio, los atributos y métodos que no deberían pertenecer a esta clase no deberían colocarse en esta clase. Por ejemplo, información como URL y AppID no debería pertenecer a AuthToken del modelo de negocio, por lo que no deberíamos ponerla en esta clase.

Los detalles segundo y tercero nos dicen que al diseñar los atributos y métodos de una clase, no debemos simplemente confiar en las necesidades actuales, sino también analizar qué atributos y métodos debe tener la clase a partir del modelo de negocio. Esto puede garantizar la integridad de la definición de clase, por un lado, y hacer preparativos no solo para las necesidades actuales sino también para las necesidades futuras.

Hay dos funciones relacionadas con la clase Url:

● Empalmar token, AppID y marca de tiempo en la URL para formar una nueva URL;

● Analizar la URL para obtener token, AppID, marca de tiempo y otra información.

Aunque en la descripción de los requisitos, todos nos referimos a la solicitud de interfaz por URL, pero la solicitud de interfaz no se expresa necesariamente en forma de URL y puede estar en otras formas como Dubbo, RPC. Para hacer esta clase más universal y más apropiada, la llamaremos ApiRequest a continuación. La siguiente es la clase ApiRequest que diseñé según la descripción del punto funcional.

Inserte la descripción de la imagen aquí

Hay un punto de función relacionado de la clase CredentialStorage:

● Recuperar AppID y la contraseña correspondiente del almacenamiento.

La clase CredentialStorage es muy simple y el diagrama de clases se muestra a continuación. Para abstraer y encapsular métodos de almacenamiento específicos, diseñamos CredentialStorage como una interfaz, basada en interfaces en lugar de una programación de implementación específica.

Inserte la descripción de la imagen aquí

3. Definir la interacción entre la clase y la clase.

¿Cuáles son las relaciones de interacción entre clases? La relación entre seis categorías se define en el lenguaje de modelado unificado UML. Son: generalización, realización, asociación, agregación, combinación y dependencia. Hay muchas relaciones, y algunas son relativamente similares, como la agregación y la combinación, a continuación las explicaré una por una.

Generalización (Generalización) se puede entender como una simple herencia. El código específico de Java es el siguiente:


public class A {
    
     ... }
public class B extends A {
    
     ... }

Implementar (Realización) generalmente se refiere a la relación entre la interfaz y la clase de implementación. El código específico de Java es el siguiente:


public interface A {
    
    ...}
public class B implements A {
    
     ... }

Polimerización (agregación) que comprende una relación, un objeto de clase B de clase contiene el objeto, el ciclo de vida de la clase de objeto B no puede depender del ciclo de vida de la clase de objeto A , la clase A que se puede destruir sin afectar el objeto sujeto individual B, como La relación entre el currículo y el alumno. El código específico de Java es el siguiente:


public class A {
    
    
  private B b;
  public A(B b) {
    
    
    this.b = b;
  }
}

Composición (Composición) que comprende también una relación. Los objetos de clase A incluyen objetos de clase B. El ciclo de vida de los objetos de clase B depende del ciclo de vida de los objetos de clase A. Los objetos de clase B no pueden existir solos , como la relación entre pájaros y alas. El código específico de Java es el siguiente:


public class A {
    
    
  private B b;
  public A() {
    
    
    this.b = new B();
  }
}

Asociación (Association) es una relación muy débil, incluida la agregación y la combinación. Específicamente al nivel de código, si el objeto de la clase B es una variable miembro de la clase A, entonces la clase B y la clase A están asociadas. El código específico de Java es el siguiente:


public class A {
    
    
  private B b;
  public A(B b) {
    
    
    this.b = b;
  }
}
或者
public class A {
    
    
  private B b;
  public A() {
    
    
    this.b = new B();
  }
}

La dependencia (Dependencia) es una relación más débil que la relación, incluida la relación. Independientemente de si es una variable miembro de un objeto de clase B o un método de clase A que usa un objeto de clase B como parámetro o valor de retorno o variable local, siempre que exista alguna relación de uso entre el objeto de clase B y el objeto de clase A, los llamamos dependientes relación. El código específico de Java es el siguiente:


public class A {
    
    
  private B b;
  public A(B b) {
    
    
    this.b = b;
  }
}
或者
public class A {
    
    
  private B b;
  public A() {
    
    
    this.b = new B();
  }
}
或者
public class A {
    
    
  public void func(B b) {
    
     ... }
}

Después de leer la introducción detallada de los seis tipos de relaciones UML, ¿cómo se siente? Personalmente, creo que esta división es demasiado detallada, aumenta el costo del aprendizaje y no tiene mucho sentido para guiar el desarrollo de la programación. Por lo tanto, desde una perspectiva de programación, ajusté la relación entre clases y solo retuve cuatro relaciones: generalización, implementación, composición y dependencia, para que sea más fácil de dominar.

Entre ellos, las definiciones de generalización, implementación y dependencia permanecen sin cambios, y la relación de combinación reemplaza los tres conceptos de combinación, agregación y asociación en UML, lo que equivale a cambiar el nombre de la relación como una relación de combinación, y ya no distingue la combinación y agregación en UML. Conceptos. La razón de este cambio de nombre es unificar el significado de "combinación" en el principio de diseño de "combinar más y menos herencia" que mencionamos anteriormente. Siempre que el objeto de clase B sea una variable miembro del objeto de clase A, podemos decir que la clase A y la clase B están combinadas.

Ahora que la teoría está terminada, echemos un vistazo a ¿cuáles son las relaciones entre las clases que acabamos de definir? Debido a que actualmente solo hay tres clases principales, solo se usa la relación de implementación, es decir, la relación entre CredentialStorage y MysqlCredentialStorage. A continuación, cuando hablemos de clases ensambladoras, también usaremos relaciones de dependencia y composición, pero las relaciones de generalización no se usan por el momento.

4. Reúna la clase y proporcione una entrada de ejecución.

Se definen las clases y también se diseñan las relaciones de interacción necesarias entre las clases A continuación, ensamblaremos todas las clases para proporcionar una entrada de ejecución. Esta entrada puede ser una función main () o puede ser un conjunto de interfaces API para uso externo. A través de esta entrada, podemos activar la ejecución del código completo.

La autenticación de interfaz no es un sistema independiente, sino un componente que se ejecuta en el sistema. Por lo tanto, encapsulamos todos los detalles de implementación y diseñamos una clase de interfaz ApiAuthenticator de nivel superior para exponer un grupo de llamadores externos. La interfaz API sirve como punto de entrada para activar la ejecución de la lógica de autenticación. El diseño de clase específico es el siguiente:

Inserte la descripción de la imagen aquí

¿Cómo hacer programación orientada a objetos?

Una vez completado el diseño orientado a objetos, hemos definido claramente la interacción entre clases, atributos, métodos y clases, y hemos ensamblado todas las clases para proporcionar una entrada de ejecución unificada. A continuación, el trabajo de la programación orientada a objetos es traducir estas ideas de diseño en implementación de código. Con el diagrama de clases anterior, esta parte del trabajo es relativamente simple. Por lo tanto, aquí solo doy la implementación del ApiAuthenticator más complicado.

Para las tres clases de AuthToken, ApiRequest y CredentialStorage, no daré implementaciones de código específicas aquí. Deje una tarea para usted, puede intentar implementar todo el marco de autenticación usted mismo.


public interface ApiAuthenticator {
    
    
  void auth(String url);
  void auth(ApiRequest apiRequest);
}

public class DefaultApiAuthenticatorImpl implements ApiAuthenticator {
    
    
  private CredentialStorage credentialStorage;
  
  public DefaultApiAuthenticatorImpl() {
    
    
    this.credentialStorage = new MysqlCredentialStorage();
  }
  
  public DefaultApiAuthenticatorImpl(CredentialStorage credentialStorage) {
    
    
    this.credentialStorage = credentialStorage;
  }

  @Override
  public void auth(String url) {
    
    
    ApiRequest apiRequest = ApiRequest.buildFromUrl(url);
    auth(apiRequest);
  }

  @Override
  public void auth(ApiRequest apiRequest) {
    
    
    String appId = apiRequest.getAppId();
    String token = apiRequest.getToken();
    long timestamp = apiRequest.getTimestamp();
    String originalUrl = apiRequest.getOriginalUrl();

    AuthToken clientAuthToken = new AuthToken(token, timestamp);
    if (clientAuthToken.isExpired()) {
    
    
      throw new RuntimeException("Token is expired.");
    }

    String password = credentialStorage.getPasswordByAppId(appId);
    AuthToken serverAuthToken = AuthToken.generate(originalUrl, appId, password, timestamp);
    if (!serverAuthToken.match(clientAuthToken)) {
    
    
      throw new RuntimeException("Token verfication failed.");
    }
  }
}

Pensamiento dialéctico y aplicación flexible

En la explicación anterior, análisis, diseño e implementación orientados a objetos, los límites de cada enlace son relativamente claros. Además, el diseño y la implementación se traducen básicamente frase por frase según la descripción de los puntos de función. La ventaja de esto es que qué hacer primero y qué hacer después es muy claro, claro y basado en reglas. Incluso los ingenieros principiantes sin mucha experiencia en diseño pueden seguir este proceso paso a paso para hacer análisis, diseño e implementación.

Sin embargo, en el trabajo ordinario, la mayoría de los programadores a menudo completan el análisis y el diseño orientados a objetos en sus cabezas o en papel borrador, y luego comienzan a escribir código, mientras escriben y piensan mientras refactorizan, y no seguirán estrictamente El proceso se ejecuta. Además, para ser honesto, incluso si pasamos mucho tiempo haciendo análisis y diseño antes de escribir el código, dibujando diagramas de clases perfectos y diagramas UML, es imposible pensar con claridad en cada detalle e interacción. Al implementar el código, todavía tenemos que iterar, refactorizar, romper y reescribir.

Después de todo, todo el desarrollo de software es originalmente un proceso de iteración, parcheo y resolución de problemas, así como un proceso de reconstrucción constante. No podemos seguir estrictamente los pasos en orden. Esto es similar a aprender una licencia de conducir. La escuela de manejo enseña un proceso más formal. Primero haga lo que hace y luego haga lo que hace. Puede retroceder el vehículo sin problemas siempre que siga las instrucciones, pero de hecho, cuando conduzca con soltura, retroceda. Las bibliotecas a menudo se basan en la experiencia y el sentimiento.

Supongo que te gusta

Origin blog.csdn.net/zhujiangtaotaise/article/details/110379120
Recomendado
Clasificación