Approccio "object oriented" in F90: relazioni fra classi

Descriviamo ora le possibili modalità di relazione tra classi.
Aggregazione: una classe può contenere un riferimento ad una seconda classe attraverso un pointer ad un oggetto di tale classe; in questa maniera un oggetto della seconda classe può essere aggregato contemporaneamente a più oggetti della prima e la sua vita non dipende dalla vita degli oggetti a cui è aggregato.

Esempio della classe "stazione" che aggrega a sé un oggetto della classe "anagrafica di stazione".

Si tratta di una relazione di tipo "has a": una stazione "ha" un'anagrafica.

MODULE staz_ana_class
IMPLICIT NONE

TYPE staz_ana
  REAL :: lon, lat
  ...
END TYPE staz

CONTAINS
...
END MODULE staz_ana_class

MODULE staz_class
USE staz_ana
IMPLICIT NONE

TYPE staz
  REAL :: obs
  ...
  TYPE(staz_ana), POINTER :: ana
END TYPE staz

CONTAINS
...
END MODULE staz_class
Composizione: una classe può contenere una seconda classe tra i suoi membri; in questa maniera un oggetto della prima classe possiede una copia personale di un oggetto della seconda, la cui esistenza è legata all'esistenza dell'oggetto della prima classe.

Esempio della classe "anagrafica di stazione" che si compone di un oggetto della classe "coordinata geografica".

Si tratta anche in questo caso di una relazione di tipo "has": un'anagrafica di stazione "ha" una coordinata.

In F90 la classe contenente deve ricordarsi di chiamare il costruttore e il distruttore delle eventuali classi contenute, all'interno dei proprî cotruttore e distruttore rispettivamente.

MODULE geo_coord_class
IMPLICIT NONE

TYPE geo_coord
  REAL :: lon, lat
  DOUBLE PRECISION :: utme, utmn
  INTEGER :: fuso
  CHARACTER(LEN=20) :: elliss
END TYPE staz

CONTAINS

FUNCTION geo_coord_init(...) RESULT(this)
...
TYPE(geo_coord) :: this
...
END FUNCTION geo_coord_init
...
END MODULE geo_coord_class

MODULE staz_ana_class
USE geo_coord_class
IMPLICIT NONE

TYPE staz_ana
  TYPE(geo_coord) :: coord
  ...
END TYPE staz

CONTAINS

FUNCTION staz_ana_init(...) RESULT(this)
...
TYPE(staz_ana) :: this
...
this%coord = geo_coord_init(...)
...
END FUNCTION staz_ana_init
...
END MODULE staz_ana_class
Uso: una classe può fare uso, all'interno di un suo metodo, di oggetti di una seconda classe, pur non contenendo riferimenti nelle variabili della classe stessa; tipicamente l'oggetto della seconda classe è passato come argomento al metodo o è una sua variabile locale.

Esempio della classe geo_coord che, in un suo metodo, fa uso di un oggetto della classe "estranea" arcview_class per convertirlo ad un oggetto della propria classe.

In questo caso è sufficiente fare una USE del modulo della classe estranea usata. Se tale classe è usata in un numero ristretto di metodi (sottoprogrammi della classe) la USE può stare nei singoli sottoprogrammi, altrimenti, se l'utilizzo è esteso a molti metodi, può essere più opportuno mettere lo USE nel MODULE stesso.

MODULE geo_coord_class
IMPLICIT NONE

TYPE geo_coord
  REAL :: lon, lat
  DOUBLE PRECISION :: utme, utmn
  INTEGER :: fuso
  CHARACTER(LEN=20) :: elliss
END TYPE staz

CONTAINS

...

SUBROUTINE geo_coord_from_arcview_coord(this, av_c)
USE arcview_class
TYPE(staz) :: this
TYPE(arcview_coord) :: av_c

...
this%utme = av_c%easting/1000.
this%utmn = av_c%northing/1000.
...

END SUBROUTINE geo_coord_from_arcview_coord
...
END MODULE geo_coord_class
Ereditarietà o estensione: è la relazione più potente (e anche più lacunosa in F90) e si ha quando una classe ("figlio") eredita le variabili e i metodi di un'altra classe ("genitore") avendo la possibilità di estenderne le capacità, aggiungendo cioè nuove variabili ed eventualmente nuovi metodi.

Esempio di una classe staz_sond che estende le caratteristiche della classe staz aggiungendo la capacità di trattare osservazioni con molteplici livelli verticali come i radiosondaggi.

Si tratta in questo caso di una relazione di tipo "is": una stazione di radiosondaggio "è" un tipo di stazione.

F90 non implementa l'ereditarietà in maniera nativa, per cui è possibile emularla includendo un'istanza della classe "genitore" nel "figlio", così come si fa per la composizione, e implementando, nel figlio, i metodi del padre come chiamate a tali metodi. Anche così resta comunque la limitazione che le variabili della classe padre sono accessibili solo con un doppio livello di indirezione istanza%padrenelfiglio%varnelpadre e non semplicemente istanza%varnelpadre.

MODULE staz_class
IMPLICIT NONE

TYPE staz
  REAL :: obs
  ...
END TYPE staz

INTERFACE fa_qualcosa
  MODULE PROCEDURE staz_fa_qualcosa
END INTERFACE

CONTAINS

FUNCTION staz_init(...) RESULT(this)
...
TYPE(staz) :: this

...
END FUNCTION staz_init

SUBROUTINE staz_fa_qualcosa(this)
TYPE(staz) :: this

...
END SUBROUTINE staz_fa_qualcosa

END MODULE staz_class

MODULE staz_sond_class
USE staz
IMPLICIT NONE

TYPE staz_sond
  TYPE(staz) :: staz
  REAL, POINTER :: soundobs(:), livelli(:)
  INTEGER :: tipo_liv
  ...
END TYPE staz_sond

INTERFACE fa_qualcosa
  MODULE PROCEDURE staz_sond_fa_qualcosa
END INTERFACE

CONTAINS

FUNCTION staz_sond_init(...) RESULT(this)
...
TYPE(staz_sond) :: this

this%staz = staz_init(...)
...
END FUNCTION staz_sond_init

SUBROUTINE staz_sond_fa_qualcosa(this)
TYPE(staz_sond) :: this

CALL fa_qualcosa(this%staz)
END SUBROUTINE staz_fa_qualcosa

END MODULE staz_class
Classi Friend: una classe b può avere la necessità di accedere agli elementi privati di un'altra classe a per estenderne le capacità in maniera più potente; questo si realizza in C++ dichiarando in a la classe b come friend, dandole in questo modo poteri speciali non concessi ad una generica classe. Una cosa del genere è realizzabile in F90 dichiarando entrambe le classi all'interno dello stesso MODULE, in questo modo una classe può accedere a tutte le variabili e i metodi dell'altra compresi quelli dichiarati come PRIVATE (e viceversa).
MODULE time_class
IMPLICIT NONE

! Classe datetime con i propri membri privati
TYPE datetime
  PRIVATE
  INTEGER :: minutes
END TYPE datetime

! Classe timedelta con i propri membri privati
TYPE timedelta
  PRIVATE
  INTEGER :: minutes
END TYPE timedelta

INTERFACE OPERATOR(-)
  MDULE PROCEDURE datetime_diff
END INTERFACE

CONTAINS

...

! Un metodo della classe datetime 
! accede ai membri privati di timedelta
FUNCTION datetime_diff(this, that) RESULT(diff)
TYPE(datetime), INTENT(in) :: this, that
TYPE(timedelta) :: diff

diff%minutes = this%minutes - that%minutes

END FUNCTION datetime_diff

END MODULE time_class