
[RESUELTO] Expandir TCollectionItem
#1
Posted 15 November 2011 - 11:12 AM
A ver si me se explicar porque estoy muy espeso hoy jejjejeje
Bien, resulta que tengo una clase (TGMMarker) la cual tiene una propiedad que es una colección (TMarkers) de TCollectionItems (TMarker). Hasta ahí todo bien.
Ahora quiero heredar de TGMMarker (no hay problema) para crear una nueva clase. Esta nueva clase necesita que sus TCollectionItems tengan más propiedades que los TMarker que heredo de TGMMarke. ¿Cómo puedo hacer ésto?
Espero que haya quedado claro, sino preguntar y miro de explicarme mejor jejeje
Nos leemos
#2
Posted 15 November 2011 - 11:40 AM
Ahora quiero heredar de TGMMarker (no hay problema) para crear una nueva clase. Esta nueva clase necesita que sus TCollectionItems tengan más propiedades que los TMarker que heredo de TGMMarke. ¿Cómo puedo hacer ésto?
Deriva una clase de TMarker y en el constructor de tu clase derivada de TGMMarker construyes tu nuevo TMarker. Ahora tienes el dilema de realizar un cast para el uso se tus nuevas propiedades o crear una propiedad que sobrecargue la vieja TMarker...
Saludos.
#3
Posted 15 November 2011 - 11:44 AM
Pues, yo también estoy espeso... y tengo mi cabeza en off pero... ¿Y donde está el problema?
Se deriva de esa clase item como toda la vida.
TMinuevoItem = class(...)
Y como dice escafandra, sobreescribes la propiedad vectorial para que acepte esta nueva clase.
Es como volver a aplicar la colección.
Saludos,
#4
Posted 15 November 2011 - 12:06 PM
Cuando creas una colección le pasas en su constructor el tipo de clase de los elementos que va a manejar (el respectivo descediente de TCollectionItem). Aquí la pega puede que la tengas en que TGMMarker crea su colección pasándole como tipo base TMarker. Si creas una nueva clase descendiente de TGMMarker que quieres que maneje otro tipo base para su colección deberás ingeniarte un mecanismo "virtual" para que cada clase indique el tipo base (derivado de TCollectionItem) que quiere emplear. Puedes por ejemplo definir una función virtual de este estilo:
(...) constructor TGMMarker.Create; begin ... MiCollection := TMarkers.Create(GetBaseItemClass ); ... end; function TGMMarker.GetBaseItemClass : TCollectionItemClass; // este método es virtual begin result := TMarker; end; // y en la clase derivada ... function TGMMarkerDesc.GetBaseItemClass : TCollectionItemClass; // este método es override begin result := TMarkerAmpliado; end;
Como ves, en el constructor de la colección le pasamos una referencia a la función virtual que devuelve el tipo de clase de elemento deseado.
Saludos
#5
Posted 16 November 2011 - 09:17 AM
Efectivamente, Andrés, eso es lo que andaba buscando. El hecho de heredar de mi TCollectionItems no era el problema. El problema era decirle a mi TCollection que lo que iba a almacenar era de la clase nueva
Ahora, ya para terminar de ponerlo bonito.....
Tengo lo siguiente:
Por un lado TGMMarker => TMarkers (TCollection) => TMarker (TCollectionItem)
Y por otro TGMRoute => TMarkers => TRoute (extiende la clase TMarker)
En TGMMarker tengo
published property MarkerList: TMarkers read FMarkerList write FMarkerList;
Por lo que si yo accedo por código a los TRoute de TGMRoute me devuelve un TMarker, obligándome a hacer una conversión de tipos para acceder a las propiedades de TRoute
TRoute(GMRoute1.MarkerList[0]).StopOver := False; // por ejemplo
Está claro que puedo deribar de TMarkers para crear una clase TRoutes (por ponerle un nombre) y que mediante la técnica mostrada por Andrés, el constructor de TGMMarker puede crear una colección o otra
constructor TGMMarker.Create(aOwner: TComponent); begin inherited; // GetCollectionClass es una función virtual que te devuelve un TMarkersClass FMarkerList := GetCollectionClass.Create(Self, GetCollectionItemClass); end;
Pero como MarkerList es de tipo TMarkers, siempre me devuelve un TMarker. Pregunta..... ¿Hay alguna manera para devolver un TRoute en lugar de un TMarker para así evitar tener que hacer la conversión de tipos?
Gracias
Nos leemos
#6
Posted 16 November 2011 - 11:47 AM
Pero como MarkerList es de tipo TMarkers, siempre me devuelve un TMarker. Pregunta..... ¿Hay alguna manera para devolver un TRoute en lugar de un TMarker para así evitar tener que hacer la conversión de tipos?
Esa conversión de tipos que quieres ahorrar al usuario de los componentes la puedes delegar a la clase que alberga la colección, creando una propiedad matricial que devuelva el tipo de Item deseado para cada caso. Más o menos como el ejemplo:
type TGMMarker = class(TComponent) private function GetMarker(Index: Integer) : TMarker; public property Markers[Index: Integer] : TMarker read GetMarker; end; // en la clase derivada TGMRoute = class(TGMMarker) private function GetRoute(Index: Integer) : TRoute; public property Routes[Index: Integer] : TRoute read GetRoute; // Opción A // o bien property Markers[Index: Integer] : TRoute read GetRoute; // Opción B end; // (...) implementation function TGMMarker.GetMarker(Index: Integer) : TMarker; begin result := TMarket(FMarkerList[Index]); end; // (...) function TGMRoute.GetRoute(Index: Integer) : TRoute; begin result := TRoute(MarkerList[Index]); end;
En el ejemplo, en la segunda clase TGMRoute, podemos (opción A) crear una propiedad alternativa Routes que no anularía a la heredada Markers, de modo que al usar Routes devolvería el tipo que deseas, y se seguiría pudiendo usar Markers desde un objeto TGMRoute para acceder a los mismos elementos devueltos como TMarker.
O bien podemos (opción B) crear una propiedad Markers llamada igual que la del componente ancestro, pero que devuelve el tipo deseado (TRoute). Al redefinir una propiedad con el mismo nombre que el de la clase ancestra, la nueva propiedad tiene prioridad cuando se la llama desde un componente del tipo TGMRoute. Pero la pega de esta segunda opción es que perdemos polimorfismo ya que si quisiéramos acceder indistintamente a elementos de ambos tipos de componentes (TGMMarker y TGMRoute) llamando a la propiedad matricial Markers, Delphi accedería a los elementos tal como están definidos en la clase ancestra, es decir que devolvería elementos TMarker para todos los casos (eso es la teoría, no lo he probado para decirlo con total seguridad).
Pero como ves, la técnica consiste no en acceder a los elementos desde sus colecciones sino desde la clase (componente en este caso) que las alberga mediante esa propiedad de tipo matricial. Tampoco es mala idea para estos propósitos habilitar una propiedad Count dentro del componente TGMMarker que devuelva el número de elementos de la colección, de modo que para el accseso a los elementos ni siquiera hiciera falta referenciar la colección.
Saludos
#7
Posted 16 November 2011 - 10:21 PM

