Borland/Embarcadero no tienen la culpa de la existencia de las interfaces... ni Microsoft. Las interfaces forman parte de la teoría OO, su introducción fue posterior a la aparición del primer LOO, SmallTack (Aunque técnicamente el primer LOO fue Simula).
SmallTack introdujo las bases, de allí en más el paradigma evolucionó a la medida en que aparecieron los siguientes LOO.
Las interfaces fueron añadidas al paradigma con posterioridad para dar respuesta a la herencia múltiple y poder extender el paradigma.
De ser así, entonces los verdaderos culpables han sido las mentes que se han dedicado a divulgar el modelo que venimos empleando masivamente desde 2 décadas.
Luego si, cada lenguaje eligió su arsenal... pero el punto es que las interfaces son originariamente del paradigma. Es natural que un LOO las lleve a cabo.
Ahora bien, que las interfaces no lleven implementaciones es su gracia. Es el mecanismo dinámico lo que aporta la posibilidad de tratar a las clases de una forma más abstracta.
Por más que Delphi hiciera uso de herencia múltiple, debe contar con las interfaces ya que Microsoft las emplea en sus bibliotecas... Y nuevamente, aquí no tiene la culpa Microsoft... sino la teoría. Microsoft adoptó el uso de las interfaces para poder implementar los mecanismos dinámicos.
Si existen interfaces o clases abstractas, da lo mismo... en última se trata de un punto de unión conceptual que desafía en algunos niveles las posibilidades de la comprensión y algunos desarrollos de bibliotecas.
Yo me planteo la posibilidad de herencia múltiple (sea por interfaces o clases abstractas) debido a que en términos prácticos, en el desarrollo de amplios árboles tendríamos varios cruces de ramas en diferentes niveles. Lo cual resulta un tanto confuso y complejo.
Adoptar esta forma de trabajo obliga a un diseño tanto horizontal como vertical en todas las ramas, añadir puntos de unión y ramificación para conseguir la mayor extensibilidad posible.
Imagínate dos árboles muy próximos en donde sus ramas se entrelazan y cruzan.
Este es el camino que llevó a la práctica Java: añadió interfaces en todos los niveles intentando llevar a su máxima capacidad la herencia múltiple.
Dedicaré unos párrafos a ilustrar esto de los árboles y ramas que se cruzan. Observa la siguiente imagen:
+-+ +--+ +-+ |A| |AB| |B| +-+ +--+ +-+ / \ / \ / \ / \ / \ / \ +-+ +---+ +---+ +-+ |A| |AAB| |ABB| |B| +-+ +---+ +---+ +-+ \ / \ / \ / +----+ +----+ +----+ |AAAB| |AABB| |ABBB| +----+ +----+ +----+
Espero que se vea bien... La rama A y B se han extendido y dan origen a nuevos elementos.
Si utilizamos herencia múltiple habrá que adoptar un criterio complejo de uso de memoria, etc. Los nodos que hacen de puente entre A y B deberían ser clases, abstractas o no... clases en fin.
El problema de ello es que son puntos fijos, tienen que tener su lugar en la VTM, se adosan fuertemente a ambas ramas, y termina construyendo una nueva rama haciendo a la VTM mucho más compleja y larga... En vez de ganar dinamismo y flexibilidad se está haciendo más rígida.
Todos los vínculos entre las clases están definidos previamente y ampliar ese esquema lleva a una reevaluación de estos vínculos a fin de incorporar las nuevas ramas.
Ramas de ramas vinculadas con demasiada fuerza, un acoplamiento elevado.
Por más clases abstractas que sean el mecanismo debe ser muy seguro y a pruebas de cualquier problema. Un cambio o alteración en un lado tiene el doble o más de repercusiones que en otro esquema:
+-+ +--+ +-+ |A| |A?| |*| +-+ +--+ +-+ / \ / \ / \ / \ / \ / \ +-+ +---+ +---+ +-+ |A| |AA?| |A?*| |?| +-+ +---+ +---+ +-+ \ / \ / \ / +----+ +----+ +----+ |AAA?| |AA?*| |A?**| +----+ +----+ +----+
Es un potencial castillo de naipes que se puede desmoronar con mayor facilidad. Puede pensarse que la herencia múltiple conduce a una buena manera de extenderse (si, es muy cierto, ofrece el doble o más de capacidad de extenderse que en esquema simple. Esa es una ventaja por sobre la herencia simple) pero al mismo tiempo debe ser rígido puesto que la flexibilidad exige de mecanismos seguros y fuertes para evitar que ramas enteras se vean perjudicadas. (Punto en contra).
Al tratarse de clases, se requieren de unas cuantas pasadas del compilador para linkear todo y volver a reestructurar sus tablas. Un esfuerzo enorme.
Si fuera por el contrario fueran interfaces las ramas ganan mayor independiencia.
Ya que no se tocan tanto las implementaciones... Las interfaces se "vinculan" con las clases por demanda. Las ramas de los árboles son más flexibles, "aparecen" y "desaparecen" en cuanto son necesarias.
Las interfaces nos permiten saltar de una clase a otra sin afectar, con demasiados cambios, las implementaciones de una a otra.
¿Porqué? Porque todo funciona en memoria dinámica. En vez de generar una entrada más compleja en la VTM le indica que trabaje con las entradas de la VTM de las clases definidas según necesite y rote de una entrada a otra.
De este modo un cambio en B afectará más en la rama B y en menor medida en las ramas que se vincula con A.
El compilador sólo tendrá que actualizar las entradas relacionadas con B y no será necesario entrar en la zona de A:
+-+ +---+ +-+ |A| |IAB| |B| +-+ +---+ +-+ / \ | / \ / \ | / \ +-+ +--+ +----+ +--+ +-+ |A| |IA| | AB | |IB| |B| +-+ +--+ +----+ +--+ +-+ \ | / \ | / +---+ +----+ +---+ |AAB| |I...| |ABB| +---+ +----+ +---+ \ | / +----+ |AABB| +----+
Como puede verse, entre las clases existen interfaces, denominadas con Ixxx. Las interfaces hacen de puente entre las clases. Veamos el efecto del cambio:
+-+ +---+ +-+ |A| |IA*| |*| +-+ +---+ +-+ / \ | / \ / \ | / \ +-+ +--+ +----+ +--+ +-+ |A| |IA| | A* | |I*| |*| +-+ +--+ +----+ +--+ +-+ \ | / \ | / +---+ +----+ +---+ |AA?| |I...| |AB*| +---+ +----+ +---+ \ | / +----+ |AAB*| +----+
Un efecto directo en su rama, en las comunes a A, y en "duda" o menor en la rama que entran hacia A.
Lo interesante del uso de las interfaces es que ambas ramas pueden actuar de forma separada. No sucede lo mismo con el esquema basado en clases. Si quitas una clase, el resto se viene abajo:
+-+ +--+ +-+ |A| |X | |B| +-+ +--+ +-+ / \ / \ / \ / \ / \ / \ +-+ +---+ +---+ +-+ |A| | X | | X | |B| +-+ +---+ +---+ +-+ \ / \ / \ / +----+ +----+ +----+ | X | | X | | X | +----+ +----+ +----+
Con las interfaces, si la eliminamos afectamos la posibilidad de entrar en la herencia múltiple... pero no se toca ninguna implementación de las ramas y se vuelve a un esquema en donde no existe herencia múltiple, existiendo dos ramas separadas. En este escenario es donde existe la ambiguedad de donde colocar las clases "mixtas":
+-+ +-+ |A| |B| +-+ +-+ / | ? ? | \ +--+ +--+ +--+ +--+ +--+ |A1| |A2| ... |AB| ... |B2| |B1| +--+ +--+ +--+ +--+ +--+
Es por ello que en realidad los vínculos dado por las interfaces son más flexibles... en realidad se inicia una nueva rama desde AB y la interface hace de puente hacia A y B en cuanto sea necesario:
+-+ +-+ |A| |B| +-+******** +-+ / | * | \ +--+ +--+ +--+ +--+ +--+ |A1| |A2| ... |AB| ... |B2| |B1| +--+ +--+ +--+ +--+ +--+
+-+ +-+ |A| |B| +-+ ********+-+ / | * | \ +--+ +--+ +--+ +--+ +--+ |A1| |A2| ... |AB| ... |B2| |B1| +--+ +--+ +--+ +--+ +--+
Esa es la virtud que poseen las interfaces. Así se las han definido, no es que sea una implementación pobre por parte de Embarcadero... el funcionamiento es igual en cualquier otro LOO que tenga soporte a interfaces.
Las interfaces son esas cajas imprendidas, en la que no hay nada pero puede tener todo...
Deben y necesitan ser "huecas" para poder trabajar de forma polimorfica con las clases. No son clases, ni van a serlo.
Como he dicho: el caso de Java es peculiar: se diseñó para intentar conseguir un buen manejo de herencia múltiple y tratar de corregir ciertos defectos de como se lo ha implementado en C++. El punto es que ha logrado muchísima rigidez añadiendo demasiadas interfaces, y por más flexible que éstan sean... en buena cantidad en vez de hacer más fácil lo difícil, hace de forma difícil lo difícil.
La postura y estructura de Delphi va en otro sentido, adoptó las interfaces y la utilizó sabiamente ofreciendo un equilibrio entre flexibilidad y rigidez.
Son esquemas de trabajo diferentes. Aún así, sea cual fuese... no deja de evidenciar que son casos que pueden traernos malos problemas... Se puede hacer mal las cosas tanto en herencia simple como en múltiple, se puede hacer guizados tanto en la programación estructurada como en POO... eso no lo discuto, pero en una herencia múltiple es mucho más fácil meter la pata.
Repito nuevamente, yo me lo sigo pensando... ¿Cuantos "ornitorrincos" realmente podemos esperar? Prefiero tener estabilidad en el esquema de herencia simple y adoptar las interfaces para ganar dinamismo sin afectar el diseño en cuanto y cuando sea necesario.
En ocasiones es bueno añadir una clase abstracta y santo remedio. Si es que esta clase luego realmente nos aporta alguna utilidad. Pero hay casos en los que añadir clase(s) en vez de facilitarnos las cosas, nos nubla el desarrollo. ¿Viste el enlace que había pasado unos post antes Marc? Allí hay un caso de uso.
El meollo del asunto es cuando se plantea la posibilidad de llevar el uso de herencia múltiple (sea por clases o por interfaces) al extremo... poniendo en riesgo los límites mismos del paradigma:
Llevar la abstracción a una situación más abstracta.
Quizá en alguna que otra ocasión sea necesario, para lo normal la herencia simple trabaja fenomenalmente.
Todo paradigma cuando se lo lleva a trabajar con los puntos extremos tiene problemas. Hagamos una analogía con el sistema de punto flotante... tenemos los números normalizados y los desnormalizados... las interfaces sería el mecanismo de bajo desbordamiento gradual del estándar IEEE 754.
Quizá te parezca un "parche" a la teoría OO, pero si lo vemos así... entonces la OO no deja de ser un parche de la programación estructurada. Después de todo, por debajo de la OO descansa la programación estructurada...
Es lo que se vino haciendo desde siempre: poner una capa sobre otra... una pila, una fachada.
Y un nuevo paradigma, si es que llega uno, hará lo mismo: envolverlo.
Disculpen lo largo del post.
Saludos,