Ir al contenido


Foto

¿Usas interfaces en tus desarrollos habituales?


  • Por favor identifícate para responder
45 respuestas en este tema

Encuesta: ¿Usas interfaces en tus desarrollos habituales? (17 miembros han emitido voto)

¿Usas interfaces en tus desarrollos habituales?

  1. Si (8 votos [47.06%])

    Porcentaje de voto: 47.06%

  2. No (4 votos [23.53%])

    Porcentaje de voto: 23.53%

  3. Desconozco este recurso (5 votos [29.41%])

    Porcentaje de voto: 29.41%

Votar Los invitados no pueden votar

#21 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 01 octubre 2010 - 11:15

Hola Marc,

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:



delphi
  1.     +-+        +--+        +-+
  2.     |A|        |AB|        |B|
  3.     +-+        +--+        +-+
  4.     /  \      /    \      /  \
  5.   /    \    /      \    /    \
  6. +-+      +---+        +---+      +-+
  7. |A|      |AAB|        |ABB|      |B|
  8. +-+      +---+        +---+      +-+
  9.   \    /    \      /    \    /
  10.   +----+      +----+      +----+
  11.   |AAAB|      |AABB|      |ABBB|
  12.   +----+      +----+      +----+


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:



delphi
  1.     +-+        +--+        +-+
  2.     |A|        |A?|        |*|
  3.     +-+        +--+        +-+
  4.     /  \      /    \      /  \
  5.   /    \    /      \    /    \
  6. +-+      +---+        +---+      +-+
  7. |A|      |AA?|        |A?*|      |?|
  8. +-+      +---+        +---+      +-+
  9.   \    /    \      /    \    /
  10.   +----+      +----+      +----+
  11.   |AAA?|      |AA?*|      |A?**|
  12.   +----+      +----+      +----+



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:



delphi
  1.     +-+  +---+  +-+
  2.     |A|  |IAB|  |B|
  3.     +-+  +---+  +-+
  4.     /  \  |    /  \
  5.   /    \  |  /    \
  6. +-+  +--+ +----+ +--+  +-+
  7. |A|  |IA| | AB | |IB|  |B|
  8. +-+  +--+ +----+ +--+  +-+
  9.   \  |  /      \  |  /
  10.     +---+ +----+ +---+
  11.     |AAB| |I...| |ABB|
  12.     +---+ +----+ +---+
  13.         \  |  /
  14.           +----+
  15.           |AABB|
  16.           +----+


Como puede verse, entre las clases existen interfaces, denominadas con Ixxx. Las interfaces hacen de puente entre las clases. Veamos el efecto del cambio:



delphi
  1.     +-+  +---+  +-+
  2.     |A|  |IA*|  |*|
  3.     +-+  +---+  +-+
  4.     /  \  |    /  \
  5.   /    \  |  /    \
  6. +-+  +--+ +----+ +--+  +-+
  7. |A|  |IA| | A* | |I*|  |*|
  8. +-+  +--+ +----+ +--+  +-+
  9.   \  |  /      \  |  /
  10.     +---+ +----+ +---+
  11.     |AA?| |I...| |AB*|
  12.     +---+ +----+ +---+
  13.         \  |  /
  14.           +----+
  15.           |AAB*|
  16.           +----+


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:



delphi
  1.     +-+        +--+        +-+
  2.     |A|        |X |        |B|
  3.     +-+        +--+        +-+
  4.     /  \      /    \      /  \
  5.   /    \    /      \    /    \
  6. +-+      +---+        +---+      +-+
  7. |A|      | X |        | X |      |B|
  8. +-+      +---+        +---+      +-+
  9.   \    /    \      /    \    /
  10.   +----+      +----+      +----+
  11.   | X  |      | X  |      | X  |
  12.   +----+      +----+      +----+



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":



delphi
  1.     +-+                +-+
  2.     |A|                |B|
  3.     +-+                +-+
  4.     / |    ?        ?    | \
  5. +--+ +--+    +--+    +--+ +--+
  6. |A1| |A2| ... |AB| ... |B2| |B1|
  7. +--+ +--+    +--+    +--+ +--+



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:



delphi
  1.     +-+                +-+
  2.     |A|                |B|
  3.     +-+********        +-+
  4.     / |        *        | \
  5. +--+ +--+    +--+    +--+ +--+
  6. |A1| |A2| ... |AB| ... |B2| |B1|
  7. +--+ +--+    +--+    +--+ +--+





delphi
  1.     +-+                +-+
  2.     |A|                |B|
  3.     +-+        ********+-+
  4.     / |        *        | \
  5. +--+ +--+    +--+    +--+ +--+
  6. |A1| |A2| ... |AB| ... |B2| |B1|
  7. +--+ +--+    +--+    +--+ +--+



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,
  • 0

#22 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 01 octubre 2010 - 11:27

Rolphy, el ejemplo que pones demuestra por qué las interfaces no sirven para emular la herencia múltiple más que de forma parcial, además queriendo ahorrar código por un lado se complica la cosa con un gran número de clases e interfaces intermedias.

Crear "clases sueltas" que implementen comportamientos aislados no responde a la mayoría de necesidades reales. Una cosa es mostrar un mensaje a piñón fijo -ShowMessage('Hola')-, pero ¿cómo solucionas aquellos métodos en que es necesario acceder a variables / propiedades / métodos propias de la clase principal? Ahí no te queda más remedio que desarrollar el cuerpo del código dentro de un método de la clase principal, y si hay otra clase que declara la misma interfaz tendrá que crear su propio método repitiendo todo ese código -si es que necesita hacer lo mismo-, no se me ocurre otra salida, si alguien sabe una alternativa que no consista en crear una "clase extra" para cada comportamiento diferente ... Llevando la OOP al extremo, tanto en clases como en interfaces, podríamos discutir el uso de clases extra especializadas en realizar pequeñas tareas para evitar repetición de código, aunque creo que en ese caso es peor el remedio que la enfermedad.

Compañero andres1569 si realizas una mala implementación, obtendrás un resultado erróneo tanto sea con Interfaces como con Clases. 

:smiley: Totalmente de acuerdo, es de perogrullo. Por supuesto, lo que dije referente a las interfaces es aplicable a cualquier tipo de desarrollo, sólo que siempre me ha parecido que las interfaces, al no desarrollar un comportamiento por defecto (al igual, como bien dices que las clases abstractas), deben documentarse bien y dejar claro qué se espera de ellas. Sobre todo porque cada clase que declare una interfaz está obligada a desarrollar todos los métodos de dicha interfaz, no hay comportamientos heredados y no le basta sólo con heredar.

Y hablo también de documentación porque, aunque cada uno en sus propios desarrollos sabe bastante bien qué hace cada cosa, en el caso de las interfaces que nos toca usar -y no programar- cuando accedemos a su código fuente sólo vemos una declaración de métodos, a diferencia de las clases, y hay que usar muchas veces la imaginación para saber qué hace cada cual. Ésto es lo que me ha sucedido al acceder a objetos COM y a interfaces para acceder al IDE de Delphi.

Saludos
  • 0

#23 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 01 octubre 2010 - 01:19

Hola Delphius.

Parece que tienes habilidad en traer sobre la mesa aspectos de Delphi que me sacan de quicio. En realidad no creas que hay tantos (aunque a la vista de este hilo parezca lo contrario), el mayor de ellos probablemente sea la sección published, ¿ que pinta esta sección en lugar de poner dentro de private los controles del formulario ?, ¿ a que lumbreras se le ocurrió hacer públicos todos los controles en un formulario para que desde otro formulario nos puedan destrozar a su gusto toda la lógica que implementemos en el primero ?.

Dejando el off-topic y volviendo al tema de las interfaces/herencia múltiple, profundizas mucho en la implementación de la herencia por parte del compilador. Y deja que te diga que personalmente a mi eso no me interesa en lo más mínimo, no es mi problema, ni siquiera debería conocer la existencia de las VTM, para eso está el compilador. Soy un programador de alto nivel, no un programador de bajo nivel, no quiero conocer las interioridades del compilador y del ejecutable generado. Decía que esto me saca de quicio porqué cada vez que tengo que definir un método como dynamic o virtual, nunca olvido mentar las madres de los empleados de Embarcadero. Yo debería poder sobreescribir cualquier método sin necesidad de haberlo declarado previamente dinámico o virtual, y no solo me obligan a declararlo sino que encima me veo forzado a tomar una decisión que compete únicamente al compilador.

