El principio Tell Don't Ask dice:
debes esforzarte por decirle a los objetos lo que quieres que hagan; no les haga preguntas sobre su estado, tome una decisión y luego dígales qué hacer.
El problema es que, como la persona que llama, no debe tomar decisiones basadas en el estado del objeto llamado que provoque que cambie el estado del objeto. La lógica que está implementando es probablemente la responsabilidad del objeto llamado, no la suya. Para que usted tome decisiones fuera del objeto viola su encapsulación.
Un ejemplo simple de "Dile, no preguntes" es
Widget w = ...;
if (w.getParent() != null) {
Panel parent = w.getParent();
parent.remove(w);
}
y la versión tell es ...
Widget w = ...;
w.removeFromParent();
Pero, ¿qué sucede si necesito saber el resultado del método removeFromParent? Mi primera reacción fue simplemente cambiar removeFromParent para devolver un valor booleano que indica si el padre fue eliminado o no.
Pero luego me encontré con el Patrón de separación de consulta de comando que dice NO hacer esto.
Establece que cada método debe ser un comando que realiza una acción o una consulta que devuelve datos al llamante, pero no ambos. En otras palabras, hacer una pregunta no debería cambiar la respuesta. Más formalmente, los métodos deberían devolver un valor solo si son referencialmente transparentes y, por lo tanto, no poseen efectos secundarios.
¿Están estos dos realmente en desacuerdo entre sí y cómo elijo entre los dos? ¿Voy con el programador pragmático o Bertrand Meyer en esto?
Respuestas:
En realidad, su problema de ejemplo ya ilustra la falta de descomposición.
Vamos a cambiarlo un poco:
Esto no es realmente diferente, pero hace que la falla sea más evidente: ¿por qué el libro sabría sobre su estante? En pocas palabras, no debería. Introduce una dependencia de libros en los estantes (lo que no tiene sentido) y crea referencias circulares. Todo esta mal.
Del mismo modo, no es necesario que un widget conozca a su padre. Dirá: "Ok, pero el widget necesita que su padre se distribuya correctamente, etc." y debajo del capó, el widget conoce a su padre y le pide sus métricas para calcular sus propias métricas basadas en ellas, etc. Según lo dicho, no pregunte que esto está mal. El padre debe decirle a todos sus hijos que rindan, pasando toda la información necesaria como argumentos. De esta manera, uno puede tener fácilmente el mismo widget en dos padres (ya sea que tenga sentido o no).
Para volver al ejemplo del libro, ingrese el bibliotecario:
Por favor, comprenda que ya no estamos preguntando en el sentido de decir, no pregunte .
En la primera versión, el estante era una propiedad del libro y usted lo solicitó. En la segunda versión, no hacemos tal suposición sobre el libro. Todo lo que sabemos es que el bibliotecario puede decirnos un estante que contiene el libro. Presumiblemente, se basa en algún tipo de tabla de búsqueda para hacerlo, pero no lo sabemos con certeza (también podría simplemente recorrer todos los libros en cada estante) y, en realidad , no queremos saberlo .
No confiamos en si la respuesta de los bibliotecarios está acoplada a su estado o el de otros objetos de los que dependa. Le decimos al bibliotecario que busque el estante.
En el primer escenario, codificamos directamente la relación entre un libro y un estante como parte del estado del libro y recuperamos este estado directamente. Esto es muy frágil, porque también suponemos que el estante devuelto por el libro contiene el libro (esta es una restricción que debemos garantizar, de lo contrario podríamos no poder hacer
remove
el libro desde el estante en el que realmente está).Con la introducción del bibliotecario, modelamos esta relación por separado, logrando así la separación de la preocupación.
fuente
Si necesita saber el resultado, entonces lo sabe; Ese es tu requisito.
La frase "los métodos deberían devolver un valor solo si son referencialmente transparentes y, por lo tanto, no poseen efectos secundarios" es una buena guía a seguir (especialmente si está escribiendo su programa en un estilo funcional , por concurrencia u otras razones), pero es No es un absoluto.
Por ejemplo, es posible que necesite saber el resultado de una operación de escritura de archivo (verdadero o falso). Ese es un ejemplo de un método que devuelve un valor, pero siempre produce efectos secundarios; no hay forma de evitar eso.
Para completar la Separación de Comando / Consulta, tendría que realizar la operación de archivo con un método, y luego verificar su resultado con otro método, una técnica indeseable porque desacopla el resultado del método que causó el resultado. ¿Qué pasa si algo le sucede al estado de su objeto entre la llamada al archivo y la verificación de estado?
La conclusión es esta: si está utilizando el resultado de una llamada a un método para tomar decisiones en otra parte del programa, entonces no está violando Tell Don't Ask. Si, por otro lado, está tomando decisiones para un objeto en función de una llamada de método a ese objeto, entonces debe mover esas decisiones al objeto mismo para preservar la encapsulación.
Esto no es en absoluto contradictorio con la separación de consultas de comandos; de hecho, lo impone aún más, porque ya no es necesario exponer un método externo para propósitos de estado.
fuente
Creo que debes seguir tu instinto inicial. A veces, el diseño debe ser intencionalmente peligroso, para introducir complejidad de inmediato cuando te comunicas con el objeto de manera que puedas forzarlo a manejarlo de inmediato, en lugar de tratar de manejarlo en el objeto en sí mismo y ocultar todos los detalles sangrientos y dificultar la limpieza cambiar los supuestos subyacentes
Debería tener una sensación de hundimiento si un objeto le ofrece equivalentes implementados
open
yclose
comportamientos e inmediatamente comienza a comprender a qué se enfrenta cuando ve un valor de retorno booleano para algo que pensó que sería una tarea atómica simple.Cómo lidiar más adelante es lo tuyo. Puede crear una abstracción sobre ella, un tipo de widget con
removeFromParent()
, pero siempre debe tener un respaldo de bajo nivel, de lo contrario posiblemente estaría haciendo suposiciones prematuras.No intentes simplificar todo. No hay nada tan decepcionante cuando confías en algo que parece elegante y tan inocente, solo para darte cuenta de que es un verdadero horror más tarde en los peores momentos.
fuente
Lo que usted describe es una "excepción" conocida al principio de Separación de consulta de comando.
En este artículo, Martin Fowler explica cómo amenazaría con tal excepción.
En su ejemplo, consideraría la misma excepción.
fuente
La separación de Comando-Consulta es increíblemente fácil de entender mal.
Si te digo en mi sistema, hay un comando que también devuelve un valor de una consulta y dices "¡Ja, estás en violación!" Estás saltando el arma.
No, eso no es lo que está prohibido.
Lo que está prohibido es cuando ese comando es la ÚNICA forma de hacer esa consulta. No. No debería tener que cambiar de estado para hacer preguntas. Eso no significa que tenga que cerrar los ojos cada vez que cambio de estado.
No importa si lo hago, ya que voy a abrirlos de nuevo de todos modos. El único beneficio de esta reacción exagerada fue garantizar que los diseñadores no se volvieran perezosos y olvidaran incluir la consulta que no cambia el estado.
El verdadero significado es tan sutil que es más fácil para la gente perezosa decir que los acomodadores deberían volver sin efecto. Esto hace que sea más fácil asegurarse de que no está violando la regla real, pero es una reacción exagerada que puede convertirse en un verdadero dolor.
Las interfaces fluidas y los iDSL "violan" esta reacción exagerada todo el tiempo. Se ignora mucho poder si reaccionas en exceso.
Puede argumentar que un comando debe hacer solo una cosa y una consulta debe hacer solo una cosa. Y en muchos casos ese es un buen punto. ¿Quién puede decir que solo hay una consulta que debe seguir un comando? Bien, pero eso no es separación de consulta de comando. Ese es el principio de responsabilidad única.
Visto de esta manera y un stack pop no es una extraña excepción. Es una sola responsabilidad. Pop solo viola la separación de consultas de comandos si olvida proporcionar un vistazo.
fuente