¿Es posible OOP y está evitando por completo la herencia de implementación?


20

Elegiré Java como ejemplo, la mayoría de la gente lo sabe, aunque también funcionaba cada otro idioma de OO.

Java, como muchos otros lenguajes, tiene herencia de interfaz y herencia de implementación. P.ej. 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 article, saying it's better to inherit interfaces than implementations, es posible que le guste leerlo (al menos en la primera mitad de la primera página), es bastante interesante. Evita problemas como el fragile base class problem. 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 acerca de esto, es que la herencia de implementación significa código de reutilización, 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?

E.g. 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 coches, que necesitas implementar un método move() idéntico para cada uno de ellos? ¿Qué conceptos para la reutilización de códigos 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 es que hay una clase que solo implementa la interfaz Car, llamémosla 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 adicional para guardar objetos internos, ni siquiera lo necesitaría con la herencia de implementación? (Después de todo, cada objeto necesita más memoria que la suma de los datos encapsulados) Además, no es raro para un programador para escribir métodos ficticias como

public void move() { 
    abstractCarObject.move(); 
} 

?

Cualquiera 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?

  0

Por mi parte, no me gusta llamarlo "la herencia de interfaces", pero "implementación de la interfaz". Eso es una cosa que realmente me gusta del lenguaje Java, dos conceptos diferentes nombrados en consecuencia. 11 oct. 092009-10-11 21:40:20

11

Respuesta corta: Sí, es posible. Pero hay que hacerlo a propósito y no por azar (usando final, abstracta y el diseño con la herencia en cuenta, etc.)

Respuesta larga:

Bueno, la herencia no es en realidad de "reutilización de código ", es para la" especialización "de la clase, creo que esta 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 distribuían 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 de 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ño y documento para 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.

Define el "contrato" que el automóvil debe cumplir y dice que estos son los métodos que esperaría tener cuando se habla 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 proceda 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 un camión es una versión especializada de automóvil, y MosterTruck una versión especializada de camión.

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 manera 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 [API buena]

Siga leyendo artículos y con el tiempo y la 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.

Estas son las referencias tanto de Joshua Bloch (antes de trabajar en Sun en el centro de Java que ahora trabaja para Google)


[efectiva] Effective Java. Definitivamente el mejor libro de Java que un no principiante debe aprender, comprender y practicar. Una necesidad.

Effective Java


[Buena API] presentación que habla en el diseño de la API, la reutilización y temas relacionados. Es un poco largo pero vale la pena cada minuto.

How To Design A Good API and Why it Matters

Saludos.


Actualización: Eche un vistazo al minuto 42 del enlace del 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 se habla de" código de reutilización "mientras se habla de TimeTask.

  0

En serio, no veo cómo un camión debe heredar de un automóvil :) 11 oct. 092009-10-11 21:55:47

+2

Tal vez los camiones y los automóviles deberían heredar de Automóvil. :-) 18 feb. 102010-02-18 22:05:38


4

También podría usar la composición y el patrón de estrategia. link text

public class Car 
{ 
    private ICar _car; 

    public void Move() { 
    _car.Move(); 
    } 
} 

Esto es mucho más flexible que el uso de un comportamiento basado herencia, ya que le permite cambiar en tiempo de ejecución, mediante la sustitución de nuevos tipos de coches como sea necesario.


2

Puede usar composition. En su ejemplo, un objeto Car puede contener otro objeto llamado Drivetrain. El método move() del automóvil podría simplemente 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).


0

Debería 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 hacer algunas tareas útiles. 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.

Design Patterns by the Gang of Four


2

Para hacer mixins/composición más fácil, echar un vistazo a mis anotaciones y procesador de anotación:

http://code.google.com/p/javadude/wiki/Annotations

En particular, el mixins ejemplo:

http://code.google.com/p/javadude/wiki/AnnotationsMixinExample

Tenga en cuenta que actualmente no funciona si la interf aces/types que se delegan para tener métodos parametrizados (o tipos parametrizados en los métodos). Estoy trabajando en eso ...

  0

Esto es algo bastante interesante, tienes ahí. Voy a echar un vistazo detallado :) 23 sep. 082008-09-23 23:23:50


8

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 abstraer 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 ArrayList y, por lo tanto, no se debe usar la herencia. 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 la pila no debe heredar métodos que no le interesan (como get (int i) y add()). De hecho Stack debe 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 que la composición de uso caso (que tiene un ArrayList interna) y no herencia.

La herencia no está mal, la mala herencia es mala.


0

La herencia no es necesaria para un lenguaje orientado a objetos.

Considere Javascript, que es aún 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.


2

Es gracioso responder a 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 bonito, pero me encanta la idea detrás de él :-)