Por otro lado, ¿no sería más adecuado hacer:
result := MarketList[Index] as TRoute;
que
result := TRoute(MarketList[Index]);
Aunque yo acostumbro hacer esto, que creería que es más seguro:
result := inherited Markers[Index] as TRoute;
En los fuentes y ejemplos que he visto siguen esa forma.
Saludos,
#8
Posted 17 November 2011 - 02:22 AM
Entiendo por donde vas Andrés. Quieres que en lugar de usar la propiedad vectorial de TCollection suba esa acción a su owner. No me parece mala idea

Referente a lo que comenta delphius del default, no se si será o no obligatorio, pero es buena idea, así también te evitas de tener que referenciar la propiedad y puedes escribir cosas como
GMRoute[i].MiPropiedad := XXXX;
Ahora, lo que ya no se es lo que comenta a continuación

Gracias, estoy aprendiendo mucho con estos componentes

Nos leemos
#9
Posted 17 November 2011 - 11:43 AM
Recuerda que dije "creería", aunque tengo un poquitín de duda.
El punto es que se está haciendo uso de una sobresescritura de una propiedad. Y en realidad, lo único que se consigue con dicha sobreescritura es alterar el tipo de dato. En términos concretos quien en verdad hace todo el trabajo y tiene almacenado los objetos para ser utilizados por esta propiedad es la clase base, los demás simplemente hacen casting.
Debería suponerse que fueran equivalente ambas formas, tanto con como sin el inherited. Pero por precaución, yo obligo explícitamente a que pase por la propiedad vectorial de la clase base. No porque se requiera hacer algo más como comentas, sino porque es la base quien en realidad trabaja.
También hago uso de as para garantizar el cast. Con la otra forma se hace un cast implícito y técnicamente sólo es una conversión "al vuelo".
Y como dije... además así es como lo vi en las fuentes


