oop class-design

oop - ¿Qué clase de diseño es mejor?



class-design (11)

¿Qué diseño de clase es mejor y por qué?

public class User { public String UserName; public String Password; public String FirstName; public String LastName; } public class Employee : User { public String EmployeeId; public String EmployeeCode; public String DepartmentId; } public class Member : User { public String MemberId; public String JoinDate; public String ExpiryDate; }

O

public class User { public String UserId; public String UserName; public String Password; public String FirstName; public String LastName; } public class Employee { public User UserInfo; public String EmployeeId; public String EmployeeCode; public String DepartmentId; } public class Member { public User UserInfo; public String MemberId; public String JoinDate; public String ExpiryDate; }


Aquí hay un escenario en el que debes pensar:

La composición (el segundo ejemplo) es preferible si el mismo usuario puede ser tanto un empleado como un miembro. ¿Por qué? Debido a que para dos instancias (empleado y miembro) que representan el mismo usuario, si los datos del usuario cambian, no tiene que actualizarlo en dos lugares. Solo la instancia del usuario contiene toda la información del usuario y solo tiene que actualizarse. Dado que las clases de Empleado y Miembro contienen la misma instancia de Usuario, ambas contendrán automáticamente la información actualizada.


Establecer su requisito / especificación podría ayudar a llegar al ''mejor diseño''.
Su pregunta también es ''interpretación de sujeto a lector'' en este momento.


La pregunta simplemente se responde reconociendo que la herencia modela una relación "IS-A", mientras que la membresía modela una relación "HAS-A".

  • Un empleado ES un usuario
  • Un empleado TIENE una información de usuario

¿Cuál es correcto? Esta es tu respuesta.


Las verdaderas preguntas son:

  • ¿Cuáles son las reglas comerciales y las historias de usuario detrás de un usuario?
  • ¿Cuáles son las reglas comerciales y las historias de usuario detrás de un empleado?
  • ¿Cuáles son las reglas comerciales y las historias de usuario detrás de un miembro?

Estas pueden ser tres entidades completamente independientes o no, y eso determinará si su primer o segundo diseño funcionará, o si otro diseño completamente diferente está en orden.


Ninguno de los dos es bueno. Demasiado estado mutable. No debería poder construir una instancia de una clase que esté en un estado inválido o parcialmente inicializado.

Dicho esto, el segundo es mejor porque favorece la composición sobre la herencia.


No creo que la composición siempre sea mejor que la herencia (solo por lo general). Si el empleado y el miembro realmente son usuarios, y son mutuamente excluyentes, entonces el primer diseño es mejor. Considere el escenario donde necesita acceder al nombre de usuario de un empleado. Usando el segundo diseño tendrías:

myEmployee.UserInfo.UserName

que es malo (la ley de Demeter), por lo que se refactorizaría a:

myEmployee.UserName

que requiere un pequeño método en Employee para delegar en el objeto User. Todo lo cual es evitado por el primer diseño.


No me gusta ninguno de los dos. ¿Qué sucede cuando alguien es miembro y empleado?


Pregúntese lo siguiente:

  • ¿Desea modelar un Empleado ES un Usuario? Si es así, elige herencia.
  • ¿Desea modelar un empleado TIENE una información de usuario? Si es así, usa la composición.
  • ¿Hay funciones virtuales involucradas entre el usuario (información) y el empleado? Si es así, usa herencia.
  • ¿Puede un empleado tener múltiples instancias de usuario (información)? Si es así, usa la composición.
  • ¿Tiene sentido asignar un objeto Employee a un objeto User (info)? Si es así, usa herencia.

En general, trate de modelar la realidad que simula su programa, bajo las limitaciones de la complejidad del código y la eficiencia requerida.


Tres opciones más:

  1. Haga que la clase de User contenga la información complementaria para los empleados y los miembros, con los campos no utilizados en blanco (el ID de un User particular indicaría si el usuario era un empleado, miembro, ambos o lo que sea).

  2. Tener una clase de User que contenga una referencia a ISupplementalInfo , donde ISupplementalInfo es heredado por ISupplementalEmployeeInfo , ISupplementalMemberInfo , etc. El código aplicable a todos los usuarios podría funcionar con objetos de la clase User , y el código que tenía una referencia de User podría tener acceso a un usuario información complementaria, pero este enfoque evitaría tener que cambiar de User si se requieren diferentes combinaciones de información suplementaria en el futuro.

  3. Como se ISupplementalInfo anteriormente, pero la clase User contiene algún tipo de colección de ISupplementalInfo . Este enfoque tendría la ventaja de facilitar la adición de propiedades en tiempo de ejecución a un usuario (por ejemplo, porque un Member fue contratado). Al usar el enfoque anterior, uno debería definir diferentes clases para diferentes combinaciones de propiedades; Convertir un "miembro" en un "miembro + cliente" requeriría que un código diferente convirtiera a un "empleado" en un "empleado + cliente". La desventaja de este último enfoque es que haría más difícil protegerse de los atributos redundantes o inconsistentes (usar algo como Dictionary<Type, ISupplementalInfo> para contener información complementaria podría funcionar, pero parecería un poco "voluminoso").

Tiendo a favorecer el segundo enfoque, ya que permite una expansión futura mejor que la herencia directa. Trabajar con una colección de objetos en lugar de un solo objeto puede ser un poco pesado, pero ese enfoque puede ser más capaz que los otros de manejar los requisitos cambiantes.


También puede pensar en el empleado como un papel del usuario (persona). El rol de un Usuario puede cambiar a tiempo (el usuario puede quedar desempleado) o el Usuario puede tener múltiples roles al mismo tiempo.

La herencia es mucho mejor cuando existe una relación real "es una", por ejemplo, Apple - Fruit. Pero tenga mucho cuidado: Circunstalar - Elipse no es real "es una" relación, porque el círculo tiene menos "libertad" que la elipse (el círculo es un estado de elipse) - vea: Círculo del problema de la elipse .


Buena pregunta, aunque para evitar distracciones sobre lo correcto y lo incorrecto , consideraría preguntar los pros y los contras de cada enfoque. Creo que eso es lo que quiso decir con lo que es mejor o peor y por qué. De todas formas ....

El primer enfoque, también conocido como herencia

Pros:

  • Permite el comportamiento polimórfico.
  • Es inicialmente simple y conveniente.

Contras:

  • Puede volverse complejo o torpe con el tiempo si se agregan más comportamientos y relaciones.

La segunda aproximación aka composición

Pros:

  • Se adapta bien a escenarios no relacionados como tablas relacionales, programación estructurada, etc.
  • Es sencillo (si no necesariamente conveniente) ampliar de forma incremental las relaciones y el comportamiento.

Contras:

  • Sin polimorfismo, por lo tanto, es menos conveniente usar información relacionada y comportamiento

Listas como estas + las preguntas que Jon Limjap mencionó lo ayudarán a tomar decisiones y comenzar; luego podrá encontrar las respuestas correctas ;-)