multiple - operator c++
Herencia o composición: ¿Confía en "is-a" y "has-a"? (9)
Cuando diseño clases y tengo que elegir entre herencia y composición, normalmente uso la regla de oro: si la relación es "es-a", uso la herencia, y si la relación es "tiene-a", use la composición.
¿Siempre es correcto?
Gracias.
Si la relación es "is-a", use la herencia, y si la relación es "has-a", use la composición. ¿Siempre es correcto?
En cierto sentido, sí. Pero debe tener cuidado de no introducir relaciones innecesarias, artificiales "es-a".
Por ejemplo, uno puede pensar que un ThickBorderedRectangle es un Rectangle, que parece razonable a primera vista, y decide usar la herencia. Pero esta situación se describe mejor diciendo que un rectángulo tiene un borde, que puede o no ser un ThickBorder. En este caso, preferiría la composición.
De la misma manera, uno puede pensar que un ThickBorder es un Borde especial y usa herencia; pero es mejor decir que un borde tiene un ancho, por lo tanto, prefiere la composición.
En todos estos casos ambiguos, mis reglas generales son pensar dos veces y, como otros aconsejaron, prefieren la composición sobre la herencia .
Creo que John tiene la Idea correcta. Consideras las relaciones de tu clase como la creación de un modelo, que es lo que intentan hacer que hagas cuando comienzan a enseñarte los principios de programación orientada a objetos. Realmente, es mejor que mires cómo funcionan las cosas en términos de la arquitectura de los códigos. Es decir, cuál es la mejor manera para que otros programadores (o usted mismo) reutilicen su código sin romperlo o tengan que mirar la implementación para ver qué hace.
Descubrí que la herencia a menudo rompe el ideal de la "caja negra", por lo que si ocultar la información es importante, puede que no sea la mejor manera de hacerlo. He descubierto cada vez más que la herencia es mejor para proporcionar una interfaz común que para la reutilización de componentes en otros lugares.
Por supuesto, cada situación es única y no hay una forma correcta de tomar esta decisión, solo reglas generales.
La gente a menudo dice que la herencia es una relación "es-a", pero que puede meterlo en problemas. La herencia en C ++ se divide en dos ideas: reutilización del código y definición de interfaces.
El primero dice "Mi clase es como esta otra clase. Escribiré código para el delta entre ellos y reutilizaré la otra implementación de la otra clase tanto como pueda".
El segundo depende de clases base abstractas y es una lista de métodos que la clase promete implementar.
El primero puede ser muy útil, pero también puede causar problemas de mantenimiento si no se hace bien, por lo que algunas personas llegarán a decir que nunca debes hacerlo. La mayoría de las personas enfatizan el segundo, usan la herencia para lo que otros lenguajes llaman explícitamente una interfaz.
La herencia no siempre significa que hay una relación "es-a" y la falta de uno no siempre significa que no existe.
Ejemplos:
- Si usa una herencia protegida o privada, pierde la sustituibilidad externa, es decir, en lo que respecta a cualquier persona fuera de su jerarquía, un derivado no es un tipo de base.
- Si anula un miembro virtual básico y cambia el comportamiento observable de alguien que hace referencia a la Base, desde la implementación Base original, entonces habrá roto la sustituibilidad. Aunque tiene una jerarquía de herencia pública derivada no es una base.
- Su clase a veces puede ser sustituible por otra sin ninguna relación de herencia en el trabajo. Por ejemplo, si tuviera que usar plantillas e interfaces idénticas para las funciones apropiadas de miembros de la función, entonces una "Elipse const &" donde la elipse resulta ser perfectamente circular podría ser sustituible por una "Const círculo".
Lo que estoy tratando de decir aquí es que se necesita mucho pensamiento y trabajo para mantener una relación sustituible "is-a" entre dos tipos, ya sea que use herencia o no.
Mi regla de oro (aunque no es concreta) es que si puedo usar el mismo código para diferentes clases, coloco ese código en una clase principal y uso la herencia. De lo contrario, uso la composición.
Esto me lleva a escribir menos código que es inherentemente más fácil de mantener.
No - "is a" no siempre conduce a la herencia. Un ejemplo bien citado es la relación entre un cuadrado y un rectángulo. Un cuadrado es un rectángulo, pero sería malo diseñar un código que herede una clase Cuadrada de una clase Rectángulo.
Mi sugerencia es mejorar su heurística "es una / tiene una" con el Principio de sustitución Liskov . Para verificar si una relación de herencia cumple con el Principio de sustitución de Liskov, pregunte si los clientes de una clase base pueden operar en la subclase sin saber que está operando en una subclase. Por supuesto, todas las propiedades de la subclase deben conservarse.
En el ejemplo de cuadrado / rectángulo, debemos preguntar si un cliente de rectángulo puede operar en un cuadrado sin saber que es un cuadrado. Todo lo que el cliente debe saber es que está operando en un rectángulo. La siguiente función muestra un cliente que asume que establecer el ancho de un rectángulo no modifica la altura.
void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetWidth() * r.GetHeight()) == 20);
}
Esta suposición es verdadera para un rectángulo, pero no para un cuadrado. Por lo tanto, la función no puede operar en un cuadrado y, por lo tanto, la relación de herencia infringe el principio de sustitución de Liskov. (Por cierto, el ejemplo vino del artículo vinculado )
No creo que sea tan simple como "is-a" versus "has-a", como dijo Cletus, la línea puede ponerse muy borrosa. No es como si dos personas siempre llegasen a la misma conclusión, solo como una pauta.
También, como dijo Cletus, prefiere la composición sobre la herencia. En mi opinión, tiene que haber realmente buenas razones para derivar de una clase base, y en particular, la clase base realmente debe haber sido diseñada para la herencia, y de hecho para el tipo de especialización que desea aplicar.
Más allá de eso, y esta será probablemente una idea impopular, todo se reduce al gusto y la tripa. Con el tiempo, creo que los buenos desarrolladores tienen una mejor idea de cuándo funcionará la herencia y cuándo no, pero es posible que les resulte difícil expresarla con claridad. Sé que me resulta difícil, como lo demuestra esta respuesta. Tal vez solo estoy proyectando la dificultad en otras personas :)
Si y no.
La línea puede ser borrosa. Esto no ha sido ayudado por algunos ejemplos bastante terribles de programación OO desde los primeros días de OO como: el gerente es un empleado es una persona.
Lo que debe recordar acerca de la herencia es: la herencia rompe la encapsulación. La herencia es un detalle de implementación. Hay todo tipo de escritos sobre este tema.
La forma más sutil de resumirlo es:
Prefiere la composición.
Eso no significa usarlo para la exclusión completa de la herencia. Simplemente significa que la herencia es una posición alternativa.
No Sí. Si la relación es "has-a", usa la composición. ( Agregado : la versión original de la pregunta decía "use inheritance" para ambos: un simple error tipográfico, pero la corrección altera la primera palabra de mi respuesta de "No" a "Sí").
Y usa la composición por defecto; solo use herencia cuando sea necesario (pero no dude en usarla).