Hoy tuve una discusión con alguien.
Estaba explicando los beneficios de tener un modelo de dominio rico en lugar de un modelo de dominio anémico. Y demostré mi punto con una clase simple que se ve así:
public class Employee
{
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastname;
}
public string FirstName { get private set; }
public string LastName { get; private set;}
public int CountPaidDaysOffGranted { get; private set;}
public void AddPaidDaysOffGranted(int numberOfdays)
{
// Do stuff
}
}
Mientras defendía su enfoque de modelo anémico, uno de sus argumentos fue: "Soy un creyente en SOLID . Usted está violando el principio de responsabilidad única (SRP) ya que ambos representan datos y realizan la lógica en la misma clase".
Encontré esta afirmación realmente sorprendente, ya que siguiendo este razonamiento, cualquier clase que tenga una propiedad y un método viola el SRP y, por lo tanto, la POO en general no es SÓLIDA, y la programación funcional es el único camino al cielo.
Decidí no responder a sus muchos argumentos, pero tengo curiosidad por saber qué piensa la comunidad sobre esta pregunta.
Si hubiera respondido, habría comenzado señalando la paradoja mencionada anteriormente, y luego habría indicado que el SRP depende en gran medida del nivel de granularidad que desee tener en cuenta y que si lo lleva lo suficientemente lejos, cualquier clase que contenga más de uno propiedad o un método lo viola.
¿Que hubieras dicho tu?
Actualización: guntbert ha actualizado generosamente el ejemplo para que el método sea más realista y nos ayude a centrarnos en la discusión subyacente.
fuente
Respuestas:
La responsabilidad única debe entenderse como una abstracción de tareas lógicas en su sistema. Una clase debe tener la responsabilidad única de (hacer todo lo necesario para) realizar una tarea única y específica. Esto realmente puede aportar mucho a una clase bien diseñada, dependiendo de cuál sea la responsabilidad. La clase que ejecuta su motor de script, por ejemplo, puede tener muchos métodos y datos involucrados en el procesamiento de scripts.
Su compañero de trabajo se está centrando en lo incorrecto. La pregunta no es "¿qué miembros tiene esta clase?" pero "¿qué operaciones útiles realiza esta clase dentro del programa?" Una vez que se entiende, su modelo de dominio se ve muy bien.
fuente
El Principio de responsabilidad única solo se refiere a si un código (o no, en OOP, generalmente estamos hablando de clases) tiene responsabilidad sobre un componente funcional . Creo que tu amigo que dice que las funciones y los datos no pueden mezclarse realmente no entendió esa idea. Si
Employee
también contuviera información sobre su lugar de trabajo, qué tan rápido va su automóvil y qué tipo de comida come su perro, entonces tendríamos un problema.Como esta clase solo trata con un
Employee
, creo que es justo decir que no viola SRP descaradamente, pero las personas siempre van a tener sus propias opiniones.Un lugar donde podríamos mejorar es separar la información del Empleado (como nombre, número de teléfono, correo electrónico) de sus vacaciones. En mi opinión, esto no significa que los métodos y los datos no puedan mezclarse , solo significa que quizás la funcionalidad de las vacaciones podría estar en un lugar separado.
fuente
En mi opinión, esta clase podría violar potencialmente SRP si continuara representando tanto an
Employee
comoEmployeeHolidays
.Tal como están las cosas, y si se me ocurriera para Peer Review, probablemente lo dejaría pasar. Si se añadieran más propiedades y métodos específicos para empleados, y se agregaran más propiedades específicas para vacaciones, probablemente recomendaría una división, citando tanto SRP como ISP.
fuente
Ya hay excelentes respuestas que señalan que SRP es un concepto abstracto sobre la funcionalidad lógica, pero hay puntos más sutiles que creo que vale la pena agregar.
Las dos primeras letras en SOLID, SRP y OCP tratan sobre cómo cambia su código en respuesta a un cambio en los requisitos. Mi definición favorita del SRP es: "un módulo / clase / función debe tener una sola razón para cambiar". Discutir sobre las posibles razones para que cambie su código es mucho más productivo que discutir sobre si su código es SÓLIDO o no.
¿Cuántas razones tiene que cambiar su clase de empleado? No lo sé, porque no sé el contexto en el que lo estás usando, y tampoco puedo ver el futuro. Lo que puedo hacer es hacer una lluvia de ideas sobre posibles cambios basados en lo que he visto en el pasado, y usted puede evaluar subjetivamente qué tan probable es. Si hay más de un puntaje entre "razonablemente probable" y "mi código ya ha cambiado por ese motivo exacto", está violando SRP contra ese tipo de cambio. Aquí hay uno: alguien con más de dos nombres se une a su empresa (o un arquitecto lee este excelente artículo del W3C ). Aquí hay otro: su empresa cambia la forma en que asigna los días festivos.
Tenga en cuenta que estos motivos son igualmente válidos incluso si elimina el método AddHolidays. Muchos modelos de dominio anémico violan el SRP. Muchos de ellos son solo tablas de bases de datos en código, y es muy común que las tablas de bases de datos tengan más de 20 razones para cambiar.
Aquí hay algo para analizar: ¿cambiaría su clase de Empleado si su sistema necesitara rastrear los salarios de los empleados? Direcciones? ¿Informacion de contacto de emergencia? Si dijiste "sí" (y "es probable que suceda") a dos de ellos, entonces tu clase estaría violando SRP incluso si todavía no tuviera código. SOLID se trata tanto de procesos y arquitectura como de código.
fuente
Que la clase represente datos no es responsabilidad de la clase, es un detalle de implementación privado.
La clase tiene una responsabilidad, representar a un empleado. En este contexto, eso significa que presenta una API pública que le brinda la funcionalidad que necesita para tratar con los empleados (si AddHolidays es un buen ejemplo es discutible).
La implementación es interna; Sucede que esto necesita algunas variables privadas y algo de lógica. Eso no significa que la clase ahora tenga múltiples responsabilidades.
fuente
La idea de que mezclar lógica y datos de alguna manera siempre es incorrecta, es tan ridícula que ni siquiera merece discusión. Sin embargo, hay una clara violación de la responsabilidad individual en el ejemplo, pero no es porque haya una propiedad
DaysOfHolidays
y una funciónAddHolidays(int)
.Se debe a que la identidad del empleado está entremezclada con la gestión de las vacaciones, lo cual es malo. La identidad del empleado es algo complejo que se requiere para rastrear las vacaciones, el salario, las horas extra, para representar quién está en qué equipo, para vincular a los informes de rendimiento, etc. Un empleado también puede cambiar el nombre, el apellido o ambos, y permanecer igual empleado. Los empleados pueden incluso tener múltiples deletreos de su nombre, como un ASCII y un deletreo unicode. Las personas pueden tener 0 a n nombres y / o apellidos. Pueden tener diferentes nombres en diferentes jurisdicciones. El seguimiento de la identidad de un empleado es una responsabilidad suficiente para que la administración de vacaciones o vacaciones no pueda agregarse sin llamarla una segunda responsabilidad.
fuente
Al igual que los demás, no estoy de acuerdo con esto.
Diría que se viola el SRP si está realizando más de una pieza de lógica en la clase. La cantidad de datos que deben almacenarse dentro de la clase para lograr esa única pieza de lógica es irrelevante.
fuente
No me parece útil en estos días debatir sobre qué constituye y qué no constituye una sola responsabilidad o una sola razón para cambiar. Yo propondría un Principio de pena mínima en su lugar:
¿Cómo es eso? No debería recurrir a un científico espacial para descubrir por qué esto puede ayudar a reducir los costos de mantenimiento y, con suerte, no debería ser un punto de debate interminable, pero como con SOLID en general, no es algo que se aplique a ciegas en todas partes. Es algo a tener en cuenta al equilibrar las compensaciones.
En cuanto a la probabilidad de requerir cambios, eso se reduce con:
En cuanto a la dificultad de realizar cambios, aumenta con acoplamientos eferentes. Las pruebas introducen acoplamientos eferentes pero mejoran la fiabilidad. Bien hecho, generalmente hace más bien que mal y es totalmente aceptable y promovido por el Principio Mínimo de Pena.
Y el Principio Make Badass está vinculado al Principio Mínimo de Pena, ya que las cosas rudas encontrarán una menor probabilidad de requerir cambios que las cosas que apestan a lo que hacen.
Desde el punto de vista de SRP, una clase que apenas hace algo ciertamente tendría solo una (a veces cero) razones para cambiar:
¡Eso prácticamente no tiene razones para cambiar! Es mejor que SRP. Es ZRP!
Excepto que sugeriría que es una violación flagrante del Principio Make Badass. También es absolutamente inútil. Algo que hace tan poco no puede esperar ser rudo. Tiene muy poca información (TLI). Y, naturalmente, cuando tienes algo que es TLI, no puede hacer nada realmente significativo, ni siquiera con la información que encapsula, por lo que tiene que filtrarlo al mundo exterior con la esperanza de que alguien realmente haga algo significativo y rudo. Y esa fuga está bien para algo que solo pretende agregar datos y nada más, pero ese umbral es la diferencia que veo entre "datos" y "objetos".
Por supuesto, algo que es TMI también es malo. Podríamos poner todo nuestro software en una clase. Incluso puede tener un solo
run
método. Y alguien podría incluso argumentar que ahora tiene una razón muy amplia para cambiar: "Esta clase solo tendrá que cambiarse si el software necesita mejoras". Estoy siendo tonto, pero por supuesto podemos imaginar todos los problemas de mantenimiento con eso.Por lo tanto, existe un acto de equilibrio en cuanto a la granularidad o aspereza de los objetos que diseña. A menudo lo juzgo por la cantidad de información que tiene que filtrar al mundo exterior y cuánta funcionalidad significativa puede realizar. A menudo encuentro útil el Principio Make Badass para encontrar el equilibrio mientras lo combino con el Principio Mínimo de Pena.
fuente
Por el contrario, para mí, el modelo de dominio anémico rompe algunos conceptos principales de OOP (que unen atributos y comportamiento), pero puede ser inevitable en función de las elecciones arquitectónicas. Los dominios anémicos son más fáciles de pensar, menos orgánicos y más secuenciales.
Muchos sistemas tienden a hacer esto cuando varias capas deben jugar con los mismos datos (capa de servicio, capa web, capa de cliente, agentes ...).
Es más fácil definir la estructura de datos en un lugar y el comportamiento en otras clases de servicio. Si se usó la misma clase en varias capas, esta puede crecer y pregunta qué capa es responsable de definir el comportamiento que necesita y quién puede llamar a los métodos.
Por ejemplo, puede que no sea una buena idea que un proceso de Agente que computa estadísticas de todos sus empleados pueda llamar a un cómputo por días pagados. Y la GUI de la lista de empleados ciertamente no necesita en absoluto el nuevo cálculo de ID agregado utilizado en este agente estadístico (y los datos técnicos que lo acompañan). Cuando segrega los métodos de esta manera, generalmente termina con una clase con solo estructuras de datos.
Puede serializar / deserializar con demasiada facilidad los datos del "objeto", o solo algunos de ellos, o en otro formato (json) ... sin preocuparse por ningún concepto / responsabilidad del objeto. Sin embargo, son solo datos que pasan. Siempre puede hacer un mapeo de datos entre dos clases (Employee, EmployeeVO, EmployeeStatistic ...) pero ¿qué significa realmente Employee aquí?
Entonces sí, separa completamente los datos en las clases de dominio y el manejo de datos en las clases de servicio, pero aquí se necesita. Dicho sistema es al mismo tiempo funcional para aportar valor comercial y también técnico para propagar los datos donde sea necesario, manteniendo un alcance de responsabilidad adecuado (y el objeto distribuido tampoco resuelve esto).
Si no necesita separar los ámbitos de comportamiento, tiene más libertad para colocar los métodos en clases de servicio o en clases de dominio, dependiendo de cómo vea su objeto. Tiendo a ver un objeto como el concepto "real", esto naturalmente ayuda a mantener SRP. Entonces, en su ejemplo, es más realista que el Día de pago adicional del Empleado del jefe otorgado a su PayDayAccount. Un empleado es contratado por la empresa, Works, puede estar enfermo o se le puede pedir un consejo, y él tiene una cuenta de Payday (el Jefe puede recuperarlo directamente de él o de un registro de PayDayAccount ...) Pero puede hacer un acceso directo agregado aquí por simplicidad si no quieres demasiada complejidad para un software simple.
fuente
A mí me parece muy razonable. El modelo podría no tener propiedades públicas si expone acciones. Básicamente es una idea de Separación de consulta de comando. Tenga en cuenta que Command tendrá un estado privado seguro.
fuente
No puede violar el Principio de responsabilidad única porque es solo un criterio estético, no una regla de la naturaleza. No se deje engañar por el nombre que suena científicamente y las letras mayúsculas.
fuente