No me importan ni quiero conocer estos problemas (y disculpa que lo diga tan claro, puesto que es evidente que te has trabajado mucho los ejemplos), le competen al compilador y si Embarcadero hiciese bien su trabajo, el compilador se podría hacer cargo perfectamente de ello en lugar de molestarnos a nosotros, como hacen ahora.

Tampoco me preocupa demasiado la complejidad del árbol de herencia en nuestros programas. Puesto que este árbol tendrá la profundidad y ancho que nosotros queramos darle. Como bien dices, se pueden hacer buenos y malos diseños en todas las tecnologías.

No entiendo como puedes definir la falta de implementación como una gracia y no una desgracia. No entiendo porqué piensas que vas a encontrar pocos ornitorrincos. Si los buscas vas a encontrar muchos casos de animales que andan y nadan, que pueden ir a cuatro patas y a dos patas, ... O más prosaicamente, encontrarás que lo podrías usar en muchos casos prácticos en la programación de gestión que hacemos la mayoría.

Imagina por ejemplo que te declaras una clase TFacturas. Esta clase lo más probable es que la hagamos heredar de alguna clase del estilo de TListaValoradaProductos, de forma que no tengamos que repetir toda la lógica de añadir productos, calcular importes, etc. ... Pero probablemente nos gustará que también tenga las características de otras clases, como podría ser la TDocumentoEMailable (documento que se puede enviar por e-Mail), con sus métodos Preparar, Enviar, etc. ...

Yo no quiero que TDocumentoEMailable sea una simple interfaz, vacía de toda implementación, y que tenga que reprogramarla de nuevo, totalmente, para las TFacturas (y para cualquier otra clase a la que la quiera aplicar). Yo quiero que ya tenga todo su código para verificar la validez del correo electrónico, conectar a un Servidor SMTP y comunicarse con él, generar un mensaje con un archivo anexo, etc. .... Lo único que voy a personalizar, sobreescribiéndolo en la TFactura es el renderizado del archivo anexo a enviar (renderizado que evidentemente va a ser distinto para una factura, que para, pongamos, la agenda de un usuario).

La implementación actual de las interfaces creo que me soluciona bien pocas cosas, y por eso ni las utilizo ni creo que vaya a hacerlo nunca. La verdadera herencia múltiple sería harina de otro costal.

Saludos.
  • 0

#24 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 01 octubre 2010 - 01:20

Rolphy, el ejemplo que pones demuestra por qué las interfaces no sirven para emular la herencia múltiple más que de forma parcial, además queriendo ahorrar código por un lado se complica la cosa con un gran número de clases e interfaces intermedias.

Crear "clases sueltas" que implementen comportamientos aislados no responde a la mayoría de necesidades reales. Una cosa es mostrar un mensaje a piñón fijo -ShowMessage('Hola')-, pero ¿cómo solucionas aquellos métodos en que es necesario acceder a variables / propiedades / métodos propias de la clase principal? Ahí no te queda más remedio que desarrollar el cuerpo del código dentro de un método de la clase principal, y si hay otra clase que declara la misma interfaz tendrá que crear su propio método repitiendo todo ese código -si es que necesita hacer lo mismo-, no se me ocurre otra salida, si alguien sabe una alternativa que no consista en crear una "clase extra" para cada comportamiento diferente ... Llevando la OOP al extremo, tanto en clases como en interfaces, podríamos discutir el uso de clases extra especializadas en realizar pequeñas tareas para evitar repetición de código, aunque creo que en ese caso es peor el remedio que la enfermedad.


Saludos.

Se me ocurre de plano para satisfacer tus necesidades (por decirle de algún modo) lo siguiente:


delphi
  1. type
  2.  
  3.   TMostrar = class;
  4.  
  5.   //*************
  6.   IMostrarMensaje = interface
  7.     ['{C71A12B4-CBFB-4882-A54B-DF012EC77C84}']
  8.     procedure MostrarMensaje;
  9.   end;
  10.  
  11.   TMostrarMensaje = class(TInterfacedObject, IMostrarMensaje)
  12.     FMostrar : TMostrar;
  13.     procedure MostrarMensaje; virtual;
  14.     constructor Create(AMostrar : TMostrar);
  15.   end;
  16.   //*******************
  17.  
  18.   //*************
  19.   IMostrarDialogo = interface
  20.     ['{0937AF3C-DB17-4B68-8124-5B002E5F6AE9}']
  21.     procedure MostrarDialogo;
  22.   end;
  23.  
  24.   TMostrarDialogo = class(TInterfacedObject, IMostrarDialogo)
  25.     FMostrar : TMostrar;
  26.     procedure MostrarDialogo;
  27.     constructor Create(AMostrar : TMostrar);
  28.   end;
  29.   //*************
  30.  
  31.   //*************
  32.   IMostrarInput = interface
  33.     ['{EAF3FF31-2377-4BAC-A3B0-D6A47BD59761}']
  34.     procedure MostrarInput;
  35.   end;
  36.  
  37.   TMostrarInput = class(TInterfacedObject,IMostrarInput)
  38.     FMostrar : TMostrar;
  39.     procedure MostrarInput;
  40.     constructor Create(AMostrar : TMostrar);
  41.   end;
  42.   //*************
  43.  
  44.   TMostrar = class(TInterfacedObject, IMostrarMensaje, IMostrarDialogo, IMostrarInput)
  45.   private
  46.     function GetMiPropiedad: string;
  47.   protected
  48.     FMostrarMensaje : IMostrarMensaje;
  49.     FMostrarDialogo : IMostrarDialogo;
  50.     FMostrarInput : IMostrarInput;
  51.   public
  52.     property MostrarMensaje : IMostrarMensaje read FMostrarMensaje write FMostrarMensaje implements IMostrarMensaje;
  53.     property MostrarDialogo : IMostrarDialogo read FMostrarDialogo write FMostrarDialogo implements IMostrarDialogo;
  54.     property MostrarInput : IMostrarInput read FMostrarInput write FMostrarInput implements IMostrarInput;
  55.     property MiPropiedad : string read GetMiPropiedad;
  56.   end;
  57.  
  58.  
  59.  
  60.   TForm2 = class(TForm)
  61.     Button1: TButton;
  62.     procedure Button1Click(Sender: TObject);
  63.   private
  64.     { Private declarations }
  65.     FMostrar : TMostrar;
  66.   public
  67.     { Public declarations }
  68.   end;
  69.  
  70. var
  71.   Form2: TForm2;
  72.  
  73. implementation
  74.  
  75. {$R *.dfm}
  76.  
  77. { TMostrarMensaje }
  78.  
  79. constructor TMostrarMensaje.Create(AMostrar: TMostrar);
  80. begin
  81.   inherited Create;
  82.   FMostrar := AMostrar;
  83. end;
  84.  
  85. procedure TMostrarMensaje.MostrarMensaje;
  86. begin
  87.   ShowMessage('MostrarMensaje');
  88. end;
  89.  
  90. { TMostrarDialogo }
  91.  
  92. constructor TMostrarDialogo.Create(AMostrar: TMostrar);
  93. begin
  94.   inherited Create;
  95.   FMostrar := AMostrar;
  96. end;
  97.  
  98. procedure TMostrarDialogo.MostrarDialogo;
  99. begin
  100.   ShowMessage('MostrarDialogo');
  101. end;
  102.  
  103. { TMostrarInput }
  104.  
  105. constructor TMostrarInput.Create(AMostrar: TMostrar);
  106. begin
  107.   inherited Create;
  108.   FMostrar := AMostrar;
  109. end;
  110.  
  111. procedure TMostrarInput.MostrarInput;
  112. begin
  113.   ShowMessage('MostrarInput');
  114.   ShowMessage(FMostrar.MiPropiedad);
  115. end;
  116.  
  117. { TForm2 }
  118.  
  119. procedure TForm2.Button1Click(Sender: TObject);
  120. begin
  121.   FMostrar := TMostrar.Create;
  122.   FMostrar.FMostrarMensaje := TMostrarMensaje.Create(FMostrar);
  123.   FMostrar.FMostrarDialogo := TMostrarDialogo.Create(FMostrar);
  124.   FMostrar.FMostrarInput := TMostrarInput.Create(FMostrar);
  125.   FMostrar.MostrarInput.MostrarInput;
  126. end;
  127.  
  128. { TMostrar }
  129.  
  130. function TMostrar.GetMiPropiedad: string;
  131. begin
  132.   Result := 'Soy una propiedad de la clase principal';
  133. end;
  134.  
  135. end.


