Pregunta

En un proyecto, nuestro equipo está utilizando listas de objetos para realizar operaciones masivas en conjuntos de datos que deberían procesarse de manera similar.En particular, lo ideal sería que diferentes objetos actuaran de la misma manera, lo que se lograría muy fácilmente con polimorfismo.El problema que tengo con esto es que la herencia implica la es un relación, en lugar de la tiene un relación.Por ejemplo, varios objetos tener un contador de daños, pero para que esto sea fácil de usar en una lista de objetos, se podría usar polimorfismo, excepto que eso implicaría un es un relación que no sería cierta.(Una persona no es un contador de daños.)

La única solución que se me ocurre es hacer que un miembro de la clase devuelva el tipo de objeto adecuado cuando se emite implícitamente en lugar de depender de la herencia.¿Sería mejor renunciar a es un / tiene un ¿Ideal a cambio de facilidad de programación?

Editar:Para ser más específico, estoy usando C++, por lo que usar polimorfismo permitiría que los diferentes objetos "actúen igual" en el sentido de que las clases derivadas podrían residir dentro de una única lista y ser operadas por una función virtual de la clase base.El uso de una interfaz (o imitarla mediante herencia) parece una solución que estaría dispuesto a utilizar.

¿Fue útil?

Solución

Esto se puede lograr mediante herencia múltiple.En su caso específico (C++), puede utilizar clases virtuales puras como interfaces.Esto le permite tener herencia múltiple sin crear problemas de alcance/ambigüedad.Ejemplo:

class Damage {
    virtual void addDamage(int d) = 0;
    virtual int getDamage() = 0;
};

class Person : public virtual Damage {
    void addDamage(int d) {
        // ...
        damage += d * 2;
    }

    int getDamage() {
        return damage;
    }
};

class Car : public virtual Damage {
    void addDamage(int d) {
        // ...
        damage += d;
    }

    int getDamage() {
        return damage;
    }
};

Ahora tanto la Persona como el Auto 'es-un' Daño, es decir, implementan la interfaz de Daño.El uso de clases virtuales puras (para que sean como interfaces) es clave y debe usarse con frecuencia.Aísla los cambios futuros de alterar todo el sistema.Lea sobre el principio abierto-cerrado para obtener más información.

Otros consejos

