Pregunta

Quiero hacer una pregunta sobre cómo abordaría un problema de diseño orientado a objetos simple. Tengo algunas ideas propias sobre cuál es la mejor manera de abordar este escenario, pero me interesaría escuchar algunas opiniones de la comunidad de Stack Overflow. También se aprecian enlaces a artículos relevantes en línea. Estoy usando C #, pero la pregunta no es específica del idioma.

Supongamos que estoy escribiendo una aplicación de video store cuya base de datos tiene una tabla Person , con PersonId , Name , DateOfBirth y Dirección . También tiene una tabla Personal , que tiene un enlace a un PersonId , y una tabla Cliente que también enlaza a PersonId .

Un enfoque orientado a objetos simple sería decir que un Cliente " es un " Person y, por lo tanto, crea clases como esta:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer : Person {
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff : Person {
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}

Ahora podemos escribir una función para enviar correos electrónicos a todos los clientes:

static void SendEmailToCustomers(IEnumerable<Person> everyone) { 
    foreach(Person p in everyone)
        if(p is Customer)
            SendEmail(p);
}

Este sistema funciona bien hasta que tenemos a alguien que es cliente y miembro del personal. Suponiendo que realmente no queremos que nuestra lista de todos tenga la misma persona dos veces, una vez como Customer y una vez como Staff , hacemos una elección arbitraria entre:

class StaffCustomer : Customer { ...

y

class StaffCustomer : Staff { ...

Obviamente, solo el primero de estos dos no romperá la función SendEmailToCustomers .

Entonces, ¿qué harías?

  • Haga que la clase Person tenga referencias opcionales a una clase StaffDetails y CustomerDetails ?
  • ¿Crear una nueva clase que contenga una Persona , más los opcionales StaffDetails y CustomerDetails ?
  • ¿Convierte todo en una interfaz (por ejemplo, IPerson , IStaff , ICustomer ) y crea tres clases que implementen las interfaces adecuadas?
  • ¿Toma otro enfoque completamente diferente?
¿Fue útil?

Solución

Mark, esta es una pregunta interesante. Encontrarás tantas opiniones sobre esto. No creo que haya una respuesta 'correcta'. Este es un gran ejemplo de cómo un diseño de objeto jerárquico rígido realmente puede causar problemas después de que se construye un sistema.

Por ejemplo, digamos que fuiste con el " Cliente " y " Personal " clases Implementas tu sistema y todo está contento. Unas semanas más tarde, alguien señala que están 'en el personal' y 'clientes' y que no reciben correos electrónicos de los clientes. En este caso, tiene que hacer muchos cambios de código (rediseñar, no factorizar).

Creo que sería demasiado complejo y difícil de mantener si intentas tener un conjunto de clases derivadas que implementen todas las permutaciones y combinaciones de personas y sus roles. Esto es especialmente cierto dado que el ejemplo anterior es muy simple: en la mayoría de las aplicaciones reales, las cosas serán más complejas.

Para su ejemplo aquí, me gustaría ir a " Tomar otro enfoque completamente diferente " ;. Implementaría la clase Persona e incluiría en ella una colección de "roles". Cada persona puede tener uno o más roles, como " Cliente " ;, " Personal " ;, y " Proveedor " ;.

Esto hará que sea más fácil agregar roles a medida que se descubren nuevos requisitos. Por ejemplo, simplemente puede tener una base " Role " clase, y derivar nuevos roles de ellos.

Otros consejos

Es posible que desee considerar el uso de Patrones de partido y responsabilidad

De esta manera, la Persona tendrá un conjunto de Responsabilidades que puede ser del tipo Cliente o Personal.

El modelo también será más sencillo si agrega más tipos de relaciones más adelante.

El enfoque puro sería: Hacer que todo sea una interfaz. Como detalles de implementación, puede usar opcionalmente cualquiera de las diversas formas de composición o herencia de implementación. Ya que estos son detalles de implementación, no son importantes para su API pública, por lo que puede elegir la que le haga la vida más sencilla.

Una persona es un ser humano, mientras que un cliente es solo un rol que una persona puede adoptar de vez en cuando. Hombre y Mujer serían candidatos para heredar Persona, pero Cliente es un concepto diferente.

El principio de sustitución de Liskov dice que debemos poder usar clases derivadas donde tenemos referencias a una clase base, sin saberlo. Tener una persona heredada por el cliente violaría esto. Un cliente podría ser también un papel desempeñado por una organización.

Hazme saber si entendí la respuesta de Foredecker correctamente. Aquí está mi código (en Python; lo siento, no sé C #). La única diferencia es que no notificaría algo si una persona " es un cliente " ;, lo haría si uno de sus roles " está interesado en " esa cosa. ¿Es esto suficientemente flexible?

# --------- PERSON ----------------

class Person:
    def __init__(self, personId, name, dateOfBirth, address):
        self.personId = personId
        self.name = name
        self.dateOfBirth = dateOfBirth
        self.address = address
        self.roles = []

    def addRole(self, role):
        self.roles.append(role)

    def interestedIn(self, subject):
        for role in self.roles:
            if role.interestedIn(subject):
                return True
        return False

    def sendEmail(self, email):
        # send the email
        print "Sent email to", self.name

# --------- ROLE ----------------

NEW_DVDS = 1
NEW_SCHEDULE = 2

class Role:
    def __init__(self):
        self.interests = []

    def interestedIn(self, subject):
        return subject in self.interests

class CustomerRole(Role):
    def __init__(self, customerId, joinedDate):
        self.customerId = customerId
        self.joinedDate = joinedDate
        self.interests.append(NEW_DVDS)

class StaffRole(Role):
    def __init__(self, staffId, jobTitle):
        self.staffId = staffId
        self.jobTitle = jobTitle
        self.interests.append(NEW_SCHEDULE)

# --------- NOTIFY STUFF ----------------

def notifyNewDVDs(emailWithTitles):
    for person in persons:
        if person.interestedIn(NEW_DVDS):
            person.sendEmail(emailWithTitles)

Evitaría el " es " check (& ??quot; instanceof " en Java). Una solución es utilizar un Decorator Pattern . Podría crear un EmailablePerson que decore a la Persona donde EmailablePerson usa la composición para mantener una instancia privada de una Persona y delega todos los métodos que no son de correo electrónico al objeto de la Persona.

Estudiamos este problema en la universidad el año pasado, estábamos aprendiendo eiffel, así que usamos la herencia múltiple. De todos modos, la alternativa de roles de Foredecker parece ser lo suficientemente flexible.

¿Qué tiene de malo enviar un correo electrónico a un cliente que es miembro del personal? Si él es un cliente, entonces se le puede enviar el correo electrónico. ¿Me equivoco al pensar así? ¿Y por qué debería tomar " todos " como su lista de correo electrónico? Por lo tanto, sería mejor tener una lista de clientes ya que estamos tratando con " sendEmailToCustomer " método y no " sendEmailToEveryone " ¿método? Incluso si desea utilizar " todos " lista no puede permitir duplicados en esa lista.

Si ninguno de estos se puede lograr con una gran cantidad de cambios, iré con la primera respuesta de Foredecker y es posible que tenga algunos roles asignados a cada persona.

Sus clases son solo estructuras de datos: ninguna de ellas tiene ningún comportamiento, solo captadores y definidores. La herencia es inapropiada aquí.

Adopte otro enfoque completamente diferente: el problema con la clase StaffCustomer es que su miembro del personal podría comenzar como personal y convertirse en cliente más adelante, por lo que tendría que eliminarlos como personal y crear una nueva instancia de la clase StaffCustomer . Tal vez un booleano simple dentro de la clase de Personal de 'isCustomer' permitiría que nuestra lista de todos (presumiblemente compilada de obtener todos los clientes y todo el personal de las tablas apropiadas) no obtenga al miembro del personal ya que sabrá que ya se ha incluido como cliente.

Aquí hay algunos consejos más: De la categoría de & # 8220; ni siquiera pienses en hacer esto & # 8221; Aquí hay algunos malos ejemplos de código encontrado:

El método del buscador devuelve Object

Problema: Dependiendo de la cantidad de ocurrencias encontradas, el método del buscador devuelve un número que representa la cantidad de ocurrencias & # 8211; ¡o! Si solo uno encontrado devuelve el objeto real.

¡No hagas esto! Esta es una de las peores prácticas de codificación e introduce ambigüedad y desordena el código de manera que cuando un desarrollador diferente entra en juego, ella o él te odiarán por hacer esto.

Solución: Si & # 8217; sa necesita esas 2 funcionalidades: contar y recuperar una instancia crea 2 métodos, uno que devuelve el recuento y otro que devuelve la instancia, pero nunca un solo método que haga ambas cosas.

Problema: una mala práctica derivada es cuando un método de búsqueda devuelve la única ocurrencia encontrada o una serie de ocurrencias si se encuentra más de una. Este estilo de programación perezoso se hace mucho por los programadores que hacen el anterior en general.

Solución: teniendo esto en mis manos, devolvería una matriz de longitud 1 (una) si solo se encuentra una aparición y una matriz con longitud > 1 si se encuentran más incidencias. Además, al no encontrar ninguna incidencia se devolvería un valor nulo o una matriz de longitud 0 según la aplicación.

Programación en una interfaz y uso de tipos de retorno covariantes

Problema: Programar en una interfaz y usar tipos de retorno covariantes y conversión en el código de llamada.

Solución: en su lugar, use el mismo supertipo definido en la interfaz para definir la variable que debe apuntar al valor devuelto. Esto mantiene la programación a un enfoque de interfaz y su código limpio.

Las clases con más de 1000 líneas son un peligro al acecho ¡Los métodos con más de 100 líneas también son un peligro al acecho!

Problema: algunos desarrolladores llenan demasiada funcionalidad en una clase / método, siendo demasiado perezosos para romper la funcionalidad & # 8211; esto conduce a una baja cohesión y quizás a un alto acoplamiento & # 8211; ¡Lo inverso de un principio muy importante en OOP! Solución: Evite usar demasiadas clases internas / anidadas & # 8211; estas clases se deben usar SOLAMENTE según la necesidad, ¡no tiene que hacer un hábito al usarlas! Su uso podría llevar a más problemas como limitar la herencia. ¡Busca código duplicado! El mismo código o similar podría ya existir en alguna implementación de supertipo o tal vez en otra clase. Si & # 8217; s en otra clase que no es un supertipo también violaste la regla de cohesión. Cuidado con los métodos estáticos & # 8211; ¡Quizás necesites una clase de utilidad para agregar!
Mas en: http://centraladvisor.com/it/oop-what -son las mejores prácticas-en-oop

Probablemente no quieras usar la herencia para esto. Intenta esto en su lugar:

class Person {
    public int PersonId { get; set; }
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string Address { get; set; }
}

class Customer{
    public Person PersonInfo;
    public int CustomerId { get; set; }
    public DateTime JoinedDate { get; set; }
}

class Staff {
    public Person PersonInfo;
    public int StaffId { get; set; }
    public string JobTitle { get; set; }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top