Cabe aclarar que esto se me ocurrió de plano, existen varias maneras lo único que en estos momentos no puedo sentarme a realizar uno mejor ejemplo.  Pero de que existe el modo lo hay.

Como podrás notar el lenguaje es bastante rico. ;) (y)

Gracias anticipadas.
  • 0

#25 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 01 octubre 2010 - 02:38

Hola,
Vaya que hemos hecho un desmadre de hilo.

Marc, yo también me pregunto el porqué de los controles y componentes de un form están en un alcance público. No debería serlo, desde el punto de vista de los principios de las Variaciones Protegidas, del Encapsulamiento y el principio Abierto/Cerrado. Pero bueno, así está definido. Algún motivo tendrán... nunca me preocupé demasiado en ese sentido, ni me molesté en averiguarlo. Para mi, esa es la capa de interfaz y mientras la lógica no esté allí el peligro es menor.
Esto es otro tema que quizá merezca su propio debate... No por ello lanzaré a maldecir y desechar algo que funciona y trabaja bien.

Lo que comentas de la posibilidad de redefinir los métodos sin necesidad de añadir las cláusulas, es quizá gusto de uno. A mi no me molesta en poner unas cláusulas.
Quizá algunos ven bien la posibilidad que comentas, a otros les resultará indiferente. Esto de todas formas es una nimiedad dentro de las ventajas que te ofrece Delphi. ¿No crees?

Habrá gustos y gustos, y también opiniones diferentes.
Yo sólo exponía mis ideas, y mi modo de entender al tema. Tu también.
Quizá con el tiempo cambie de opinión, tal vez no...
No vengo con la intención de ofrecer la verdad porque no la tengo. La diferencia que noto, y con todo respeto amigo, es que dije y aclaré perfectamente... "aún me cuestiono". Que es totalmente distinta tu postura que es más cerrada, directamente afirmas que estás totalmente en contra, no quieres pensar en ello y estás absolutamente convencido de que es algo que no sirve para nada.

Si me lo cuestiono es porque ando evaluando que tanto de si y que tanto de no. Si están será por algo... por algo lo han pensado otros... por algo lleva 2 décadas esta teoría... por algo... Por esos "algo" es que salen nuevas cosas.

Mientras tanto, aún mantengo mi posición... aunque dejo la posibilidad de examinarlo en un futuro.

Con todo respeto estás siendo bastante exabrupto. Esto no es una guerra, es un intercambio de ideas.

Lo de los ornitorrincos quizá pueda ser discutible, Tu dices que pueden haber muchos ornitorrincos... yo me lo pongo en duda. Hay demasiados animales en este mundo... y son relativamente pocos los que traen confusión del tipo "¿Y adonde lo metemos?" comparados a los que vemos habitualmente y ya conocemos. Si la herencia simple funciona bien para la gran mayoría de las especies... entonces es natural seguir la misma idea. Cuando encuentre más ornitorrincos me preguntaré si es viable.
Y no es que no quiera verlos, o evite verlos... a mi modo de ver y pensar, y a como estoy acostumbrado a analizar mis diseños son pocas las veces.
Repito, si ese "algo" múltiple estuviera más presente que un "algo" simple... ya se hubiera cambiado el enfoque.

Cada persona tiene su propia manera de pensar y analizar... a mi hasta el momento pensar de forma simple me sirve para la gran mayoría de las veces. Quizá luego, cuando mis desarrollos crezcan y maduren empiece a verlos.

Pero por ahora, a mi y a muchos, los ornitorrincos no son más que algunos casos aislados y excepciones de la naturaleza a la que estamos acostumbrados a ver.


Saludos,
  • 0

#26 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 04 octubre 2010 - 11:04

Hola.

Se me ocurre de plano para satisfacer tus necesidades (por decirle de algún modo) lo siguiente:


delphi
  1. type
  2.  
  3.   TMostrar = class; 
  4.  
  5. //************* 
  6.   IMostrarMensaje = interface   
  7.     ['{C71A12B4-CBFB-4882-A54B-DF012EC77C84}']   
  8.     procedure MostrarMensaje; 
  9.   end;
  10.  
  11.   TMostrarMensaje = class(TInterfacedObject, IMostrarMensaje)   
  12.     FMostrar : TMostrar;   
  13.     procedure MostrarMensaje; virtual;   
  14.     constructor Create(AMostrar : TMostrar); 
  15.   end; 
  16.  
  17. //*******************
  18.  
  19. ( ... )


Cabe aclarar que esto se me ocurrió de plano, existen varias maneras lo único que en estos momentos no puedo sentarme a realizar uno mejor ejemplo.  Pero de que existe el modo lo hay.



Rolphy, me esperaba algo parecido. Ahora esas clases sueltas "conocen" a la clase que las invoca, por lo tanto pueden acceder a sus propiedades, métodos ... etc. Son clases amigas. Pero fíjate que ahí el concepto de herencia múltiple salta por los aires, puesto que dichas "clases auxiliares", pensadas para ahorrar código y poder ser empleadas desde varias "clases principales" (utilizo estos términos para que nos entendamos) pasan a poder servir sólo para una de ellas, en el ejemplo sólo a TMostrar.

¿Qué sucede si quiero implementar una clase que no herede de TMostrar pero que sí declare una interfaz común? Pues que esas clases auxiliares no sabrán cómo actuar para dicho caso. Tampoco se trata de pasarles en el constructor tantos parámetros como posibles clases principales puedan usarlas.

Las interfaces permiten heredar declaraciones, no implementaciones, con lo cual nos vemos obligados a repetir éstas en cada clase, aunque con algunos trucos para ahorrar código que sirven para casos aislados. Alabo tu perseverancia en buscar fórmulas válidas, aunque me temo que no las hay. Aunque seguiré atento a la nueva versión  ;)


Delphius, entiendo la idea general de tu explicación (aunque tendré que releerlo varias veces para empezar a enterarme de esos gráficos que pones  *-)), como ha dicho Marc son dificultades que atañen al compilador, aunque creo que sí es bueno que el programador final sepa algo de todo esto para al menos conocer en qué tipo de berenjenal se puede meter programando herencia múltiple si no lleva cuidado.


el mayor de ellos probablemente sea la sección published, ¿ que pinta esta sección en lugar de poner dentro de private los controles del formulario ?, ¿ a que lumbreras se le ocurrió hacer públicos todos los controles en un formulario para que desde otro formulario nos puedan destrozar a su gusto toda la lógica que implementemos en el primero ?.


Esto al menos se puede corregir "a mano" en aquellos formularios donde queramos ser más estrictos. ¿Sería mejor que estuvieran como private desde un principio? Pues sí, pero que alguien te los pueda destrozar desde otro formulario, ¿quién, un duende?  :) Entiendo que sea un fallo de diseño pero dudo que nadie salvo tú por error pueda destrozar nada. Siendo puristas, cualquier objeto creado dentro de una clase y que sea declarado como public o published puede ser accedido peligrosamente desde fuera y causar un estropicio. Por ejemplo, si hago Font.Free sobre la propiedad Font del formulario, ya estoy dando pie a un Access Violation de narices, ¿alguna solución al respecto? Desde el momento en que se tiene acceso a un formulario, se puede acceder a sus controles mediante su propiedad Controls, estén éstos declarados como public o como private, y causar estropicios en la lógica de dicho formulario. Evitar estas puertas traseras lo veo practicamente imposible.

