responsibility patterns pattern observer mvc chains java design-patterns chaining

observer - mvc design patterns java



Ventajas y desventajas del método de encadenamiento y la posibilidad de reemplazar todos los parámetros de retorno de vacío por el objeto mismo (9)

Es una cantidad considerable de trabajo. Especialmente cuando la herencia está involucrada. Echa un vistazo a este excelente artículo sobre el patrón de construcción

Tiene mucho sentido implementarlo cuando se espera que los clientes llamen a varios métodos en la misma instancia de forma secuencial, por ejemplo, patrones de construcción, objetos inmutables, etc. Pero en mi opinión, en la mayoría de los casos, realmente no vale la pena el esfuerzo extra.

Estoy más interesado en Java, pero creo que es una pregunta general. Recientemente he estado trabajando con el framework Arquillian ( ShrinkWrap ) que usa mucho encadenamiento de métodos. Otro ejemplo de encadenamiento de métodos son los métodos en StringBuilder , StringBuffer . Hay beneficios obvios de usar este enfoque: la reducción de la verbosidad es uno de ellos.

Ahora me preguntaba, ¿por qué no todos los métodos que tienen el parámetro de retorno void implementados como encadenables? Debe haber algún inconveniente obvio y objetivo en el encadenamiento. Porque si todos los métodos son encadenables, aún puedo elegir no usarlos.

No estoy pidiendo cambiar el código existente en Java, lo que podría romper algo en alguna parte, pero la explicación de por qué no se utilizó sería agradable también. Estoy más preguntando desde una perspectiva de diseño del futuro marco (escrito en Java).

He encontrado una pregunta similar, pero el asker original en realidad se pregunta por qué se considera una buena práctica: Método de encadenamiento: ¿por qué es una buena práctica o no?

Si bien hay algunas respuestas disponibles, todavía no estoy seguro de cuáles son todos los beneficios y desventajas de encadenar y si se consideraría útil tener todos los métodos nulos encadenables.


La desventaja que he encontrado al utilizar el método de encadenamiento es la depuración del código cuando ocurre NullPointerException o cualquier otra Exception . Supongamos que tiene el siguiente código:

String test = "TestMethodChain"; test.substring(0,10).charAt(11); //This is just an example

Luego obtendrá el índice de cadena fuera del rango: excepción al ejecutar el código anterior. Cuando entres en situaciones en tiempo real y suceden tales cosas, entonces verás qué error de línea ha sucedido pero no qué parte del método encadenado lo causó. Por lo tanto, debe usarse con prudencia cuando sepa que los datos siempre vendrán o que los errores se manejarán correctamente.

También tiene sus ventajas, como no necesitar escribir múltiples líneas de código y usar múltiples variables.

Muchos frameworks / herramientas usan esto como Dozer y cuando solía depurar el código tuve que buscar en cada parte de la cadena para encontrar el error.

Espero que esto ayude.


Personalmente, creo que es un patrón muy útil, pero no debe usarse en todas partes. Considere la situación en la que tiene un copyTo(T other) . Normalmente, esperaría que no devuelva nada, pero si devolviera un objeto del mismo tipo, ¿qué objeto esperaría? Este tipo de problema se puede aclarar con la documentación, pero sigue siendo ambiguo en el método signulate.

public class MyObject { // ... my data public MyObject copyTo(MyObject other) { //... copy data // what should I return? return this; } }


Este patrón es útil cuando hay una serie de actualizaciones que se deben realizar en el mismo objeto y las operaciones de actualización no necesitan devolver ningún estado de actualización. Por ejemplo, utilicé este patrón en alguna API que escribí para la capa de la base de datos. Para buscar algunas filas según muchos criterios, se deben agregar muchas condiciones a la cláusula where. Usando este patrón, los criterios se pueden agregar de la siguiente manera.

CriteriaCollection().instance() .addSelect(Criteria.equalTo(XyzCriteria.COLUMN_1, value1)) .addSelect(Criteria.equalTo(XyzCriteria.COLUMN_2, value2)) .addSelect(Criteria.isIn(XyzCriteria.COLUMN_3, values3)) .addOrder(OrderCriteria.desc(XyzCriteria.Order.COLUMN_1));

En última instancia, mejora la legibilidad del código.


Si prefiere la inmutabilidad y la programación funcional , nunca volverá void .

Una función sin valor de retorno solo se llama para su efecto secundario .

Por supuesto, hay situaciones en las que esto no es aplicable, pero una función que devuelve void podría considerarse una pista para intentarlo de una manera diferente.


Puede leer sobre Fluent Interface por Martin Fowler

Sumariamente

