Aquí hay un problema con el que me encuentro con frecuencia: que haya un proyecto de tienda web que tenga una clase de Producto. Quiero agregar una función que permita a los usuarios publicar comentarios en un producto. Entonces tengo una clase Review que hace referencia a un producto. Ahora necesito un método que enumere todas las revisiones de un producto. Hay dos posibilidades:
(UNA)
public class Product {
...
public Collection<Review> getReviews() {...}
}
(SI)
public class Review {
...
static public Collection<Review> forProduct( Product product ) {...}
}
Al mirar el código, elegiría (A): no es estático y no necesita un parámetro. Sin embargo, siento que (A) viola el Principio de Responsabilidad Única (SRP) y el Principio Abierto-Cerrado (OCP) mientras que (B) no:
(SRP) Cuando quiero cambiar la forma en que se recopilan las revisiones de un producto, tengo que cambiar la clase de Producto. Pero solo debe haber una razón para cambiar la clase de Producto. Y ciertamente no son las reseñas. Si empaqueto todas las funciones que tienen algo que ver con los productos en el Producto, pronto saldrán ruidosamente.
(OCP) Tengo que cambiar la clase de Producto para ampliarla con esta función. Creo que esto viola la parte del principio "Cerrado por cambio". Antes de recibir la solicitud del cliente para implementar las revisiones, consideré el Producto como terminado y lo "cerré".
¿Qué es más importante: seguir los principios SÓLIDOS o tener una interfaz más simple?
¿O estoy haciendo algo mal aquí por completo?
Resultado
¡Guau, gracias por todas tus excelentes respuestas! Es difícil elegir uno como respuesta oficial.
Permítanme resumir los principales argumentos de las respuestas:
- pro (A): OCP no es una ley y la legibilidad del código también es importante.
- pro (A): la relación de la entidad debe ser navegable. Ambas clases pueden saber sobre esta relación.
- pro (A) + (B): haga ambas cosas y delegue en (A) a (B) para que sea menos probable que se vuelva a cambiar el Producto.
- pro (C): coloca los métodos del buscador en tercera clase (servicio) donde no es estático.
- contra (B): impide la burla en las pruebas.
Algunas cosas adicionales que mis universidades en el trabajo contribuyeron:
- pro (B): nuestro marco ORM puede generar automáticamente el código para (B).
- pro (A): por razones técnicas de nuestro marco ORM, será necesario cambiar la entidad "cerrada" en algunos casos, independientemente de dónde vaya el buscador. Por lo tanto, no siempre podré apegarme a SOLID.
- contra (C): mucho alboroto ;-)
Conclusión
Estoy usando ambos (A) + (B) con delegación para mi proyecto actual. Sin embargo, en un entorno orientado a servicios, iré con (C).
Assert(5 = Math.Abs(-5));
Abs()no es el problema, probar algo que depende de él lo es. No tiene una costura para aislar el Código dependiente bajo prueba (CUT) para usar un simulacro. Esto significa que no puede probarlo como una unidad atómica y todas sus pruebas se convierten en pruebas de integración que prueban la lógica de la unidad. Una falla en una prueba podría estar en CUT o en Abs()(o su código dependiente) y elimina los beneficios de diagnóstico de las pruebas unitarias.