Decía que esto me saca de quicio porqué cada vez que tengo que definir un método como dynamic o virtual, nunca olvido mentar las madres de los empleados de Embarcadero. Yo debería poder sobreescribir cualquier método sin necesidad de haberlo declarado previamente dinámico o virtual, y no solo me obligan a declararlo sino que encima me veo forzado a tomar una
decisión que compete únicamente al compilador.


Aparte de la comodidad que le pueda dar al compilador a la hora de crear la VMT, a mí está restricción me parece lógica y, me ha pasado programando componentes, hay muchos casos donde quiero decidir qué métodos no deben poderse sobreescribir para no dejar que se altere cierto comportamiento básico definido en la clase maestra. Aunque al revés, lo que me ha causado malhumor es al querer sobreescribir un método de una clase de la VCL o de terceros que otro programador no declaró como virtual  :@, aunque tuviera sus razones.


Saludos
  • 0

#27 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 04 octubre 2010 - 11:55

Con todo respeto estás siendo bastante exabrupto. Esto no es una guerra, es un intercambio de ideas.


Mas bien estaré siendo demasiado abrupto :), ¿ no crees ?. Exabruptos son eso que no podría evitar soltar si estuvieramos hablando de, por ejemplo, las tecnologías actuales para crear aplicaciones Web. A ese debate mejor no me invitéis (cuando veo ese código PHP mezclado de mala manera dentro del HTML y todo bien aliñado con Javascript, Flash y demás engendros, me pongo enfermo). XD

Cada persona tiene su propia manera de pensar y analizar... a mi hasta el momento pensar de forma simple me sirve para la gran mayoría de las veces. Quizá luego, cuando mis desarrollos crezcan y maduren empiece a verlos.


Si te refieres a que uno normalmente ya está bien servido con la herencia simple, estoy totalmente de acuerdo.

Respecto a las interfaces, no soy capaz de imaginar ningún caso en que sean aconsejables para la programación de gestión que hago habitualmente, en cambio la herencia múltiple sí que bastantes veces me iría bien (es muy fácil inventar situaciones en las que aprovecharias heredar de varias clases, como por ejemplo hice en el mensaje anterior, no toda la herencia múltiple se basa en buscar ornitorrincos :)).

NOTA: Aunque como Delphi no da ninguna facilidad para tratar como clases a las bases de datos (ORM), entonces la necesidad tampoco es acuciante, no utilizo la POO tanto como se debería, ni creo que haya más de un puñado de personas que lo hagan, porqué el gran esfuerzo que se precisa en Delphi para tener una abstracción en clases de tu base de datos hace que no valga la pena.
  • 0

#28 egostar

egostar

    missing my father, I love my mother.

  • Administrador
  • 14.469 mensajes
  • LocationMéxico

Escrito 04 octubre 2010 - 12:03

Hola

A mi me ha parecido por demás interesante todos los puntos de vista y yo concuerdo con Marc en que no todo es compatible en un proyecto, por ejemplo, yo tengo programas que ni siquiera requieren de base de datos y para almacenar unos cuantos datos utilizo Paradox y me funciona de maravilla a pesar de que muchos la han enviado al quinto infierno.

Sin embargo, también soy capáz de reconocer (y aquí estoy de acuerdo con los demás :)) que Firebird. MySQL, MS-SQL Server, etc, etc son por mucho mejores que Paradox en aquellas circunstancias donde mi querido Paradox no da el ancho.

Yo suelo utilizar lo que me es necesario para lograr con la menor dificultad mi objetivo. Pero como siempre digo

DEPENDE, DEPENDE, DEPENDE ;)

Salud OS
  • 0

#29 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 05 octubre 2010 - 04:53

Hola Andrés.

Aparte de la comodidad que le pueda dar al compilador a la hora de crear la VMT, a mí está restricción me parece lógica y, me ha pasado programando componentes, hay muchos casos donde quiero decidir qué métodos no deben poderse sobreescribir para no dejar que se altere cierto comportamiento básico definido en la clase maestra. Aunque al revés, lo que me ha causado malhumor es al querer sobreescribir un método de una clase de la VCL o de terceros que otro programador no declaró como virtual  :@, aunque tuviera sus razones.


¿ No crees que esto debería ir exactamente al revés ?. Cuando defines una clase nunca puedes preveer todos los usos futuros que le podrás dar, y por tanto no podrás preveer que métdos vas a necesitar sobreescribir.

Así que si por alguna razón excepcional quieres asegurarte de que un método no sea reescribible, lo lógico es que declares de esta forma ese método (y no al revés, teniendo que declarar reescribibles, como hacemos ahora, todos los que podamos necesitar). Debería existir una sección llamada locked, o algún mecanismo por el estilo.

Ya sé que cuando nos encontramos con la necesidad, tampoco cuesta mucho ir a la clase original y cambiar su declaración a dynamic, pero el hecho es que eso no debería ser necesario, y como apuntas, a veces te encuentras con que querrás reescribir un método de una clase que no has escrito tú, y eso ya es una inconveniencia muy importante (no siempre es posible, y cuando lo sea cada vez que instales DevExpress en un nuevo equipo, por poner un ejplo, tendrás que recordar de volver a cambiar esa clase).

El caso es que no aparece que las directivas dynamic y virtual esten hechas para poder bloquear métodos que no queramos que sean reescribibles, puesto que en ese caso han hecho un trabajo pésimo, la declaración tenía que ser al revés (todo reescribible, excepto lo que declares explicitamente como bloqueado). Esas directivas parece que existen solo por necesidades del compilador, y por eso las encuentro tan molestas.

Saludos
  • 0

#30 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 05 octubre 2010 - 08:30

Hola Marc.

¿ No crees que esto debería ir exactamente al revés ?. Cuando defines una clase nunca puedes preveer todos los usos futuros que le podrás dar, y por tanto no podrás preveer que métdos vas a necesitar sobreescribir.

Así que si por alguna razón excepcional quieres asegurarte de que un método no sea reescribible, lo lógico es que declares de esta forma ese método (y no al revés, teniendo que declarar reescribibles, como hacemos ahora, todos los que podamos necesitar). Debería existir una sección llamada locked, o algún mecanismo por el estilo.

(...)


¡Ah pillín!, lo que a tí te mosquea es que te hagan escribir más de la cuenta,  :smiley:. Aunque bueno, habría que ver según qué casos, en algunas clases quizás harían falta menos métodos sobreescribibles y estarían plagadas de "locked"s  :D. El caso es que de una forma u otra, parece conveniente que esa decisión quede en manos del programador. Y puestos a definir un comportamiento por defecto, seguramente en Borland optaron por aquel que, por defecto, no sobrecargaba la VMT (esto ya son motivos del compilador).

Saludos
  • 0

#31 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 05 octubre 2010 - 08:45

Hola.

Esta mañana, antes de levantarme  |-), me ha venido a la mente este asunto :D y mientras le daba vueltas me he dado cuenta de algunos errores míos de apreciación que quiero rectificar. En el tema de la repetición de código, que parece ser uno de los talones de Aquiles de las interfaces, ahora veo que el planteamiento de Rolphy es acertado, con algún matiz. Me he precipitado hablando de un tema del que no me había documentado suficientemente, así que castigado cara a la pared  :.

El quid de la cuestión está, como bien apuntó Rolphy unos cuantos posts más arriba, en la cláusula implements, que permite delegar el funcionamiento de una interface a una clase que ya implemente dicha interface. Yo argumenté el problema de que dicha clase no podía conocer las propiedades o métodos de la clase principal (la que hace uso de ella, para entendernos), pero en realidad no tiene por qué ser así sino al revés, es la clase principal quien sí conoce a la clase intermedia (la que se encarga de implementar la interface), sus propiedades y sus métodos. De forma parecida es como sucede en la herencia múltiple, donde la clase derivada conoce a su ancestra, y no al revés salvo en los elementos comunes. En ese sentido, aunque con más código, se funciona de forma bastante parecida.

