java - programming - ¿Es posible OOP y está completamente evitando la herencia de implementación?
oracle java concepts (8)
Es gracioso responder mi propia pregunta, pero esto es algo que encontré que es bastante interesante: Sather .
¡Es un lenguaje de programación sin herencia de implementación en absoluto! Conoce interfaces (llamadas clases abstractas sin implementación o datos encapsulados), y las interfaces pueden heredarse entre sí (de hecho, incluso admiten herencia múltiple), pero una clase solo puede implementar interfaces (clases abstractas, tantas como quiera), no puede heredar de otra clase. Sin embargo, puede "incluir" otra clase. Esto es más bien un concepto de delegado. Las clases incluidas se deben instanciar en el constructor de su clase y se destruyen cuando se destruye su clase. A menos que sobrescriba los métodos que tienen, su clase hereda también su interfaz, pero no su código. En su lugar, se crean métodos que simplemente reenvían llamadas a su método al método igualmente nombrado del objeto incluido. La diferencia entre los objetos incluidos y los objetos simplemente encapsulados es que no tiene que crear la delegación hacia delante usted mismo y no existen como objetos independientes que puede pasar, son parte de su objeto y viven y mueren junto con su objeto (o más hablado técnicamente: la memoria para su objeto y todos los incluidos se crea con una sola llamada alloc, el mismo bloque de memoria, solo necesita iniciarlos en su llamada de constructor, mientras que cuando usa delegados reales, cada uno de estos objetos causa una llamada de alloc propia, tiene un bloque de memoria propio y vive completamente independientemente de su objeto).
El idioma no es tan hermoso, pero me encanta la idea que hay detrás :-)
Elegiré Java como ejemplo, la mayoría de la gente lo sabe, aunque todos los demás lenguajes OO también funcionaban.
Java, como muchos otros lenguajes, tiene herencia de interfaz y herencia de implementación. Por ejemplo, una clase Java puede heredar de otra y cada método que tiene una implementación allí (suponiendo que el padre no es abstracto) también se hereda. Eso significa que la interfaz se hereda y la implementación de este método también. Puedo sobrescribirlo, pero no es necesario. Si no lo sobrescribo, heredé la implementación.
Sin embargo, mi clase también puede "heredar" (no en términos de Java) solo una interfaz, sin implementación. En realidad, las interfaces realmente se denominan de esa manera en Java, proporcionan herencia de interfaz, pero sin heredar ninguna implementación, ya que todos los métodos de una interfaz no tienen implementación.
Ahora estaba este artículo, diciendo que es mejor heredar interfaces que implementaciones , que te gustaría leerlo (al menos en la primera mitad de la primera página), es bastante interesante. Evita problemas como el problema de la clase base frágil . Hasta ahora, esto tiene mucho sentido y muchas otras cosas que se dicen en el artículo tienen mucho sentido para mí.
Lo que me molesta de esto, es que la herencia de implementación significa la reutilización de código , una de las propiedades más importantes de los lenguajes OO. Ahora bien, si Java no tiene clases (como James Gosling, el padrino de Java lo ha deseado de acuerdo con este artículo), resuelve todos los problemas de herencia de implementación, pero ¿cómo haría posible la reutilización de código?
Por ejemplo, si tengo una clase, Car and Car tiene un movimiento de método (), que hace que el automóvil se mueva. Ahora puedo subclasificar coches para diferentes tipos de autos, que son todos autos, pero son todas versiones especializadas de autos. Algunos pueden moverse de una manera diferente, estos deben sobrescribir el movimiento () de todos modos, pero la mayoría simplemente mantendría el movimiento heredado, ya que se mueven igual que el automóvil padre abstracto. Ahora suponga por un segundo que solo hay interfaces en Java, solo las interfaces pueden heredarse entre sí, una clase puede implementar interfaces, pero todas las clases son siempre finales, por lo que ninguna clase puede heredar de ninguna otra clase.
¿Cómo evitarías eso cuando tienes un Interface Car y cientos de clases de Car, que necesitas implementar un método move () idéntico para cada uno de ellos? ¿Qué conceptos para la reutilización del código aparte de la herencia de implementación existen en el mundo de OO?
Algunos idiomas tienen Mixins. ¿Son Mixins la respuesta a mi pregunta? Leí sobre ellos, pero realmente no puedo imaginar cómo funcionaría Mixins en un mundo Java y si realmente pueden resolver el problema aquí.
Otra idea era que hay una clase que solo implementa la interfaz de Car, llamémoslo AbstractCar, e implementa el método move (). Ahora otros autos también implementan la interfaz Car, internamente crean una instancia de AbstractCar e implementan su propio método move () llamando a move () en su Auto interno abstracto. ¿Pero no sería esto desperdiciar recursos en vano (un método que llama simplemente a otro método, está bien, JIT podría alinear el código, pero aún así) y usar memoria extra para guardar objetos internos, ni siquiera lo necesitaría con la herencia de implementación? (después de todo, todos los objetos necesitan más memoria que solo la suma de los datos encapsulados). Tampoco es incómodo para un programador escribir métodos ficticios como
public void move() {
abstractCarObject.move();
}
?
¿Alguien puede imaginar una mejor idea de cómo evitar la herencia de implementación y aún así poder reutilizar el código de una manera fácil?
Deberías leer Patrones de diseño. Descubrirá que las interfaces son fundamentales para muchos tipos de patrones de diseño útiles. Por ejemplo, el resumen de diferentes tipos de protocolos de red tendrá la misma interfaz (para el software que lo llama) pero poca reutilización del código debido a los diferentes comportamientos de cada tipo de protocolo.
Para algunos algoritmos son reveladores al mostrar cómo armar la miríada de elementos de una programación para realizar alguna tarea útil. Los patrones de diseño hacen lo mismo para los objetos. Le muestra cómo combinar objetos de una manera que permite realizar una tarea útil.
Para facilitar las mezclas / composiciones, consulte mis Anotaciones y el Procesador de anotaciones:
http://code.google.com/p/javadude/wiki/Annotations
En particular, el ejemplo de mixins:
http://code.google.com/p/javadude/wiki/AnnotationsMixinExample
Tenga en cuenta que actualmente no funciona si las interfaces / tipos que se delegan tienen métodos parametrizados (o tipos parametrizados en los métodos). Estoy trabajando en eso...
Puedes usar composición En su ejemplo, un objeto Car puede contener otro objeto llamado Drivetrain. El método move () del automóvil simplemente podría llamar al método drive () de su transmisión. La clase Drivetrain podría, a su vez, contener objetos como Engine, Transmission, Wheels, etc. Si estructurara su jerarquía de clases de esta manera, podría crear fácilmente autos que se muevan de diferentes maneras al componerlos de diferentes combinaciones de las partes más simples (es decir, código de reutilización).
También puedes usar la composición y el patrón de estrategia. Texto del enlace
public class Car
{
private ICar _car;
public void Move() {
_car.Move();
}
}
Esto es mucho más flexible que usar un comportamiento basado en herencia, ya que le permite cambiar en tiempo de ejecución, sustituyendo los nuevos tipos de automóvil según sea necesario.
El problema con la mayoría de los ejemplos contra la herencia son ejemplos en los que la persona está usando la herencia de forma incorrecta, no una falla de la herencia para abstraerse correctamente.
En el artículo que publicó un enlace, el autor muestra el "quebrantamiento" de la herencia usando Stack y ArrayList. El ejemplo es defectuoso porque una Pila no es una Lista de Arreglos y, por lo tanto, la herencia no debe ser utilizada. El ejemplo es tan defectuoso como el Caracteres que se extienden por Cadena, o el Número de Extensión PointXY.
Antes de extender la clase, siempre debe realizar la prueba "is_a". Como no puedes decir que Every Stack es un ArrayList sin estar equivocado de alguna manera, entonces no deberías in-espíritu.
El contrato para Stack es diferente al contrato de ArrayList (o List) y stack no debe heredar métodos que no le interesan (como get (int i) y add ()). De hecho, Stack debería ser una interfaz con métodos tales como:
interface Stack<T> {
public void push(T object);
public T pop();
public void clear();
public int size();
}
Una clase como ArrayListStack podría implementar la interfaz Stack, y en ese caso usar la composición (que tiene una ArrayList interna) y no la herencia.
La herencia no es mala, la mala herencia es mala.
La herencia no es necesaria para un lenguaje orientado a objetos.
Considere Javascript, que está incluso más orientado a objetos que Java, podría decirse. No hay clases, solo objetos. El código se reutiliza al agregar métodos existentes a un objeto. Un objeto JavaScript es esencialmente un mapa de nombres para funciones (y datos), donde el contenido inicial del mapa es establecido por un prototipo, y se pueden agregar nuevas entradas a una instancia determinada sobre la marcha.
Respuesta corta: Sí, es posible. Pero tienes que hacerlo a propósito y no por casualidad (usando final, abstracto y diseño con herencia en mente, etc.)
Respuesta larga:
Bueno, la herencia no es para "reutilización del código", es para "especialización" de clase, creo que es una interpretación errónea.
Por ejemplo, es una muy mala idea crear una Pila a partir de un Vector, simplemente porque son iguales. O propiedades de HashTable solo porque almacenan valores. Ver [Efectivo].
La "reutilización del código" era más una "vista comercial" de las características OO, lo que significa que los objetos se podían distribuir fácilmente entre los nodos; y eran portátiles y no tenían los problemas de generación de lenguajes de programación previos. Esto ha sido probado a medias. Ahora tenemos bibliotecas que se pueden distribuir fácilmente; por ejemplo, en java, los archivos jar se pueden usar en cualquier proyecto ahorrando miles de horas de desarrollo. OO todavía tiene algunos problemas con la portabilidad y cosas por el estilo, esa es la razón por la cual WebServices es tan popular (como antes era CORBA), pero ese es otro hilo.
Este es un aspecto de la "reutilización del código". El otro es efectivamente, el que tiene que ver con la programación. Pero en este caso no se trata solo de "salvar" líneas de código y crear monstruos frágiles, sino de diseñar teniendo en cuenta la herencia. Este es el elemento 17 en el libro mencionado anteriormente; Elemento 17: Diseñe y documente por herencia o bien prohíba. Ver [Efectivo]
Por supuesto, puede tener una clase de automóvil y toneladas de subclases. Y sí, el enfoque que mencionas sobre Car interface, AbstractCar y CarImplementation es una forma correcta de hacerlo.
Usted define el "contrato" que el automóvil debería cumplir y dice que estos son los métodos que esperaría tener cuando se trata de automóviles. El automóvil abstracto que tiene la funcionalidad básica de cada automóvil pero dejando y documentando los métodos que las subclases son responsables de manejar. En java, haces esto marcando el método como abstracto.
Cuando se avanza de esta manera, no hay ningún problema con la clase "frágil" (o al menos el diseñador es consciente o la amenaza) y las subclases completan solo aquellas partes que el diseñador les permite.
La herencia es más para "especializar" las clases, de la misma manera que un camión es una versión especializada de Car, y MosterTruck una versión especializada de Truck.
No es necesario crear una subclase "ComputerMouse" desde un automóvil simplemente porque tiene una rueda (rueda de desplazamiento) como un automóvil, se mueve y tiene una rueda debajo solo para guardar las líneas de código. Pertenece a un dominio diferente y se usará para otros fines.
La forma de evitar la herencia de "implementación" está en el lenguaje de programación desde el principio, debe usar la palabra clave final en la declaración de clase y de esta manera está prohibiendo las subclases.
Subclases no es malo si se hace a propósito. Si se hace sin cuidado, puede convertirse en una pesadilla. Diría que debe comenzar como privado y "final" como sea posible y, si es necesario, hacer las cosas más públicas y ampliables. Esto también se explica ampliamente en la presentación "Cómo diseñar buenas API y por qué es importante" Ver [Buena API]
Sigue leyendo artículos y con tiempo y práctica (y mucha paciencia) esto se aclarará. Aunque a veces solo necesitas hacer el trabajo y copiar / pegar código: P. Esto está bien, siempre que intentes hacerlo bien primero.
Aquí están las referencias tanto de Joshua Bloch (anteriormente trabajando en Sun en el centro de Java que ahora trabaja para Google)
[Efectivo] Java efectivo. Definitivamente el mejor libro de Java que un no principiante debe aprender, comprender y practicar. Una necesidad. [Buena API] Presentación que trata sobre el diseño, la reutilización y temas relacionados de API. Es un poco largo pero vale la pena cada minuto.Cómo diseñar una buena API y por qué es importante
Saludos.
Actualización: Eche un vistazo al minuto 42 del enlace de video que le envié. Habla sobre este tema:"Cuando tienes dos clases en una API pública y crees que una es una subclase de otra, como Foo es una subclase de Bar, pregúntate a ti mismo, ¿es Every Foo a Bar? ..."
Y en el minuto anterior habla de "reutilización del código" mientras habla de TimeTask.