Saludos,
#10
Posted 17 November 2011 - 12:31 PM
me parece buena idea lo de usar Default, pero no es obligatorio para sobrescribir una propiedad con el mismo nombre, sino que se usa únicamente en propiedades vectoriales, y sólo en una de ellas por cada clase, para definir dicha propiedad como la "propiedad vectorial por defecto", ahorrando algo de código al acceder a ella, como muestra el ejemplo que ha puesto Cadetill.
En cuanto a lo demás que comentas, Delphius, repecto a usar inherited al igual que tú no veo realmente la diferencia ni que una forma sea más eficiente que otra. Sobre el uso del operador as, no lo estimo necesario cuando sabemos que el objeto es de un tipo, algo que sucede en este caso.
Saludos
Saludos
#11
Posted 18 November 2011 - 01:38 AM
Una pregunta, Andrés, ¿por algún motivo haces la propiedad vectorial ReadOnly? Es que no me había fijado en ese detalle hasta ahora que le he podido meter mano al tema jejeje
Gracais
Nos leemos
#12
Posted 18 November 2011 - 06:37 AM
Se bien que default sólo es válido para las propiedades vectoriales, y que para establecer una de ellas (si hay más de una) como por defecto (lo que permite hacer miClase[].Algo). Pero recuerdo haber leído en un artículo que cuando uno está sobrescribiendo la propiedad es aconsejable utilizar default para que asuma a ésta propiedad, que está pensada para trabajar con el tipo sobrescrito y no la de la clase base.Hola,
me parece buena idea lo de usar Default, pero no es obligatorio para sobrescribir una propiedad con el mismo nombre, sino que se usa únicamente en propiedades vectoriales, y sólo en una de ellas por cada clase, para definir dicha propiedad como la "propiedad vectorial por defecto", ahorrando algo de código al acceder a ella, como muestra el ejemplo que ha puesto Cadetill.
Yo no dije que sea más eficiente una que otra. No noto diferencia en una u otra, ya que al menos en teoría son equivalente. Nomás lo hago de esa forma por dos cosas: 1) Seguridad de que no meteré la pata; o al menos debería serlo y 2) En muchas referencias y en la misma VCL se hace de esa forma (que por algo será)En cuanto a lo demás que comentas, Delphius, repecto a usar inherited al igual que tú no veo realmente la diferencia ni que una forma sea más eficiente que otra. Sobre el uso del operador as, no lo estimo necesario cuando sabemos que el objeto es de un tipo, algo que sucede en este caso.
Saludos
Saludos,
#13
Posted 18 November 2011 - 06:44 AM
Hola.Buenas,
Una pregunta, Andrés, ¿por algún motivo haces la propiedad vectorial ReadOnly? Es que no me había fijado en ese detalle hasta ahora que le he podido meter mano al tema jejeje
Gracais
Nos leemos
Yo creo que es a modo de ejemplo nomás.
Pero seguro que habrá casos en los que es deseable que sea read-only únicamente, y otros en los que se necesita read/write. Por ejemplo, en mi implementación del patrón Observer, la clase TSubject posee una propiedad vectorial Observers[] de sólo lectura ya que técnicamente el Subject no tiene responsabilidad para estar alterando a como guste a los observadores. Más bien son estos últimos quienes dan su consentimiento e interés de Suscribirse/Desuscribirse.
Un buen motivo para que sea read-only es para evitar "pisar" algún item ya definido y que se ha o está utilizando.
Saludos,
#14
Posted 18 November 2011 - 12:00 PM
Buenas,
Una pregunta, Andrés, ¿por algún motivo haces la propiedad vectorial ReadOnly? Es que no me había fijado en ese detalle hasta ahora que le he podido meter mano al tema jejeje
Pues sí lo hice a propósito, ya que no tiene mucho sentido que sea de escritura. Esta propiedad está pensada para ser pública, no publicada, por lo tanto no interviene a la hora de guardar los datos en el stream del formulario (1ª razón), y además devuelve objetos, por lo que con acceder a cada uno de ellos ya podemos modificar sus propiedades (2ª razón, para esto da igual que se acceda a ellos desde una propiedad Read-Only como es Markers). Además, el hecho de que ya exista la propiedad publicada MarkerList, heredera de TCollection, y que la tengas definida como lectura/escritura ya garantiza que Delphi pueda leerla y recuperarla del .DFM y que puedas manejar la lista desde el editor de colecciones que estás empleando.
Digamos que la gestión de adición y eliminación de elementos de la lista se hace a través de los métodos de la clase TCollection correspondiente, y la propiedad Markers sólo sirve para recorrerlos.
Se bien que default sólo es válido para las propiedades vectoriales, y que para establecer una de ellas (si hay más de una) como por defecto (lo que permite hacer miClase[].Algo). Pero recuerdo haber leído en un artículo que cuando uno está sobrescribiendo la propiedad es aconsejable utilizar default para que asuma a ésta propiedad, que está pensada para trabajar con el tipo sobrescrito y no la de la clase base.
Hola,
me parece buena idea lo de usar Default, pero no es obligatorio para sobrescribir una propiedad con el mismo nombre, sino que se usa únicamente en propiedades vectoriales, y sólo en una de ellas por cada clase, para definir dicha propiedad como la "propiedad vectorial por defecto", ahorrando algo de código al acceder a ella, como muestra el ejemplo que ha puesto Cadetill.
Buen apunte, supongo que te refieres a que de esa forma se sustituye, en nuestro ejemplo, la propiedad Routers por Markers como propiedad vectorial por defecto en la clase TGMRoute.
Yo no dije que sea más eficiente una que otra. No noto diferencia en una u otra, ya que al menos en teoría son equivalente. Nomás lo hago de esa forma por dos cosas: 1) Seguridad de que no meteré la pata; o al menos debería serlo y 2) En muchas referencias y en la misma VCL se hace de esa forma (que por algo será)
En cuanto a lo demás que comentas, Delphius, repecto a usar inherited al igual que tú no veo realmente la diferencia ni que una forma sea más eficiente que otra. Sobre el uso del operador as, no lo estimo necesario cuando sabemos que el objeto es de un tipo, algo que sucede en este caso.
Saludos
Saludos,
Ok
Saludos
#15
Posted 18 November 2011 - 12:12 PM
Caramba!!!
Me lo pueden explicar con manzanitas