Y para los casos donde se necesita reimplementar algún método concreto de la interface, Delphi nos permite asignar dicha implementación a un método específico de la clase principal, sin necesidad de derivar adrede una clase de la clase intermedia, así que se solucionan muchos aspectos relacionados con la herencia y la redefinición de métodos para aquellos casos donde se quiera obtener un comportamiento diferente al implementado por la clase intermedia. También se puede crear una clase derivada de esa clase intermedia, como el ejemplo que ha puesto Rolphy (TNuevaMostrarMensaje), pero resulta algo farragoso y creo que no sería estrictamente necesario para muchos casos.

Copio y pego el ejemplo que trae la ayuda de Delphi:


delphi
  1. type
  2.  
  3.   IMyInterface = interface
  4.     procedure P1;
  5.     procedure P2;
  6.   end;
  7.  
  8.   TMyImplClass = class
  9.     procedure P1;
  10.     procedure P2;
  11.   end;
  12.  
  13.   TMyClass = class(TInterfacedObject, IMyInterface)
  14.     FMyImplClass: TMyImplClass;
  15.     property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
  16.     procedure IMyInterface.P1 = MyP1;  // ejemplo de método redefinido en la clase principal
  17.     procedure MyP1;
  18.   end;



Respecto a la herencia múltiple....... siempre puedes simularla poniendo una property de la clase que necesites tener herencia múltiple.


Como indica Cadetill, esto de instanciar clases que cumplen determinados objetivos se puede hacer con interfaces o sin ellas. Si usamos interfaces que acceden a clases mediante el uso de  implements, los métodos quedan adheridos a nuestra clases como propios -ClasePrincipal.Metodo1- (bueno, no exactamente, sólo si los accedemos como interface), siendo el resultado final más elegante, y si evitamos las interfaces, podemos crear una instancia que realice tal misión y hacerla pública para que se puedan usar sus métodos de forma calificada -ClasePrincipal.MiClaseExtra.Metodo1-.

Aunque no se alcance el paradigma de la herencia múltiple, o se intente con mayor número de tipos, juntando clases e interfaces, ahora veo que se pueden conseguir cosas interesantes usando las interfaces, principalmente cuando se quiera ocultar cierta complejidad al programador que las vaya a usar en último término y que no tiene por qué conocer esas clases intermedias. El uso es más engorroso, eso sí, y hoy por hoy no edificaría toda una jerarquía basada en este recurso, pero quizás sí me anime a programar algo al respecto en el futuro.

Saludos (y gracias por tan valiosas explicaciones como aquí se han dado en uno u otro sentido)
  • 0

#32 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 05 octubre 2010 - 12:16

Hola.

Y para los casos donde se necesita reimplementar algún método concreto de la interface, Delphi nos permite asignar dicha implementación a un método específico de la clase principal, sin necesidad de derivar adrede una clase de la clase intermedia, así que se solucionan muchos aspectos relacionados con la herencia y la redefinición de métodos para aquellos casos donde se quiera obtener un comportamiento diferente al implementado por la clase intermedia. También se puede crear una clase derivada de esa clase intermedia, como el ejemplo que ha puesto Rolphy (TNuevaMostrarMensaje), pero resulta algo farragoso y creo que no sería estrictamente necesario para muchos casos.

Copio y pego el ejemplo que trae la ayuda de Delphi:


delphi
  1. type
  2.  
  3.   IMyInterface = interface
  4.     procedure P1;
  5.     procedure P2;
  6.   end;
  7.  
  8.   TMyImplClass = class
  9.     procedure P1;
  10.     procedure P2;
  11.   end;
  12.  
  13.   TMyClass = class(TInterfacedObject, IMyInterface)
  14.     FMyImplClass: TMyImplClass;
  15.     property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
  16.     procedure IMyInterface.P1 = MyP1;  // ejemplo de método redefinido en la clase principal
  17.     procedure MyP1;
  18.   end;



Saludos.

Compañero Andres1569 ese método que explican en la ayuda de Delphi lleva por nombre de Resolución de Métodos; así puedes googlear un poco.  (y)

Me alegra que hayas podido ver un poco más allá sobre las bondades del recurso en vez de sus posibles limitaciones.  Como te había mencionado ese ejemplo fue al vuelo que lo hice y veo que lo pudiste extender de una manera elegante.

Me satisface mucho el haberte ayudado a entender mejor este recurso que es una herramienta más de nuestro diario programar.... ;)
  • 0

#33 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 05 octubre 2010 - 12:51

Hola.

El quid de la cuestión está, como bien apuntó Rolphy unos cuantos posts más arriba, en la cláusula implements, que permite delegar el funcionamiento de una interface a una clase que ya implemente dicha interface. Yo argumenté el problema de que dicha clase no podía conocer las propiedades o métodos de la clase principal (la que hace uso de ella, para entendernos), pero en realidad no tiene por qué ser así sino al revés, es la clase principal quien sí conoce a la clase intermedia (la que se encarga de implementar la interface), sus propiedades y sus métodos. De forma parecida es como sucede en la herencia múltiple, donde la clase derivada conoce a su ancestra, y no al revés salvo en los elementos comunes. En ese sentido, aunque con más código, se funciona de forma bastante parecida.


No entiendo esta parte. Entonces, ¿ cuando asignas la interfaz a una nueva clase, hereda la implementación de una clase intermedia anterior (en el ejemplo : TMyImplClass) ?.

La verdad es que esta declaración me parece de lo más liosa (especialmente la propiedad MyImplClass) :



delphi
  1.   TMyClass = class(TInterfacedObject, IMyInterface)
  2.     FMyImplClass: TMyImplClass;
  3.     property MyImplClass: TMyImplClass read FMyImplClass implements IMyInterface;
  4.     procedure IMyInterface.P1 = MyP1;  // ejemplo de método redefinido en la clase principal
  5.     procedure MyP1;
  6.   end;



ACTUALIZACION: Bueno, ya entiendo el funcionamiento, pero la sintaxis me sigue pareciendo de lo más liosa y antinatural, tanto para declarar que vas a usar la implementación en la clase intermedia, como para sobreescribir un método (aparte de que con esta forma de sobreescribir métodos, supongo que en su código no puedes usar el inherited para especificar que quieres ejecutar el código original, para después añadir el específico a continuación). En fin, para la mayoría de las necesidades de herencia múltiple puede servir perfectamente, pero preferiria la simplicidad de la sintaxis y de clases de una verdadera herencia múltiple.
  • 0

#34 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 05 octubre 2010 - 01:07

¡Ah pillín!, lo que a tí te mosquea es que te hagan escribir más de la cuenta,  :smiley:. Aunque bueno, habría que ver según qué casos, en algunas clases quizás harían falta menos métodos sobreescribibles y estarían plagadas de "locked"s  :D. El caso es que de una forma u otra, parece conveniente que esa decisión quede en manos del programador. Y puestos a definir un comportamiento por defecto, seguramente en Borland optaron por aquel que, por defecto, no sobrecargaba la VMT (esto ya son motivos del compilador).


Sinceramente dudo enormemente de que ese fuese el motivo de Borland, y me gustaría conocer algún ejemplo que justifique el bloquear que un método no sea sobreescribible (más aún toda una clase). Yo nunca he tenido la urgencia de bloquear un método al escribir una clase.

Se supone que los programadores somos todos adultos y al sobreescribir un método lo primero que escribimos es un inherited para que haga todo lo que se hacía anteriormente, y solo a continuación ponemos el código personalizado para la nueva clase. En contadas ocasiones no querremos que se ejecute el código anterior, y de nuevo se supone que el programador de la nueva clase es un adulto responsable, que sabe lo que puede estar estropeando si deshabilita la ejecución del código de la clase original.

El responsable del correcto funcionamiento de la nueva clase, es su programador, y no el programadar de las clases en que se ha basado.
  • 0

#35 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 07 octubre 2010 - 10:38

Sinceramente dudo enormemente de que ese fuese el motivo de Borland, y me gustaría conocer algún ejemplo que justifique el bloquear que un método no sea sobreescribible (más aún toda una clase). Yo nunca he tenido la urgencia de bloquear un método al escribir una clase.

