Hace un cuarto de siglo, cuando estaba aprendiendo C ++, me enseñaron que las interfaces deberían ser indulgentes y, en la medida de lo posible, no preocuparse por el orden en que se llamaron los métodos, ya que el consumidor puede no tener acceso a la fuente o la documentación en lugar de esta.
Sin embargo, cada vez que he sido mentor de programadores junior y desarrolladores senior me han escuchado, reaccionan con asombro, lo que me hace preguntarme si esto realmente fue una cosa o si simplemente se ha pasado de moda.
¿Tan claro como el barro?
Considere una interfaz con estos métodos (para crear archivos de datos):
OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile
Ahora, por supuesto, podría ir a través de estos en orden, pero decir que no le importaba el nombre del archivo (pensar a.out
) o qué encabezado y cadena de avance se incluían, simplemente podía llamar AddDataLine
.
Un ejemplo menos extremo podría ser omitir los encabezados y trailers.
Otro podría estar configurando las cadenas de encabezado y avance antes de que se abra el archivo.
¿Es este un principio de diseño de interfaz reconocido o solo POLA antes de que se le pusiera un nombre?
NB no se atasque en las minucias de esta interfaz, es solo un ejemplo en aras de esta pregunta.
fuente
Respuestas:
Una forma en la que puede apegarse al principio de menor asombro es considerar otros principios como ISP y SRP , o incluso DRY .
En el ejemplo específico que ha dado, la sugerencia parece ser que existe una cierta dependencia de ordenar para manipular el archivo; pero su API controla tanto el acceso a los archivos como el formato de datos, lo que huele un poco a una violación de SRP.
Editar / Actualizar: también sugiere que la API en sí misma le está pidiendo al usuario que viole DRY, ya que deberán repetir los mismos pasos cada vez que usen la API .
Considere una API alternativa donde las operaciones de E / S están separadas de las operaciones de datos. y donde la propia API 'posee' el pedido:
ContentBuilder
FileWriter
Con la separación anterior,
ContentBuilder
no es necesario "hacer" nada aparte de almacenar las líneas / encabezado / avance (tal vez también unContentBuilder.Serialize()
método que conoce el orden). Al seguir otros principios SÓLIDOS, ya no importa si configura el encabezado o el avance antes o después de agregar líneas, porque nada en elContentBuilder
archivo se escribe realmente en el archivo hasta que se pasa aFileWriter.Write
.También tiene el beneficio adicional de ser un poco más flexible; por ejemplo, podría ser útil escribir el contenido en un registrador de diagnóstico, o tal vez pasarlo a través de una red en lugar de escribirlo directamente en un archivo.
Al diseñar una API, también debe considerar la notificación de errores, ya sea un estado, un valor de retorno, una excepción, una devolución de llamada u otra cosa. El usuario de la API probablemente esperará poder detectar mediante programación cualquier violación de sus contratos, o incluso otros errores que no pueda controlar, como errores de E / S de archivos.
fuente
SetHeader
seaAddLine
importante. Para eliminar esta dependencia del orden no es ISP ni SRP, es simplemente POLA.FileWriter
podría requerir el valor del últimoContentBuilder
paso delWrite
método para garantizar que todo el contenido de entrada esté completo, lo que haceInvalidContentException
innecesario.ContentBuilder
y permitirFileWriter.Write
encapsular ese bit de conocimiento. La excepción sería necesaria en caso de que algo esté mal con el contenido (por ejemplo, como un encabezado faltante). Un retorno también podría funcionar, pero no soy fanático de convertir las excepciones en códigos de retorno.No se trata solo de POLA, sino también de prevenir un estado no válido como una posible fuente de errores.
Veamos cómo podemos proporcionar algunas restricciones a su ejemplo sin proporcionar una implementación concreta:
Primer paso: no permita que se llame a nada antes de abrir un archivo.
Ahora debería ser obvio que se
CreateDataFileInterface.OpenFile
debe llamar para recuperar unaDataFileInterface
instancia, donde se pueden escribir los datos reales.Segundo paso: asegúrese de que los encabezados y trailers siempre estén configurados.
Ahora debe proporcionar todos los parámetros necesarios por adelantado para obtener un
DataFileInterface
: nombre de archivo, encabezado y avance. Si la cadena de avance no está disponible hasta que se escriben todas las líneas, también puede mover este parámetro aClose()
(posiblemente renombrando el método aWriteTrailerAndClose()
) para que el archivo al menos no pueda finalizar sin una cadena de avance.Para responder al comentario:
Cierto. No quería concentrarme más en el ejemplo de lo necesario para hacer mi punto, pero es una buena pregunta. En este caso, creo que lo llamaría
Finalize(trailer)
y argumentaría que no hace demasiado. Escribir el tráiler y cerrar son simples detalles de implementación. Pero si no está de acuerdo o tiene una situación similar en la que es diferente, aquí hay una posible solución:Realmente no lo haría para este ejemplo, pero muestra cómo llevar a cabo la técnica en consecuencia.
Por cierto, supuse que los métodos deben llamarse en este orden, por ejemplo, para escribir secuencialmente muchas líneas. Si esto no es necesario, siempre preferiría un constructor, como lo sugirió Ben Cottrel .
fuente
WriteTrailerAndClose()
) está al borde de una violación de SRP. (Esto es algo con lo que he luchado en varias ocasiones, pero su sugerencia parece ser un posible ejemplo). ¿Cómo respondería?OpenFile
sobrecarga que no requiera una.