[/off-topic]
Salud OS
#16
Posted 18 November 2011 - 12:15 PM
Pues hasta Delphi XE2, todo esto funciona bajo Windows, no con Apple[off-topic]
Caramba!!!
Me lo pueden explicar con manzanitas
[/off-topic]


#17
Posted 18 November 2011 - 12:21 PM
Pues hasta Delphi XE2, todo esto funciona bajo Windows, no con Apple
[off-topic]
Caramba!!!
Me lo pueden explicar con manzanitas
[/off-topic]
![]()
Ah vaya ya decía yo, estaba con la marca equivocada



La verdad es que fuera de broma me quedo con los ojos cuadrados y la boca abierta, son cosas que rebasan mi nivel de abstracción.


Salud OS
#18
Posted 18 November 2011 - 10:33 PM
Eliseo es muy fácil. Haz de cuenta que en ver las clases que se han propuesto tienes cualquier clase de colección. Pongamos por ejemplo la de TColumns y TColumn. Como sabemos, podemos acceder a la colección de TColumns gracias a la propiedad vectorial [] que hereda desde TColletionItem. Esta propiedad devuelve un objeto de clase TColumn.
Ahora nuestro querido compañero necesita ampliar este concepto y tener sus propios TMiColumns y TMiColumn.
Básicamente el "trabajo sucio" ya está hecho en las clase base, en realidad el mayor fuerte está ahora en sobreescribir esta propiedad vectorial para que trabaje con la nueva clase "item" TMiColumn.
De allí es que surje este debate.
Si quieres repasar este tema en la Cara Oculta de D4 está explicado sobre las propiedades vectoriales en el capítulo dedicado a Propiedades.
Y ahora que lo pienso... ¿alguno de ustedes se ha encontrado con propiedades indexadas? Es una característica bastante interesante pero no muy vista.
Saludos,
#19
Posted 19 November 2011 - 08:47 AM
Eliseo, imagina que quiero hacerme mi propio TMyDBGrid para añadirle funcionalidades
TMyDBGrid = class(TDBGrid) .....
También sabes que el TDBGrid tiene columnas (TColumn => TCollectionItem) a las que accedes a través de su propiedad Columns (TDBGridColumns => TCollection) gracias a su propiedad vectorial Items que, al estar declarada como default, no hace falta que referenciemos esa propiedad para acceder a una columna
DBGrid1.Columns.Items[0].Width := 10; // es lo mismo que DBGrid1.Columns[0].Width := 10;
Bien, ahora imagina que en nuestro nuevo grid, no sólo queremos añadir nuevas funcionalidades al TDBGrid, sino también a las columnas. Pues toda la explicación de cómo hacerlo la tienes en este hilo

Espero haber aclarado algo tus dudas (a mi así lo han logrado los compañeros)
Nos leemos
#20
Posted 21 November 2011 - 01:16 PM
Normalmente, cuando se deriva una clase de TCollectionItem, se tiende a derivar otra clase de TCollection, como el ejemplo de TColumn y TDBGridColumns que habéis puesto. En estos casos, la clase que deriva de TCollection suele redefinir la propiedad llamada Items para que apunte a los mismos elementos pero "casteados" al tipo específico que usa dicha colección, como lo que está programando Cadetill. Pero en este caso concreto, puesto que se crean dos descendientes de TCollectionItem (TMarker y TRoute) y tan sólo una clase de TCollection (TMarkers), propuse que la propiedad vectorial la implementara el componente propietario de la colección (TGMMarker o TGMRoute en nuestro caso), para no tener que crear descendientes innecesarios de TCollection sólo para ese fin.
Saludos