Se supone que los programadores somos todos adultos y al sobreescribir un método lo primero que escribimos es un inherited para que haga todo lo que se hacía anteriormente, y solo a continuación ponemos el código personalizado para la nueva clase. En contadas ocasiones no querremos que se ejecute el código anterior, y de nuevo se supone que el programador de la nueva clase es un adulto responsable, que sabe lo que puede estar estropeando si deshabilita la ejecución del código de la clase original.

El responsable del correcto funcionamiento de la nueva clase, es su programador, y no el programadar de las clases en que se ha basado.


Hola Marc, llevo tantos años con Pascal / Delphi y su forma de declarar los métodos virtuales, que me ha costado entender tu planteamiento, y tras darle algunas vueltas creo que es bastante lógico lo que dices. Incluso diría que el programador de una clase descendiente es muy libre de saltarse el funcionamiento heredado de su ancestra, con el fin que él crea conveniente. Lo cierto es que, tal como está ahora, Delphi deja en manos del programador de una clase el decidir si declarar todos sus métodos como virtuales o no (aunque conlleve el engorro de escribir esa coletilla final para cada uno), para ser redefinidas o no por sus descendientes. Digamos que delega esa decisión en el padre de cada criatura.

Lo que puede motivar a un programador a no declarar un método como virtual puede ser una medida de protección ante posibles errores no intencionados en sus descendientes. Hablas de adultos responsables, pero los programadores, aunque adultos y responsables, son por definición humanos (*), y como tales cometen errores, a veces simplemente por desconocimiento. Tú mismo, en un post anterior, te quejabas de la posibilidad de que alguien pudiera destrozarte un formulario accediendo directamente a un control del mismo y clamabas porque fuera private (mayor restricción todavía que la "no virtualidad de un método"), y no creo que fuera porque no confíes en que sea un adulto responsable, sino por no dejar abierta esa posibilidad al error. Esto me recuerda haber leído a gente que incluso abogaba por no poner ningún método como private (salvo los GetXXX y SetXXX de las propiedades) para no prohibir su posible uso en clases descendientes, y yo opino que este tipo de restricción lo que busca es precisamente facilitar la vida al programador que venga detrás, ocultando un aluvión de métodos que no va a tener que usar para nada. Claro, me dirás, que esto es lo que piensa quien programó la clase ancestra y al programador que viene detrás le puede crear una seria limitación en algunos casos: cierto en ocasiones. Se me ocurre que quizás también tenga sentido en equipos de trabajo donde el jefe de proyecto quiera "tener atado" lo que pueden hacer o no las clases descendientes, limitando daños colaterales, por mucho que tenga por serios y responsables a sus programadores. Y no hablamos solamente de olvidarse o no de llamar a inherited, sino de escribir código inapropiado para el fin para el que fue concebido dicho método.

Aunque no sea un argumento de peso, pues depende de la costumbre de cada uno, muchas veces al enfrentarme a una clase de un tercero, por norma general las de la VCL, he visto en los métodos virtuales una indicación de por dónde debía hincarle el diente para implementar novedades en mi clase descendiente, viendo a los métodos estáticos como de sólo usar. Esto en la mayoría de ocasiones me facilita las cosas, pues da pistas de cómo funciona esa clase ancestra y lo que propone a sus derivadas. Pero, también más de una vez, me he topado con clases con métodos desgraciadamente estáticos, o peor aún, con variables privadas sin su consiguiente propiedad que las haga visibles y que me han hecho tener que recurrir a trucos malabares o repetir código de la clase ancestra para conseguir mi objetivo. ¿Achacable a Delphi o al programador que las hizo?

En cuanto a los motivos reales de Borland para dejar esto así, también los ignoro, pero es un hecho que las VMT ocupan menos espacio cuanto menos métodos virtuales haya.

ACTUALIZACION: ... (aparte de que con esta forma de sobreescribir métodos, supongo que en su código no puedes usar el inherited para especificar que quieres ejecutar el código original, para después añadir el específico a continuación). ...


Bueno, no lo he programado nunca pero supongo que para estos casos en que se hace uso de una clase intermedia, si queremos sobrescribir un método concreto, tendremos que sustituir el inherited por una llamada al método de la clase intermedia que implementaba dicho método. Por supuesto, conviene que dicha clase intermedia publique la mayor parte de propiedades y métodos que puedan hacernos falta, obligadamente si queremos que dicha clase pueda ser empleada desde otras units.

Saludos.

(*) Bueno ..., esto cambiará con los años  :p
  • 0

#36 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 07 octubre 2010 - 12:20

Hola Andrés.

Hola Marc, llevo tantos años con Pascal / Delphi y su forma de declarar los métodos virtuales, que me ha costado entender tu planteamiento, y tras darle algunas vueltas creo que es bastante lógico lo que dices. Incluso diría que el programador de una clase descendiente es muy libre de saltarse el funcionamiento heredado de su ancestra, con el fin que él crea conveniente. Lo cierto es que, tal como está ahora, Delphi deja en manos del programador de una clase el decidir si declarar todos sus métodos como virtuales o no (aunque conlleve el engorro de escribir esa coletilla final para cada uno), para ser redefinidas o no por sus descendientes. Digamos que delega esa decisión en el padre de cada criatura.


Si no te digo que no. Solo que creo que el comportamiento predeterminado tendría que ser al revés, que la coletilla solo la tendríamos que poner si excepcionalmente queremos bloquear algo.

Lo que puede motivar a un programador a no declarar un método como virtual puede ser una medida de protección ante posibles errores no intencionados en sus descendientes. Hablas de adultos responsables, pero los programadores, aunque adultos y responsables, son por definición humanos (*), y como tales cometen errores, a veces simplemente por desconocimiento. Tú mismo, en un post anterior, te quejabas de la posibilidad de que alguien pudiera destrozarte un formulario accediendo directamente a un control del mismo y clamabas porque fuera private (mayor restricción todavía que la "no virtualidad de un método"), y no creo que fuera porque no confíes en que sea un adulto responsable, sino por no dejar abierta esa posibilidad al error. Esto me recuerda haber leído a gente que incluso abogaba por no poner ningún método como private (salvo los GetXXX y SetXXX de las propiedades) para no prohibir su posible uso en clases descendientes, y yo opino que este tipo de restricción lo que busca es precisamente facilitar la vida al programador que venga detrás, ocultando un aluvión de métodos que no va a tener que usar para nada. Claro, me dirás, que esto es lo que piensa quien programó la clase ancestra y al programador que viene detrás le puede crear una seria limitación en algunos casos: cierto en ocasiones. Se me ocurre que quizás también tenga sentido en equipos de trabajo donde el jefe de proyecto quiera "tener atado" lo que pueden hacer o no las clases descendientes, limitando daños colaterales, por mucho que tenga por serios y responsables a sus programadores. Y no hablamos solamente de olvidarse o no de llamar a inherited, sino de escribir código inapropiado para el fin para el que fue concebido dicho método.


Es que no es lo mismo, ni lo del formulario ni lo del private :)

Yo no digo que todo en una clase tendría que estar abierto, como están por defecto los formularios. Para eso tenemos las secciones private/protected en las clases, para esconder toda la complejidad interna que no quieras que sea visible desde el exterior (y en los formularios debería ser igual, los componentes deberían ir a una sección private o protected).

Estaba empezando a escribir que no es lo mismo lo que comentas del private, y pensándolo bien he borrado lo que llevaba escrito. Tienes razón en que es lo mismo, o muy parecido, y por eso entiendo perfectamente a las personas que piden eso. Para mi también se podrían fusionar perfectamente las secciones private y protected en una sola de protected. Lo importante es ocultar esa complejidad a las clases externas, pero a las clases heredadas yo tampoco veo ninguna necesidad de bloquear el acceso a los métodos y variables privados.

