Tengo un método que crea un archivo de datos después de hablar con una pizarra digital:
CreateDataFile(IFileAccess boardFileAccess, IMeasurer boardMeasurer)
Aquí boardFileAccess
y boardMeasurer
son la misma instancia de un Board
objeto que implementa ambos IFileAccess
y IMeasurer
. IMeasurer
se utiliza en este caso para un método único que activará un pin en el tablero para hacer una medición simple Los datos de esta medición se almacenan localmente en el tablero usando IFileAccess
. Board
Se encuentra en un proyecto separado.
Llegué a la conclusión de que CreateDataFile
está haciendo una cosa al hacer una medición rápida y luego almacenar los datos, y hacer ambas cosas con el mismo método es más intuitivo para alguien que usa este código y luego tiene que hacer una medición y escribir en un archivo como llamadas a métodos separados.
Para mí, parece incómodo pasar el mismo objeto a un método dos veces. He considerado hacer una interfaz local IDataFileCreator
que se extienda IFileAccess
y IMeasurer
luego tenga una implementación que contenga una Board
instancia que solo llame a los Board
métodos requeridos . Teniendo en cuenta que el mismo objeto de pizarra siempre se usaría para la medición y la escritura de archivos, ¿es una mala práctica pasar el mismo objeto a un método dos veces? Si es así, ¿está utilizando una interfaz local e implementación una solución adecuada?
fuente
Respuestas:
No, esto está perfectamente bien. Simplemente significa que la API está sobredimensionada con respecto a su aplicación actual .
Pero eso no prueba que nunca habrá un caso de uso en el que la fuente de datos y el medidor sean diferentes. El objetivo de una API es ofrecer al programador de aplicaciones posibilidades, no todas las cuales se utilizarán. No debe restringir artificialmente lo que pueden hacer los usuarios de API a menos que complique la API de modo que la comprensibilidad neta disminuya.
fuente
De acuerdo con la respuesta de @ KilianFoth de que esto está perfectamente bien.
Aún así, si lo desea, puede crear un método que tome un solo objeto que implemente ambas interfaces:
No hay una razón general por la que los argumentos deban ser objetos diferentes, y si un método requiriera que los argumentos fueran diferentes, ese sería un requisito especial que su contrato debería aclarar.
fuente
Creo que este es tu problema, en realidad. El método no está haciendo una cosa. Realiza dos operaciones distintas que involucran E / S a diferentes dispositivos , y ambas se descargan a otros objetos:
Estas son dos operaciones de E / S diferentes. Notablemente, el primero no muta el sistema de archivos de ninguna manera.
De hecho, debemos tener en cuenta que hay un paso medio implícito:
Su API debe proporcionar cada uno de estos por separado en alguna forma. ¿Cómo sabe que una persona que llama no querrá tomar una medida sin almacenarla en algún lugar? ¿Cómo sabe que no querrán obtener una medición de otra fuente? ¿Cómo sabes que no querrán almacenarlo en otro lugar que no sea el dispositivo? Hay buenas razones para desacoplar las operaciones. En un desnudo mínimo, cada pieza debe estar disponible a cualquier persona que llama. No debería ser obligado a escribir la medida en un archivo si mi caso de uso no lo requiere.
Como ejemplo, puede separar las operaciones de esta manera.
IMeasurer
tiene una forma de obtener la medida:Su tipo de medición podría ser algo simple, como a
string
odecimal
. No insisto en que necesita una interfaz o clase para ello, pero hace que el ejemplo aquí sea más general.IFileAccess
tiene algún método para guardar archivos:Entonces necesita una forma de serializar una medición. Construya eso en la clase o interfaz que representa una medida, o tenga un método de utilidad:
No está claro si todavía tiene esta operación de serialización separada.
Este tipo de separación mejora su API. Le permite a la persona que llama decidir qué necesita y cuándo, en lugar de forzar sus ideas preconcebidas sobre lo que debe realizar la E / S. Las personas que llaman deben tener el control para realizar cualquier operación válida , si crees que es útil o no.
Una vez que tenga implementaciones separadas para cada operación, su
CreateDataFile
método se convierte en una abreviatura paraEn particular, su método agrega muy poco valor una vez que haya hecho todo esto. La línea de código anterior no es difícil de usar para las personas que llaman directamente, y su método es puramente para mayor comodidad. Debería ser y es algo opcional . Y esa es la forma correcta de comportamiento de la API.
Una vez que se tengan en cuenta todas las partes relevantes y hayamos reconocido que el método es solo una conveniencia, debemos reformular su pregunta:
¿Cuál sería el caso de uso más común para sus llamantes?
Si el objetivo es hacer que el caso de uso típico de medir y escribir en la misma pizarra sea un poco más conveniente, entonces tiene mucho sentido que esté disponible
Board
directamente en la clase:Si esto no mejora la conveniencia, entonces no me molestaría en absoluto con el método.
Al tratarse de un método de conveniencia, surge otra pregunta.
¿Debería la
IFileAccess
interfaz conocer el tipo de medición y cómo serializarlo? Si es así, puede agregar un método paraIFileAccess
:Ahora las personas que llaman solo hacen esto:
que es tan corto y probablemente más claro que su método de conveniencia como se concibe en la pregunta.
fuente
El cliente consumidor no debería tener que lidiar con un par de artículos cuando un solo artículo es suficiente. En su caso, casi no lo hacen, hasta la invocación de
CreateDataFile
.La posible solución que sugiere es crear una interfaz derivada combinada. Sin embargo, este enfoque requiere un único objeto que implemente ambas interfaces, lo cual es bastante restrictivo, posiblemente una abstracción permeable en el sentido de que está básicamente personalizado para una implementación particular. Considere lo complicado que sería si alguien quisiera implementar las dos interfaces en objetos separados: tendrían que proxy todos los métodos en una de las interfaces para reenviar al otro objeto. (FWIW, otra opción es simplemente fusionar las interfaces en lugar de requerir que un objeto tenga que implementar dos interfaces a través de una interfaz derivada).
Sin embargo, otro enfoque que limita o dicta menos la implementación es que
IFileAccess
se combina con unaIMeasurer
composición, de modo que uno de ellos está vinculado y hace referencia al otro. (Esto aumenta un poco la abstracción de uno de ellos, ya que ahora también representa el emparejamiento). Luego,CreateDataFile
podría tomar solo una de las referencias, por ejemploIFileAccess
, y aún obtener la otra según sea necesario. Su implementación actual como un objeto que implementa ambas interfaces sería simplementereturn this;
para la referencia de composición, aquí el captador paraIMeasurer
adentroIFileAccess
.Si el emparejamiento resulta ser falso en algún momento del desarrollo, es decir, a veces se usa un medidor diferente con el mismo acceso a archivos, entonces puede hacer este mismo emparejamiento pero en un nivel superior, lo que significa que la interfaz adicional introducida no será una interfaz derivada, sino más bien una interfaz que tiene dos captadores, emparejando un acceso a archivos y un medidor juntos a través de la composición en lugar de la derivación. Entonces, el cliente consumidor tiene un artículo con el que preocuparse mientras se mantenga el emparejamiento, y objetos individuales con los que lidiar (para componer nuevos emparejamientos) cuando sea necesario.
En otra nota, podría preguntar quién es el propietario
CreateDataFile
, y la pregunta es quién es este tercero. Ya tenemos algún cliente consumidor que invocaCreateDataFile
, el objeto / clase propietarioCreateDataFile
y elIFileAccess
yIMeasurer
. A veces, cuando tenemos una visión más amplia del contexto, pueden aparecer organizaciones alternativas, a veces mejores. Difícil de hacer aquí ya que el contexto es incompleto, por lo que es solo un pensamiento.fuente
Algunos han mencionado que eso
CreateDataFile
está haciendo demasiado. Podría sugerir que, en cambio,Board
está haciendo demasiado, ya que acceder a un archivo parece ser una preocupación separada del resto del tablero.Sin embargo, si suponemos que esto no es un error, el mayor problema es que la interfaz debe ser definida por el cliente, en este caso
CreateDataFile
.El Principio de segregación de interfaz establece que el cliente no debería tener que depender de más de una interfaz de la que necesita. Tomando prestada la frase de esta otra respuesta , esto se puede parafrasear como "una interfaz está definida por lo que el cliente necesita".
Ahora, es posible componer esta interfaz específica del cliente usando
IFileAccess
yIMeasurer
como sugieren otras respuestas, pero en última instancia, este cliente debe tener una interfaz a medida para ella.fuente