Creo que deberías implementar interfaces para poder hacer cumplir tu tiene un relaciones (estoy haciendo esto en C#):

public interface IDamageable
{
    void AddDamage(int i);
    int DamageCount {get;}
}

Podrías implementar esto en tus objetos:

public class Person : IDamageable

public class House : IDamageable

Y estaría seguro de que la propiedad DamageCount tiene un método que le permite agregar daños, sin implicar que una persona y una casa estén relacionadas entre sí en algún tipo de jerarquía.

Estoy de acuerdo con Jon, pero suponiendo que todavía necesites una clase de contador de daños separada, puedes hacer:

class IDamageable {
  virtual DamageCounter* damage_counter() = 0;
};
class DamageCounter {
  ...
};

Luego, cada clase que se puede dañar debe proporcionar su propia función miembro harm_counter().La desventaja de esto es que crea una tabla virtual para cada clase que se puede dañar.En su lugar puedes utilizar:

class Damageable {
 public:
  DamageCounter damage_counter() { return damage_counter_; }
 private:
  DamageCounter damage_counter_;
};

Pero muchas personas son No es genial con herencia múltiple cuando varios padres tienen variables miembro.

A veces vale la pena renunciar a lo ideal por lo realista.Si "hacerlo bien" sin ningún beneficio real va a causar un problema enorme, entonces lo haría mal.Dicho esto, a menudo pienso que vale la pena tomarse el tiempo para hacerlo bien, porque la herencia múltiple innecesaria aumenta la complejidad y poder contribuyen a que el sistema sea menos mantenible.Realmente tienes que decidir qué es lo mejor para tus circunstancias.

Una opción sería hacer que estos objetos implementen un Damageable interfaz, en lugar de heredar de DamageCounter.De esta manera, una persona tiene un contador de daño, pero es dañable.(A menudo encuentro que las interfaces tienen mucho más sentido como adjetivo que como sustantivo). Entonces podrías tener una interfaz de daño consistente en Damageable objetos, y no exponer que un contador de daños es la implementación subyacente (a menos que sea necesario).

Si desea seguir la ruta de la plantilla (asumiendo C++ o similar), puede hacerlo con mixins, pero eso puede volverse feo muy rápidamente si se hace mal.

Esta pregunta es realmente confusa :/

Su pregunta en negrita es muy abierta y tiene una respuesta de "depende", pero su ejemplo realmente no brinda mucha información sobre el contexto desde el cual pregunta.Estas líneas me confunden;

conjuntos de datos que deberían procesarse todos de manera similar

¿Que camino?¿Los conjuntos son procesados ​​por una función?¿Otra clase?¿A través de una función virtual sobre los datos?

En particular, lo ideal sería que diferentes objetos actuaran igual, lo que se lograría muy fácilmente con polimorfismo.

El ideal de "actuar igual" y el polimorfismo no tienen ninguna relación.¿Cómo el polimorfismo hace que sea fácil de lograr?

@Kevin

Normalmente, cuando hablamos de "es un" frente a "tiene un", estamos hablando de herencia frente a composición.

Um... el contador de daño sería simplemente un atributo de una de sus clases derivadas y realmente no se discutiría en términos de "Una persona es un contador de daño" con respecto a su pregunta.

Tener el contador de daño como atributo no le permite diversificar objetos con contadores de daño en una colección.Por ejemplo, una persona y un coche pueden tener contadores de daños, pero no puedes tener uno. vector<Person|Car> o un vector<with::getDamage()> o algo similar en la mayoría de los idiomas.Si tiene una clase base de Objeto común, entonces puede insertarlas de esa manera, pero luego no podrá acceder a la getDamage() método de forma genérica.

Esa era la esencia de su pregunta, tal como la leí."¿Debo violar is-a y has-a ¿Por el simple hecho de tratar ciertos objetos como si fueran iguales, aunque no lo sean?

Normalmente, cuando hablamos de "es un" frente a "tiene un", estamos hablando de herencia frente a composición.

Um... el contador de daño sería simplemente un atributo de una de sus clases derivadas y realmente no se discutiría en términos de "Una persona es un contador de daño" con respecto a su pregunta.

Mira esto:

http://www.artima.com/designtechniques/compoinh.html

Lo que podría ayudarte en el camino.

@Derek:Por la redacción, supuse que allí era una clase base, después de haber releído la pregunta, ahora veo a qué se refiere.

"Hacerlo bien" tendrá beneficios a largo plazo, aunque sólo sea porque a alguien que mantenga el sistema más adelante le resultará más fácil comprender si se hizo bien desde el principio.

Dependiendo del idioma, es posible que tenga la opción de herencia múltiple, pero normalmente las interfaces simples tienen más sentido.Por "simple" me refiero a crear una interfaz que no intente ser demasiado.Es mejor tener muchas interfaces simples y algunas monolíticas.Por supuesto, siempre hay una compensación, y demasiadas interfaces probablemente llevarían a que algunas sean "olvidadas"...

@Andrés

El ideal de "actuar igual" y el polimorfismo no tienen ninguna relación.¿Cómo el polimorfismo hace que sea fácil de lograr?

Todos ellos tienen, por ejemplo, una función en común.llamémoslo addDamage().Si quieres hacer algo como esto:

foreach (obj in mylist)
    obj.addDamage(1)

Entonces necesita un lenguaje dinámico o necesita que se extiendan desde una clase (o interfaz) principal común.p.ej.:

class Person : DamageCounter {}
class Car : DamageCounter {}

foreach (DamageCounter d in mylist)
    d.addDamage(1)

Entonces puedes tratar Person y Car lo mismo en ciertas circunstancias muy útiles.

Polimorfismo no requiere herencia.El polimorfismo es lo que se obtiene cuando varios objetos implementan la misma firma de mensaje (método).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top