Pero entiendo que hay gente paranoica (especialmente cuando hablamos de equipos de trabajo como los que comentas, con jefes de proyecto ineptos que son más un estorbo que una ayuda en la finalización del proyecto). Entiendo de que a pesar de lo inútiles y molestos que son estos bloqueos, Borland los introduzca para satisfacer a estos arquitectos de software que nunca se han ensuciado las manos programando, pero que al fin y al cabo son quienes aprueban las compras de las herramientas de desarrollo.

Pero al menos que el bloqueo sea la excepción y no la norma (como ocurre con los métodos estáticos y dinámicos).

Aunque no sea un argumento de peso, pues depende de la costumbre de cada uno, muchas veces al enfrentarme a una clase de un tercero, por norma general las de la VCL, he visto en los métodos virtuales una indicación de por dónde debía hincarle el diente para implementar novedades en mi clase descendiente, viendo a los métodos estáticos como de sólo usar. Esto en la mayoría de ocasiones me facilita las cosas, pues da pistas de cómo funciona esa clase ancestra y lo que propone a sus derivadas. Pero, también más de una vez, me he topado con clases con métodos desgraciadamente estáticos, o peor aún, con variables privadas sin su consiguiente propiedad que las haga visibles y que me han hecho tener que recurrir a trucos malabares o repetir código de la clase ancestra para conseguir mi objetivo. ¿Achacable a Delphi o al programador que las hizo?


Mitad y mitad, en mi opinión. Achacable a Delphi que el programador no haya declarado virtual el método (ya que como la declaración predeterminada es la estática, lo más fácil es que por culpa de eso queden como estáticos métodos que más adelante lamentas no encontrarlos como virtuales). Y achacable al programador el haber declarado privada una variable que puedas necesitar acceder en clases heredadas. En este caso el programador tenía que haberla puesto en protected.

En cuanto a los motivos reales de Borland para dejar esto así, también los ignoro, pero es un hecho que las VMT ocupan menos espacio cuanto menos métodos virtuales haya.


Sí, pero es que eso es un problema estrictamente del compilador, no nos lo deberían delegar a nosotros.

Sin calentarme mucho la cabeza una solución evidente es que el compilador hiciera dos pasadas en el código. Inicialmente considerara todos los métodos como estáticos, y cuando ve que en el proyecto se sobreescribe un método, lo considera entonces como virtual. Una vez terminada esa pasada, ya sabe como tiene que compilar cada método y ya puede hacer la compilación final.

Naturalmente eso es más complicada para ellos que delegarlo en nosotros. Ya que resulta que una clase de, póngamos las FastReports, no generará el mismo binario en un proyecto que en otro, ya que dependerá de lo que sobreescribas en cada proyecto. Así que cada proyecto tendría que tener su propia compilación de todas las clases usada. Sinceramente yo no veo ningún problema con eso, aparte de que seguro que la gente de Borland podría también llegar a soluciones más elegantes sin necesidad de recompilarlo todo en cada proyecto.

En fin, que ese es un problema del compilador, no nuestro.

Bueno, no lo he programado nunca pero supongo que para estos casos en que se hace uso de una clase intermedia, si queremos sobrescribir un método concreto, tendremos que sustituir el inherited por una llamada al método de la clase intermedia que implementaba dicho método. Por supuesto, conviene que dicha clase intermedia publique la mayor parte de propiedades y métodos que puedan hacernos falta, obligadamente si queremos que dicha clase pueda ser empleada desde otras units.


Bueno, no veo como en la implementación se referenciará al método original de la clase intermedia para llamarlo. Pero es muy posible que haya alguna forma (¿ quizás con una cast de Self a la clase intermedia ?), después de todo tampoco conocía esta forma de delegar la implementación de una interface. Y hay que admitir que con esto las interfaces ganan mucha utilidad al poder pseudo-heredar una implementación además de una estructura.

De nuevo, al igual que con los métodos estáticos y dinámicos, ya no niego la válidez de la implementación de Delphi, pero sí que me parece liosa y bastante menos elegante que lo que resultaría para hacer lo mismo con herencia múltiple (sin clases intermedias, sin tener que hacer cosas exóticas para sobreescribir un método, ni para llamar a su implementación original, ...)



delphi
  1. type
  2.  
  3.   IMyInterface = class
  4.     procedure P1;
  5.     procedure P2;
  6.   end;
  7.  
  8.   TMyClass = class(TForm, IMyInterface)
  9.     procedure IMyInterface.P1; override;
  10.   end;


  • 0

#37 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.301 mensajes
  • LocationArgentina

Escrito 07 octubre 2010 - 06:04

Marc, con todo respeto me parece que lo que ideal es que el programador se adapte a la herramienta y no que la herramienta se adapte al programador.

Respecto al tema de los métodos estáticos, dinámicos y virtuales y el porqué así está hecho y definido el lenguaje se debe a que quien elabora una clase necesita tener toda la flexibilidad posible. Se supone que un desarrollador de una clase debe poder elegir como diseñarla, lo que propones es limitarlo a adoptar un diseño. Va en contra... la herramienta debe permitir la flexibilidad necesaria para que el programador pueda adaptarse... y no que genere rechazo.

Respecto al tema de que en ocasiones uno se ve molesto o perjudicado por clases de terceros, eso es cosa de ahora y siempre. El programador original quizá no tuvo en cuenta eso, o bien no lo consideró necesario.
Una posible vía es la de disponer de una Indirección, o un Adaptador (Adapter, en inglés) para que se comunique con esta clase caótica y directamente nos independizamos de ésta al trabajar con la indirecta. Esto lo comenté en el post que se borró.

Los diseños de las clases, y su visibilidad evolucionan. Pareciera que te olvidas de que la teoría OO es un modelo vivo, que se adapta y cambia según se determina como se va relacionando... Tras un tiempo uno puede verse obligado a alterar una clase... y es por situaciones como éstas en donde está presente la reingeniería. Se vuelve a regenerar... ¿O no te olvidas de que la misma VCL se fué adaptando tras cada versión?
Tus palabras dan a entender que las clases están muertas y lo estarán por siempre y no puedes hacer nada más que usarlas como están.
Te recuerdo además, que si un método o atributo es privado puedes hacerlo público en una clase descendiente.

Si entiendo y comparto en que quizá sea un error el que los controles de un form deban estar en private o en protected.

Tus conceptos de que debe estar todo cerrado y luego poder abrirlo va en contra de que lo que se conoce como el principio abierto/cerrado: una clase se diseña para ser lo suficientemente cerrada para ocultar los elementos innecesarios o que lo son de importancia para el usuario o cliente de la clase, y a la vez lo suficientemente abierta para ofrecer una interfaz adecuada para comprender su uso y su propósito.
El que por defecto todo o en su buena parte sea estático limita demasiado a la clase.

Yo como desarrollador debo tener la plena libertad de poder indicar al compilador como lo quiero. Luego el me dirá en base a sus posibilidades como puedo llevar a cabo mis diseños. Pero no que me corte demasiado, si se supone que nuestros diseños con el tiempo se verán afectados debo tener las herramientas necesarias.

La idea de fusionar protected y private me parece errada. Private está para ocultar, protected está para ofrecer la suficiente seguridad de poder brindar un mejor conocimiento de una clase para poder extenderla y a su vez los medios para ofrecer de seguridad en cuanto a su uso y evitar estar publicando demasiado.

Saludos,
  • 0

#38 Marc

Marc

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.484 mensajes
  • LocationMallorca

Escrito 08 octubre 2010 - 02:50

Hola Delphius.

Marc, con todo respeto me parece que lo que ideal es que el programador se adapte a la herramienta y no que la herramienta se adapte al programador.

Respecto al tema de los métodos estáticos, dinámicos y virtuales y el porqué así está hecho y definido el lenguaje se debe a que quien elabora una clase necesita tener toda la flexibilidad posible. Se supone que un desarrollador de una clase debe poder elegir como diseñarla, lo que propones es limitarlo a adoptar un diseño. Va en contra... la herramienta debe permitir la flexibilidad necesaria para que el programador pueda adaptarse... y no que genere rechazo.


No lo comparto en absoluto. Creo que es más que evidente que la implementación actual en Delphi no está hecha para ofrecer la máxima flexibilidad al desarrollador, sino para ahorrarse ellos tiempo y quebraderos de cabeza en escribir el compilador.