  • no encadene el método por excelencia porque rompe el principio de diseño de Segregación de Responsabilidad de Consulta de Comando (CQRS).
  • mejore el diseño de la API acercándolo a la forma en que las empresas hablan de esas operaciones, piense en ellas como DSL internas
  • trate de evitar encadenar métodos independientes porque contaminan la API y pueden no ser capaces de revelar intenciones al cliente / mantenedor del código

Los objetos tienen atributos y métodos. Cada método cumple una parte del propósito general del objeto. Algunos tipos de métodos, como constructores y getters y setters, están llevando a cabo la gestión del ciclo de vida de los atributos y el objeto en sí. Otros métodos devuelven el estado del objeto y sus atributos. Esos métodos normalmente no son nulos.

Los métodos vacíos vienen en dos formas: 1. concernientes a la administración del ciclo de vida del objeto o los atributos. 2. tener una salida que se maneje completamente dentro del método y no debe causar ningún cambio en ningún otro lado.

anuncio 1. Pertenecen al funcionamiento interno del objeto. ad 2. La información de los parámetros se usa para ejecutar algún trabajo dentro del método. Una vez que el método se ha completado, no ha habido ningún cambio en el efecto en el objeto en sí ni en el estado interno de uno de los parámetros.

Pero ¿por qué debería hacer que un método volviera vacío y por qué no, por ejemplo, un booleano (éxito o no)? La motivación para que un método devuelva el vacío (como en un setter) es que el método no tiene ningún efecto secundario. Volver verdadero o falso podría ser un efecto secundario. Void implica que el método no tiene ningún efecto secundario cuando el método se ejecuta como se diseñó. Un método nulo que devuelve una excepción es bueno, porque una excepción no es un efecto secundario del procesamiento normal de un método.

Un método que devuelve vacío implica que, por definición, es un método de trabajo aislado. Una cadena de métodos de vacío es una cadena de métodos débilmente acoplados, porque ningún otro método puede depender del resultado de su predecesor, ya que ningún método tiene ningún resultado. Hay un patrón de diseño para esto, a saber, el patrón de diseño de Cadena de responsabilidad. Encadenar diferentes registradores es un ejemplo tradicional y la llamada a los filtros posteriores dentro de la api servlet.

Encadenar los métodos vacíos de una manera significativa implica que el objeto compartido en el que funcionan esos métodos está después de cada paso en un estado que es entendible por el método nulo que trabaja en el objeto. Todas las llamadas posteriores no pueden depender del resultado de su predecesor ni influir en el funcionamiento de las llamadas después de su propia llamada. La forma más fácil de garantizar esto es no permitirles cambiar el estado interno del objeto (como el ejemplo del registrador), o dejar que cada método cambie otra parte del estado interno del objeto.

Los métodos vacíos para ser encadenados son, en mi opinión, solo aquellos métodos que tienen sabor 2 y comparten algún tipo de procesamiento. No encadenaría los métodos vacíos con respecto al ciclo de vida de la clase, porque cada método es un cambio de una parte diferente del estado completo del objeto. La presencia de esos métodos puede cambiar con el tiempo con cualquier cambio de diseño de la clase, por lo tanto, no aconsejo encadenar esos métodos. Además, todas las excepciones son arrojadas por el primer tipo de métodos vacíos, independientemente el uno del otro, mientras que puede esperar que las excepciones arrojadas por los métodos de vacío encadenados de sabor 2 que comparten algún tipo de procesamiento tengan algo en común.


El encadenamiento de métodos es una forma de implementar interfaces fluidas, independientemente del lenguaje de programación. El principal beneficio de esto (código legible) te dice exactamente cuándo usarlo. Si no hay una necesidad particular para el código legible, mejor evite usarlo, a menos que la API esté diseñada naturalmente para devolver el contexto / objeto como resultado de las llamadas al método.

Paso 1: interfaz fluida vs. API de consulta de comandos

La interfaz fluida se debe considerar en comparación con la API de consulta de comandos. Para entenderlo mejor, déjame escribir una definición de lista de viñetas de la API de consulta de comandos a continuación. En palabras simples, esto es solo un enfoque estándar de codificación orientada a objetos:

  • El método que modifica los datos se llama Command . El comando no devuelve un valor.
  • El método que devuelve un valor se llama una Query . Query no modifica los datos.

Seguir la API de consulta de comandos le brindará los siguientes beneficios:

  • Al mirar el código orientado a objetos, usted comprende lo que está sucediendo.
  • La depuración del código es más fácil porque cada llamada se realiza por separado.

Paso 2: Interfaz fluida en la parte superior de la API de Command-Query

Pero la API de consulta de comandos existe por algún motivo, y de hecho, se lee mejor. Entonces, ¿cómo tenemos los beneficios tanto de la interfaz fluida como de la API de consulta de comandos?

Respuesta: la interfaz fluida debe implementarse sobre la API de consulta de comandos (en lugar de reemplazar la API de consulta de comandos por la interfaz fluida). Piense en una interfaz fluida como una fachada sobre la API de consulta de comandos. Y, después de todo, se llama " interfaz " fluida: una interfaz legible o de conveniencia sobre la API estándar (consulta de comandos).

Por lo general, después de que la API de consulta de comandos esté lista (escrita, probablemente probada en una unidad, pulida para depurar fácilmente), puede escribir una capa de software de interfaz fluida encima. En otras palabras, la interfaz fluida cumple sus funciones utilizando la API de consulta de comandos. Luego, use la interfaz fluida (con encadenamiento de métodos) donde desee una conveniencia y legibilidad. Sin embargo, una vez que quiera comprender lo que está sucediendo realmente (por ejemplo, al depurar una excepción), siempre puede profundizar en la API de consulta de comandos, un buen código antiguo orientado a objetos.


Inconvenientes

  • Principalmente confunde la firma, si algo devuelve una nueva instancia no espero que sea también un método de mutador. Por ejemplo, si un vector tiene un método de escala, entonces si tiene un retorno, supongo que devuelve un nuevo vector escalado por la entrada, si no lo hace, entonces esperaría que escalara internamente.
  • Además, por supuesto, obtienes problemas si la clase se amplía, ya que a mitad de tu encadenamiento tu objeto se convierte en un supertipo. Esto ocurre cuando se declara un método de encadenamiento en la clase principal, pero se usa en una instancia de la clase secundaria.

Beneficios

  • Permite que el código de estilo de ecuación matemática se escriba como ecuaciones completas sin la necesidad de múltiples objetos intermedios (que conducen a sobrecarga innecesaria), por ejemplo, sin un método de encadenamiento del vector triple producto cruzado (como un ejemplo aleatorio) tendría que escribirse como

    MyVector3d tripleCrossProduct=(vector1.multiply(vector2)).multiply(vector3);

    que tiene la desventaja de crear un objeto intermedio que se debe crear y recoger basura, o

    MyVector3d tripleCrossProduct=vector1; tripleCrossProduct.multiplyLocal(vec2); tripleCrossProduct.multiplyLocal(vec3);

    que evita la creación de objetos intermedios pero no está muy claro, el nombre de la variable tripleCrossProduct es, de hecho, un mentira hasta la línea 3. Sin embargo, si tiene un método de encadenamiento, puede escribirse concisamente de una manera matemática normal sin crear objetos intermedios innecesarios.

    MyVector3d tripleCrossProduct=vector1.multiplyLocal(vector2).multiplyLocal(vector3);

    Todo esto supone que vector1 es sacrificable y nunca será necesario volver a utilizarlo

  • Y, por supuesto, el beneficio obvio; brevedad. Incluso si sus operaciones no están vinculadas en el señorío de mi ejemplo anterior, puede evitar referencias innecesarias al objeto

    SomeObject someObject=new SomeObject(); someObject .someOperation() .someOtherOperation();

NB MyVector3d no se está utilizando como una clase real de Java, pero se supone que realiza el producto cruzado cuando se .multiply() métodos .multiply() . .cross() no se usa para que la ''intención'' sea más clara para aquellos que no están familiarizados con el cálculo vectorial
La solución de NB Amit fue la primera respuesta para usar el encadenamiento de métodos multilínea, la incluyo como parte del cuarto punto para completar