oop - purposes - how to use hashtags on instagram
¿Es realmente necesaria la herencia? (22)
¿Alguien más recuerda a todos los OO-puristas que se vuelven balísticos por la implementación COM de "contención" en lugar de "herencia"? Logró esencialmente lo mismo, pero con un tipo diferente de implementación. Esto me recuerda tu pregunta.
Trato estrictamente de evitar guerras religiosas en el desarrollo de software. ("vi" O "emacs" ... ¡cuando todo el mundo sabe su "vi"!) Creo que son un signo de mentes pequeñas. Los profesores de Comp Sci pueden darse el lujo de sentarse y debatir estas cosas. Estoy trabajando en el mundo real y no me importa. Todas estas cosas son simplemente intentos de dar soluciones útiles a problemas reales. Si funcionan, la gente los usará. El hecho de que los lenguajes y herramientas de OO hayan estado comercialmente disponibles en gran escala durante los últimos 20 años es una apuesta bastante buena de que son útiles para mucha gente.
Debo confesar que soy algo así como un escéptico OOP. Las malas experiencias pedagógicas y laborales con orientación a objetos no ayudaron. Así que me convertí en un creyente ferviente en Visual Basic (¡el clásico!).
Entonces, un día descubrí que C ++ había cambiado y ahora tenía el STL y las plantillas. ¡Realmente me gustó eso! Hizo que el lenguaje sea útil. Luego, otro día, MS decidió aplicar cirugía facial a VB, y realmente odiaba el resultado final de los cambios gratuitos (¿usar "end while" en lugar de "wend" me convertiría en un mejor desarrollador? ¿Por qué no soltar "next" para " final para "¿también? ¿Por qué forzar el getter junto con el setter? Etc.) más muchas características de Java que encontré inútiles (herencia, por ejemplo, y el concepto de un marco jerárquico).
Y ahora, varios años después, me encuentro haciendo esta pregunta filosófica: ¿es realmente necesaria la herencia?
La pandilla de los cuatro dice que debemos favorecer la composición del objeto sobre la herencia. Y después de pensarlo, no puedo encontrar algo que pueda hacer con la herencia que no pueda hacer con las interfaces de agregado de objetos más. Entonces me pregunto, ¿por qué lo tenemos en primer lugar?
¿Algunas ideas? Me encantaría ver un ejemplo de donde la herencia sería definitivamente necesaria, o donde el uso de la herencia en lugar de la composición + interfaces puede conducir a un diseño más simple y fácil de modificar. En trabajos anteriores que encontré si necesita cambiar la clase base, necesita modificar también casi todas las clases derivadas, ya que dependían del comportamiento de los padres. Y si haces que los métodos de la clase base sean virtuales ... entonces no se comparte mucho código :(
De lo contrario, cuando finalmente haya creado mi propio lenguaje de programación (un deseo que no se ha cumplido y que la mayoría de los desarrolladores han compartido), no tendría sentido agregarle herencia ...
// Encontré esta QA muy útil. Muchos han respondido este derecho. Pero quería agregar ...
1: Capacidad para definir interfaz abstracta - Por ejemplo, para desarrolladores de complementos. Por supuesto, puede usar indicadores de función, pero esto es mejor y más simple.
2: la herencia ayuda a los tipos de modelos muy cerca de sus relaciones reales. A veces, muchos errores se detectan en tiempo de compilación, porque tiene la jerarquía de tipos correcta. Por ejemplo, shape <-- triangle
(digamos que hay mucho código para reutilizar). Es posible que desee componer un triángulo con un objeto de shape
, pero la shape
es un tipo incompleto. Insertar implementaciones ficticias como double getArea() {return -1;}
hará, pero está abriendo espacio para el error. ¡Ese return -1
puede ser ejecutado algún día!
3: void func(B* b); ... func(new D());
void func(B* b); ... func(new D());
La conversión de tipo implícita brinda una gran comodidad de notación ya que Derived
is Base
. Recuerdo haber leído a Straustrup diciendo que quería convertir a las clases en ciudadanos de primera clase al igual que los tipos de datos fundamentales (por lo tanto, sobrecargaban a los operadores, etc.). La conversión implícita de derivada a base, se comporta como una conversión implícita de un tipo de datos a uno más amplio compatible (corto a int).
A continuación, la herencia se usa para presentar una propiedad particular para todas las diversas encarnaciones específicas del mismo tipo de cosa. En este caso, GeneralPresenation tiene propiedades que son relevantes para toda "presentación" (los datos pasan a una vista MVC). La página maestra es lo único que la usa y espera una presentación general, aunque los puntos de vista específicos esperan más información, adaptada a sus necesidades.
public abstract class GeneralPresentation
{
public GeneralPresentation()
{
MenuPages = new List<Page>();
}
public IEnumerable<Page> MenuPages { get; set; }
public string Title { get; set; }
}
public class IndexPresentation : GeneralPresentation
{
public IndexPresentation() { IndexPage = new Page(); }
public Page IndexPage { get; set; }
}
public class InsertPresentation : GeneralPresentation
{
public InsertPresentation() {
InsertPage = new Page();
ValidationInfo = new PageValidationInfo();
}
public PageValidationInfo ValidationInfo { get; set; }
public Page InsertPage { get; set; }
}
Absolutamente necesario? no, pero piensa en lámparas. Puedes crear una nueva lámpara desde cero cada vez que la realices, o puedes tomar propiedades de la lámpara original y crear todo tipo de nuevos estilos de lámpara que tengan las mismas propiedades que el original, cada uno con su propio estilo.
O puede hacer una nueva lámpara desde cero o decirle a la gente que la mire de cierta manera para ver la luz, o, o, o
No es obligatorio, pero es agradable :)
Creo que preguntar por situaciones en las que la herencia realmente se necesita es perder un poco el sentido. Puedes simular la herencia usando una interfaz y algo de composición. Esto no significa que la herencia sea inútil. Puedes hacer cualquier cosa que hagas en VB6 en código ensamblador con algo de tipeo extra, eso no significa que VB6 fue inútil.
Por lo general, simplemente empiezo a usar una interfaz. A veces me doy cuenta de que realmente quiero heredar el comportamiento. Eso generalmente significa que necesito una clase base. Es así de simple.
Depende de tu definición de "necesario". No, no hay nada que sea imposible de hacer sin herencia, aunque la alternativa puede requerir un código más detallado o una reescritura importante de su aplicación.
Pero definitivamente hay casos en que la herencia es útil. Como dices, las interfaces de composición plus juntas cubren casi todos los casos, pero ¿qué ocurre si deseo proporcionar un comportamiento predeterminado? Una interfaz no puede hacer eso. Una clase base puede. A veces, lo que desea hacer es simplemente anular los métodos individuales. No reimplementar la clase desde cero (como con una interfaz), sino simplemente cambiar un aspecto de la misma. o puede que no desee que todos los miembros de la clase sean invalidables. Quizás solo tiene uno o dos métodos de miembro que desea que el usuario anule, y el resto, que los llama (y realiza validación y otras tareas importantes antes y después de los métodos reemplazados por el usuario) se especifican de una vez por todas en la clase base y no puede ser anulado
La herencia a menudo es utilizada como una muleta por personas que están demasiado obsesionadas con la definición limitada de (y obsesión con) OOP de Java, y en la mayoría de los casos estoy de acuerdo, es la solución incorrecta, como si cuanto más profunda es la jerarquía de clase, mejor es tu software .
El GoF (y muchos otros) recomiendan que solo favorezca la composición por sobre la herencia. Si tiene una clase con una API muy grande y solo desea agregarle una cantidad muy pequeña de métodos, dejando solo la implementación base, me parece inapropiado usar la composición. Tendría que volver a implementar todos los métodos públicos de la clase encapsulada para simplemente devolver su valor. Esto es una pérdida de tiempo (programador y CPU) cuando puede heredar todo este comportamiento y dedicar su tiempo a concentrarse en nuevos métodos.
Entonces, para responder a su pregunta, no, no es absolutamente necesario heredar. Sin embargo, hay muchas situaciones en las que es la opción de diseño correcta.
El mayor problema con las interfaces es que no se pueden cambiar. Haga que una interfaz sea pública, luego cámbiela (agregue un nuevo método) y rompa millones de aplicaciones en todo el mundo, porque han implementado su interfaz, pero no el nuevo método. La aplicación ni siquiera puede iniciarse, una VM puede negarse a cargarla.
Use una clase base (no abstracta) que otros programadores puedan heredar de (y anular los métodos según sea necesario); luego agrega un método. Cada aplicación que use su clase seguirá funcionando, este método no será reemplazado por nadie, pero dado que usted proporciona una implementación básica, esta será utilizada y puede funcionar perfectamente para todas las subclases de su clase ... puede también causan un comportamiento extraño porque a veces ha sido necesario anularlo, está bien, podría ser el caso, pero al menos todas esas millones de aplicaciones en el mundo seguirán funcionando.
Prefiero tener mi aplicación Java ejecutándose después de actualizar el JDK de 1.6 a 1.7 con algunos errores menores (que pueden corregirse en el tiempo) que no tenerlo ejecutándose (forzando una solución inmediata o será inútil para las personas).
El problema con la herencia es que combina el problema del subtipado (afirmación de una relación is-a) y la reutilización del código (por ejemplo, la herencia privada es solo para reutilización).
Entonces, no, es una palabra sobrecargada que no necesitamos. Prefiero subtipear (usando la palabra clave ''implements'') e importar (algo así como Ruby lo hace en las definiciones de clase)
Estoy de acuerdo con todos los demás sobre la distinción necesaria / útil.
La razón por la que me gusta OOP es porque me permite escribir código que es más limpio y más organizado lógicamente. Uno de los mayores beneficios proviene de la capacidad de "factorizar" la lógica que es común para varias clases. Podría darte ejemplos concretos en los que OOP haya reducido seriamente la complejidad de mi código, pero eso sería aburrido para ti.
Baste decir, yo corazón OOP.
Gracias a todos por sus respuestas. Mantengo mi posición de que, estrictamente hablando, la herencia no es necesaria, aunque creo que encontré una nueva apreciación de esta característica.
Algo más: en mi experiencia laboral, he encontrado que la herencia conduce a diseños más simples y claros cuando llega tarde en el proyecto, después de que se nota que muchas clases tienen muchas características comunes y se crea una clase base. En los proyectos en los que se creó un gran esquema desde el principio, con muchas clases en una jerarquía de herencia, la refactorización suele ser dolorosa y difícil.
Al ver algunas respuestas que mencionan algo similar, me pregunto si esta podría no ser exactamente la forma en que se supone que se usará la herencia: ex post facto. Me recuerda la cita de Stepanov: "no comienzas con axiomas, terminas con axiomas después de tener un montón de pruebas relacionadas". Él es un matemático, por lo que debería saber algo.
Hay muchas funciones en un lenguaje de programación que no son realmente necesarias . Pero están ahí por una variedad de razones, que básicamente se reducen a reutilización y mantenibilidad.
Todo lo que le importa a una empresa es producir (calidad por supuesto) de manera económica y rápida .
Como desarrollador que ayuda a hacer esto es haciéndose más eficiente y productivo. Por lo tanto, debe asegurarse de que el código que escribe sea fácilmente reutilizable y mantenible .
Y, entre otras cosas, esto es lo que le da la herencia: la capacidad de reutilización sin reinventar la rueda, así como la capacidad de mantener fácilmente su objeto base sin tener que realizar mantenimiento en todos los objetos similares.
Hay muchos usos útiles de la herencia, y probablemente tantos como menos útiles. Una de las útiles es la clase de flujo.
Usted tiene un método que debería poder transmitir datos. Al utilizar la clase de base de flujo como entrada para el método, se asegura de que su método se pueda usar para escribir en muchos tipos de flujo sin cambios. Al sistema de archivos, a través de la red, con compresión, etc.
La herencia define una relación "Es-A".
class Point( object ):
# some set of features: attributes, methods, etc.
class PointWithMass( Point ):
# An additional feature: mass.
Arriba, he usado la herencia para declarar formalmente que PointWithMass es un Point.
Hay varias formas de manejar el objeto P1
siendo PointWithMass así como Point. Aquí hay dos.
Tener una referencia del objeto
PointWithMass
p1
a algún objeto Pointp1-friend
. Elp1-friend
tiene los atributos dePoint
. Cuandop1
necesita involucrarse en un comportamiento tipoPoint
, necesita delegar el trabajo a su amigo.Confíe en la herencia del lenguaje para asegurar que todas las características de
Point
también sean aplicables a mi objetoPointWithMass
,p1
. Cuandop1
necesita involucrarse en el comportamiento similar a unPoint
, ya es un objetoPoint
y puede hacer lo que debe hacerse.
Prefiero no administrar los objetos adicionales que flotan para asegurarme de que todas las características de la superclase forman parte de un objeto de subclase. Prefiero tener herencia para asegurarme de que cada subclase sea una instancia de su propia clase, además es una instancia de todas las superclases también.
Editar.
Para los idiomas con tipado estático, hay una bonificación. Cuando confío en el lenguaje para manejar esto, un PointWithMass
se puede usar en cualquier Point
se esperara un Point
.
Para el abuso de la herencia realmente oscuro, lee sobre el extraño atolladero de "composición a través de la herencia privada" de C ++. Consulte ¿ Algún ejemplo sensato de creación de herencia sin crear relaciones de subtipado? para un mayor debate sobre esto. Confunde herencia y composición; no parece agregar claridad o precisión al código resultante; solo se aplica a C ++.
La herencia es algo bueno cuando la subclase es realmente el mismo tipo de objeto que la superclase. Por ejemplo, si está implementando el patrón de registro activo, está intentando asignar una clase a una tabla en la base de datos, y las instancias de la clase a una fila en la base de datos. En consecuencia, es muy probable que sus clases Active Record compartan una interfaz común e implementación de métodos como: cuál es la clave principal, si la instancia actual es persistente, guardando la instancia actual, validando la instancia actual, ejecutando callbacks luego de la validación y / o guardar, eliminar la instancia actual, ejecutar una consulta SQL, devolver el nombre de la tabla a la que se asigna la clase, etc.
También se desprende de cómo formula su pregunta que está asumiendo que la herencia es única pero no múltiple. Si necesitamos herencia múltiple, entonces tenemos que usar interfaces más composición para realizar el trabajo. Para poner un punto al respecto, Java asume que la herencia de implementación es singular y la herencia de la interfaz puede ser múltiple. Uno no necesita ir por esta ruta. Por ejemplo, C ++ y Ruby permiten herencia múltiple para su implementación y su interfaz. Dicho esto, uno debe usar herencia múltiple con precaución (es decir, mantener sus clases abstractas virtuales y / o sin estado).
Dicho esto, como usted nota, hay demasiadas jerarquías de clases de la vida real donde las subclases heredan de la superclase por conveniencia en lugar de tener una verdadera relación is-a. Por lo tanto, no es sorprendente que un cambio en la superclase tenga efectos secundarios en las subclases.
La herencia es una decisión de implementación. Las interfaces casi siempre representan un mejor diseño, y generalmente deberían usarse en una API externa.
¿Por qué escribir muchas llamadas de método de reenvío de código repetitivo a un objeto miembro compuesto cuando el compilador lo hará por usted con la herencia?
Esta respuesta a otra pregunta resume mi pensamiento bastante bien.
La herencia me permite llevar un montón de contabilidad al compilador porque me da un comportamiento polimórfico para las jerarquías de objetos que de otra forma tendría que crear y mantener. Independientemente de lo bueno que sea un OOP de bala de plata, siempre habrá casos en los que desee emplear un cierto tipo de comportamiento porque tiene sentido hacerlo. Y, en última instancia, ese es el punto de OOP: hace que una cierta clase de problemas sea mucho más fácil de resolver.
Las desventajas de la composición es que puede disfrazar la relación de los elementos y puede ser más difícil de entender para los demás. Con, digamos, una clase 2D Point y el deseo de extenderla a dimensiones más altas, presumiblemente tendrá que agregar (al menos) Z getter / setter, modificar getDistance () y quizás agregar un método getVolume (). Entonces tienes los elementos de Objects 101: estado y comportamiento relacionados.
Un desarrollador con una mentalidad compositiva presumiblemente habría definido un método getDistance (x, y) -> double y ahora definiría un método getDistance (x, y, z) -> double. O, pensando en general, podrían definir un método getDistance (lambdaGeneratingACoordinateForEveryAxis ()) -> double. Luego, probablemente escribirían los métodos de fábrica createTwoDimensionalPoint () y createThreeDimensionalPoint () (o quizás crearNDimensionalPoint (n)) que unirían los diversos estados y comportamientos.
Un desarrollador con una mentalidad de OO usaría la herencia. La misma cantidad de complejidad en la implementación de las características del dominio, menos complejidad en términos de inicialización del objeto (el constructor se encarga de él frente a un método de fábrica), pero no tan flexible en términos de lo que se puede inicializar.
Ahora piensa en ello desde un punto de vista de comprensibilidad / legibilidad. Para comprender la composición, uno tiene una gran cantidad de funciones que se componen programáticamente dentro de otra función. Así que hay poco en términos de ''estructura'' de código estático (archivos y palabras clave, etc.) que hace saltar la relación de Z y la distancia (). En el mundo de OO, tienes una gran luz roja destellante que te dice la jerarquía. Además, tiene un vocabulario esencialmente universal para debatir sobre la estructura, las notaciones gráficas ampliamente conocidas, una jerarquía natural (al menos para la herencia individual), etc.
Ahora, por otro lado, un método de Fábrica bien nombrado y construido a menudo explicita más las relaciones a veces oscuras entre estado y comportamiento, ya que una mentalidad compositiva facilita el código funcional (es decir, el código que pasa el estado a través de parámetros, no a través de esto ).
En un entorno profesional con desarrolladores experimentados, la flexibilidad de la composición generalmente supera su naturaleza más abstracta. Sin embargo, nunca se debe descartar la importancia de la comprensión, especialmente en equipos que tienen diversos grados de experiencia y / o altos niveles de rotación.
No es necesario, pero es útil.
Cada idioma tiene sus propios métodos para escribir menos código. OOP a veces se complica, pero creo que es la responsabilidad de los desarrolladores, la plataforma OOP es útil y nítida cuando se usa bien.
No.
para mí, OOP trata principalmente de la encapsulación del estado y el comportamiento y el polimorfismo.
y eso es. pero si desea comprobar el tipo estático, necesitará agrupar diferentes tipos para que el compilador pueda verificar y, al mismo tiempo, permitir el uso de tipos nuevos en lugar de otro tipo relacionado. crear una jerarquía de tipos le permite usar el mismo concepto (clases) para tipos y para grupos de tipos, por lo que es la forma más ampliamente utilizada.
pero hay otras formas, creo que la más general sería el tipado de patos y POO basado en prototipos estrechamente relacionado (que no es herencia en realidad, pero generalmente se llama herencia basada en prototipos).
Respuesta realmente muy corta: No. La herencia no es necesaria porque solo se necesita el código de bytes. Pero obviamente, el código de bytes o ensamblar no es una forma práctica de escribir su programa. OOP no es el único paradigma para la programación. Pero yo divago.
Fui a la universidad para ciencias de la computación a principios de la década de 2000 cuando la herencia (es una), las composiciones (tiene una) y las interfaces (a) se enseñaban en igualdad de condiciones. Debido a esto, uso muy poca herencia porque a menudo se adapta mejor por composición. Esto se destacó porque muchos de los profesores habían visto código incorrecto (junto con lo que usted ha descrito) debido al abuso de la herencia.
Independientemente de la creación de un idioma con o sin herencia, ¿puede crear un lenguaje de programación que evite los malos hábitos y las malas decisiones de diseño?
La herencia y la composición tienen sus propios pros y contras.
Consulte esta pregunta relacionada SE sobre pros de la herencia y contras de la composición.
¿Prefiere la composición sobre la herencia?
Echa un vistazo al ejemplo en este enlace de documentación :
El ejemplo muestra diferentes casos de uso de anulación utilizando la herencia como medio para lograr el polimorfismo.