Características de las clases avanzadas

Continuamos con la introducción al lenguaje Pascal Moderno.

Esta entrada es una traducción al español, del texto original escrito por Michalis Kamburelis.

Privado y estrictamente privado

El especificador de visibilidad privada significa que el campo (o método) no es accesible fuera de esta clase. Pero permite una excepción: todo el código definido en la misma unidad puede romper esto y acceder a campos y métodos privados. Un programador de C++ diría que en Pascal todas las clases dentro de una sola unidad son amigas. Esto suele ser útil y no rompe la encapsulación, ya que está limitado a una unidad. Sin embargo, si crea unidades más grandes, con muchas clases (que no están estrechamente integradas entre sí), es más seguro usar la privacidad estricta. Significa que el campo (o método) no es accesible fuera de este período de clase. Sin excepciones. De manera similar, hay visibilidad protegida (visible para los descendientes o amigos en la misma unidad) y protección estricta (visible para los descendientes, punto).

Más cosas dentro de clases y clases anidadas

Puede abrir una sección de constantes (const) o tipos (tipo) dentro de una clase. De esta manera, incluso puede definir una clase dentro de una clase. Los especificadores de visibilidad funcionan como siempre, en particular, la clase anidada puede ser privada (no visible para el mundo exterior), lo que suele ser útil.

Tenga en cuenta que para declarar un campo después de una constante o tipo, deberá abrir un bloque var.

Métodos de clase

Estos son métodos a los que puede llamar con una referencia de clase (TMyClass), no necesariamente una instancia de clase.

Tenga en cuenta que pueden ser virtuales — tiene sentido, y a veces es muy útil, cuando se combinan con referencias de clase.

Los métodos de clase también pueden estar limitados por los especificadores de visibilidad, como privado o protegido. Al igual que los métodos regulares.

Tenga en cuenta que un constructor siempre actúa como un método de clase cuando se llama de forma normal (MyInstance := TMyClass.Create(…​);). Aunque también es posible llamar a un constructor desde dentro de la propia clase, como un método normal, y luego actúa como un método normal. Esta es una característica útil para «encadenar» constructores, cuando un constructor (por ejemplo, sobrecargado para tomar un parámetro entero) hace algún trabajo y luego llama a otro constructor (por ejemplo, sin parámetros).

Referencias de clase

La referencia de clase le permite elegir la clase en tiempo de ejecución, por ejemplo, para llamar a un método de clase o constructor sin conocer la clase exacta en tiempo de compilación. Es un tipo declarado como clase de TMyClass.

Las referencias de clase se pueden combinar con métodos de clase virtual. Esto da un efecto similar al uso de clases con métodos virtuales — el método real que se ejecutará se determina en tiempo de ejecución.

Si tiene una instancia y desea obtener una referencia a su clase (no la clase declarada, sino la clase descendiente final utilizada en su construcción), puede usar la propiedad ClassType. El tipo declarado de ClassType es TClass, que significa clase de TObject. A menudo, puede encasillarlo de manera segura en algo más específico, cuando sabe que la instancia es algo más específico que TObject.

En particular, puede usar la referencia ClassType para llamar a métodos virtuales, incluidos los constructores virtuales. Esto le permite crear un método como Clone que construye una instancia de la clase de tiempo de ejecución exacta del objeto actual. Puede combinarlo con Cloning: TPersistent.Assign para tener un método que devuelva un clon recién construido de la instancia actual.

Recuerda que solo funciona cuando el constructor de tu clase es virtual. Por ejemplo, se puede usar con los descendientes estándar de TComponent, ya que todos deben anular el constructor virtual TComponent.Create(AOwner: TComponent).

Métodos de clase estática

Para comprender los métodos de clase estáticos, debe comprender cómo funcionan los métodos de clase normales (descritos en las secciones anteriores). Internamente, los métodos de clase normales reciben una referencia de clase de su clase (se pasa a través de un primer parámetro del método oculto e implícitamente agregado). Incluso se puede acceder a esta referencia de clase explícitamente usando la palabra clave Self dentro del método de clase. Por lo general, es algo bueno: esta referencia de clase le permite llamar a métodos de clases virtuales (a través de la tabla de métodos virtuales de la clase).

Si bien esto es bueno, hace que los métodos de clase normales sean incompatibles cuando se asignan a un puntero de procedimiento global. Es decir, esto no compilará:

Nota: En el modo Delphi, podría escribir TMyClass.Foo en lugar de un feo TMyClass(nil).Foo en el ejemplo anterior. Es cierto que TMyClass.Foo se ve mucho más elegante y el compilador también lo verifica mejor. Usar TMyClass(nil).Foo es un truco… desafortunadamente, necesario (por ahora) en el modo ObjFpc que se presenta a lo largo de este libro.

En cualquier caso, la asignación de TMyClass.Foo a la devolución de llamada anterior aún fallaría en el modo Delphi, exactamente por las mismas razones.