Estoy totalmente de acuerdo en que la herramienta otorgue la máxima flexibilidad al desarrollador. Pero no a nuestra costa. No a base de complicarnos la vida a los desarrolladores. Es su trabajo lidiar con el compilador, no debemos ser nosotros quienes continuamente póngamos coletillas como dynamic/virtual para indicar al compilador como tiene que hacer su trabajo.

Es como el tema de las interfaces. Si esto es lo único que saben aportar al lenguaje, pues apechugaremos y trabajaremos con ello, pero al menos que reconozcan su trabajo "mediocre". No vayamos encima a felicitarles.

Los diseños de las clases, y su visibilidad evolucionan. Pareciera que te olvidas de que la teoría OO es un modelo vivo, que se adapta y cambia según se determina como se va relacionando... Tras un tiempo uno puede verse obligado a alterar una clase... y es por situaciones como éstas en donde está presente la reingeniería. Se vuelve a regenerar... ¿O no te olvidas de que la misma VCL se fué adaptando tras cada versión?
Tus palabras dan a entender que las clases están muertas y lo estarán por siempre y no puedes hacer nada más que usarlas como están.


¿ De donde sacas una conclusión tan "exótica" ?.

O sea que pido que los métodos no esten bloqueados para las clases heredadas (como lo estan los métodos estáticos, que es la opción por defecto en Delphi). Y eso debe ser porqué no tengo la menor intención de sobreescribirlos en el futuro, tengo una visión de las clases "muerta". ¿ Para que demonios debo querer entonces poder sobreescribir cualquier método ?.

Curiosa interpretación.

Te recuerdo además, que si un método o atributo es privado puedes hacerlo público en una clase descendiente.


¿ En serio permite eso ?. Le había bajado la visibilidad a algunos métodos, pasándolos de public a private o protected, pero jamás se me ocurrió ni siquiera intentarlo al revés. Menuda chapuza por parte de Borland, eso echa por tierra toda la supuesta utilidad de la sección private para proteger el funcionamiento diseñado por el desarrollador original.

Razón de más para eliminar la sección private y fusionarla con la protected, puesto que según dices, con una clase intermedia donde reubiques los métodos y propiedades, vas a poder lograr igualmente el mismo objetivo.

Tus conceptos de que debe estar todo cerrado y luego poder abrirlo va en contra de que lo que se conoce como el principio abierto/cerrado: una clase se diseña para ser lo suficientemente cerrada para ocultar los elementos innecesarios o que lo son de importancia para el usuario o cliente de la clase, y a la vez lo suficientemente abierta para ofrecer una interfaz adecuada para comprender su uso y su propósito.
El que por defecto todo o en su buena parte sea estático limita demasiado a la clase.


Por favor no confundamos churras con merinas. Una cosa es que comente que el IDE debería cerrar los formularios, poniendo todos sus componentes en una sección private/protected, y otra cosa muy distinta es que espere lo mismo de las clases. Evidentemente que no creo que una clase deba estar toda cerrada, naturalmente que debe tener una interfaz pública.

Por cierto, no te equivoques. ¿ De donde sacas que quiero que todos los métodos sean por defecto estáticos ?. Por favor, lee cualquiera de mis mensajes. Llevo todo el hilo quejándome de que Delphi nos obligue a declarar explicitamente como dynamic/virtual los métodos que se puedan sobreescribir. Llevo todo el hilo pidiendo que los métodos sean por defecto dynamic/virtual y que solo sean estáticos los que se declaren ex-profeso como tal.

La idea de fusionar protected y private me parece errada. Private está para ocultar, protected está para ofrecer la suficiente seguridad de poder brindar un mejor conocimiento de una clase para poder extenderla y a su vez los medios para ofrecer de seguridad en cuanto a su uso y evitar estar publicando demasiado.


Es una opinión. Yo tengo otra, y no las considero erradas, ni la tuya ni la mía, solo considero mejor la mía por simpleza.

Private y protected ofrecen la suficiente flexibilidad para ocultar la complejidad interna de una clase, y a la vez poder extenderla. Cuando aprendí POO no existían las cláusulas protected, solo las public y private (con funcionalidad de protected), así pués no soy la única persona en opinar que es mejor seguir el principio KISS (Keep it simple, stupid).

Saludos.
  • 0

#39 Rolphy Reyes

Rolphy Reyes

    Advanced Member

  • Moderadores
  • PipPipPip
  • 2.092 mensajes
  • LocationRepública Dominicana

Escrito 08 octubre 2010 - 06:27

Saludos.

Amigos, creo que ya en el hilo se esta debatiendo otro tema distinto al original Interfaces.

La cuestión sobre la visibilidad y declaración de métodos es bastante extensa y se podrían escribir grandes libros sobre las distintas visiones y/o versiones de los distintos autores, escritores, idealistas, puristas y cualquier otra persona que tenga su opinión de como debió o debe (con su porque) de ser el paradigma POO.

No me vayan a mal interpretar, todas las opiniones vertidas son importantes, pero el rumbo que esta tomando el hilo es suficiente como para abrir otro en donde se pueda discutir, ampliar, abundar sobre el mismo sin cambiar el original.


  • 0

#40 andres1569

andres1569

    Advanced Member

  • Miembro Platino
  • PipPipPip
  • 431 mensajes

Escrito 08 octubre 2010 - 06:35

Volviendo al tema original, para no desvirtualizar el hilo  ;)

Bueno, no veo como en la implementación se referenciará al método original de la clase intermedia para llamarlo. Pero es muy posible que haya alguna forma (¿ quizás con una cast de Self a la clase intermedia ?), después de todo tampoco conocía esta forma de delegar la implementación de una interface. Y hay que admitir que con esto las interfaces ganan mucha utilidad al poder pseudo-heredar una implementación además de una estructura.

Desde luego, es algo más lioso y menos elegante que en la herencia múltiple, puesto que al hacer uso de otra clase implementadora (la que hemos llamado intermedia), lo que hacemos es redefinir un comportamiento en la clase principal que ya estaba definido en esa otra clase, invadiendo competencias, digamos. Llamar al método original de dicha clase implementadora para heredarlo resulta sencillo, pero el resto es algo chapucilla, puesto que posiblemente necesitemos utilizar una serie de variables y métodos de dicha clase intermedia (por eso dije anteriormente que ésta clase intermedia debía publicar muchas propiedades y métodos que puedan servir para ser usadas por clases ajenas).

Otra opción, de la que puso un ejemplo básico Rolphy, es derivar una clase de la clase intermedia que se encargue de hacer ese trabajo, esto parece más elegante y natural, aparte de que al ser descendiente de la clase implementadora conoce los métodos y propiedades protegidas. Esta clase sería la que usaríamos luego en nuestra clase principal. Claro, ahora viene la pregunta del millón: si para redefinir un método en la clase principal necesitamos implementarlo en otra clase, ¿cómo sabe esta clase lo que tiene que hacer y los parámetros? Esto nos lleva a trucos como pasarle en el constructor Create un parámetro que apunte a la clase principal, o cosas por estilo, pero en definitiva nos está llevando a una dependencia entre dos clases.

Como dije en un mensaje anterior, con este modelo se pueden realizar ciertas implementaciones de interés, pero sería una locura edificar una jerarquía entera con interfaces, algo que a tenor de la explicación con gráficos que hizo Delphius, tampoco sería recomendable puesto que las interfaces son de quita y pon, pueden ayudar a solucionar algunas necesidades pero respetando y no condicionando el árbol establecido mediante herencia simple.

De nuevo, al igual que con los métodos estáticos y dinámicos, ya no niego la válidez de la implementación de Delphi, pero sí que me parece liosa y bastante menos elegante que lo que resultaría para hacer lo mismo con herencia múltiple (sin clases intermedias, sin tener que hacer cosas exóticas para sobreescribir un método, ni para llamar a su implementación original, ...)

En eso creo que coincidimos todos. Las interfaces son lo que son y sirven para que lo que sirven.

Saludos
  • 0




IP.Board spam blocked by CleanTalk.