El ejemplo anterior no se compila porque Callback es incompatible con el método de clase Foo. Y es incompatible porque internamente el método de clase tiene ese parámetro implícito oculto especial para pasar una referencia de clase.

Una forma de corregir el ejemplo anterior es cambiar la definición de TMyCallback. Funcionará si se trata de una devolución de llamada de método, declarada como TMyCallback = procedimiento (A: Integer) of object;. Pero a veces, no es deseable.

Aquí viene el método de clase estática. Es, en esencia, solo un procedimiento/función global, pero su espacio de nombres está limitado dentro de la clase. No tiene ninguna referencia de clase implícita (y por lo tanto, no puede ser virtual y no puede llamar a métodos de clase virtual). Por el lado positivo, es compatible con las devoluciones de llamadas normales (sin objetos). Así que esto funcionará:

Propiedades de clase y variables

Una propiedad de clase es una propiedad a la que se puede acceder a través de una referencia de clase (no necesita una instancia de clase).

Es una analogía bastante directa de una propiedad regular (ver Propiedades). Para una propiedad de clase, define un getter y/o un setter. Pueden hacer referencia a una variable de clase o a un método de clase estático.

Una variable de clase es, lo adivinó, como un campo normal, pero no necesita una instancia de clase para acceder a ella. En efecto, es como una variable global, pero con el espacio de nombres limitado a la clase contenedora. Se puede declarar dentro de la sección class var de la clase. Alternativamente, se puede declarar siguiendo la definición de campo normal con la palabra clave static.

Y un método de clase estática es como un procedimiento/función global, pero con el espacio de nombres limitado a la clase contenedora. Para obtener más información sobre los métodos de clase estáticos en la sección anterior, consulte Métodos de clase estáticos.

Ayudantes de clase (Helpers)

El método es solo un procedimiento o función dentro de una clase. Desde el exterior de la clase, la llama con una sintaxis especial MyInstance.MyMethod(…​). Después de un tiempo te acostumbras a pensar que si quiero hacer una acción Acción en la instancia X, escribo X.Action(…​).

Pero a veces, necesita implementar algo que conceptualmente es una acción en la clase TMyClass sin modificar el código fuente de TMyClass. A veces es porque no es su código fuente y no quiere cambiarlo. A veces se debe a las dependencias — agregar un método como Render a una clase como TMy3DObject parece una idea sencilla, pero tal vez la implementación base de la clase TMy3DObject debería mantenerse independiente del código de renderizado. Sería mejor «mejorar» una clase existente, agregarle funcionalidad sin cambiar su código fuente.

Una forma sencilla de hacerlo es crear un procedimiento global que tome una instancia de TMy3DObject como su primer parámetro.

El concepto más general es «type helper». Al usarlos, puede agregar métodos incluso a tipos primitivos, como enteros o enumeraciones. También puede agregar «ayudantes de registro» a (lo adivinó…) registros. Consulte http://lists.freepascal.org/fpc-announce/2013-February/000587.html.

Constructores virtuales, destructores

El nombre del destructor siempre es Destroy, es virtual (ya que puede llamarlo sin conocer la clase exacta en tiempo de compilación) y sin parámetros.

El nombre del constructor es por convención Create.

Puede cambiar este nombre, aunque tenga cuidado con esto: si define CreateMy, siempre redefina también el nombre Create, de lo contrario, el usuario aún puede acceder al constructor Create del antepasado, sin pasar por su constructor CreateMy.

En el TObject base no es virtual, y al crear descendientes eres libre de cambiar los parámetros. El nuevo constructor ocultará el constructor en el ancestro (nota: no ponga aquí sobrecarga, a menos que quiera romperlo).

En los descendientes de TComponent, debe anular su constructor Create (AOwner: TComponent);. Para la funcionalidad de transmisión, para crear una clase sin conocer su tipo en el momento de la compilación, es muy útil tener constructores virtuales (consulte Referencias de clase más arriba).

Una excepción en el constructor.

¿Qué sucede si ocurre una excepción durante un constructor? La línea:

no se ejecuta hasta el final en este caso, no se puede asignar X, entonces, ¿quién limpiará después de una clase parcialmente construida?

La solución de Object Pascal es que, en caso de que ocurra una excepción dentro de un constructor, se llama al destructor. Esta es una razón por la que su destructor debe ser robusto, lo que significa que debería funcionar en cualquier circunstancia, incluso en una instancia de clase creada a medias. Por lo general, esto es fácil si libera todo de forma segura, como FreeAndNil.

También tenemos que depender en tales casos de que se garantice que la memoria de la clase se ponga a cero justo antes de que se ejecute el código del constructor. Entonces sabemos que al principio, todas las referencias de clase son nulas, todos los números enteros son 0 y así sucesivamente.

Entonces, a continuación, funciona sin fugas de memoria:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio esta protegido por reCAPTCHA y laPolítica de privacidady losTérminos del servicio de Googlese aplican.

El periodo de verificación de reCAPTCHA ha caducado. Por favor, recarga la página.