Il 0% ha trovato utile questo documento (0 voti)
35 visualizzazioni7 pagine

Classi e Files

Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
35 visualizzazioni7 pagine

Classi e Files

Copyright
© © All Rights Reserved
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 7

1.

CLASSI E FILES
A prescindere dal tipo di database utilizzato, uno dei punti di forza di
WinDev è sicuramente l'analisi. L'analisi può essere vista come una mera
descrizione dei files che costituiscono il database (o i database) utilizzati nel
progetto, con i relativi campi che costituiscono i record di ogni files.
Ma nell'analisi è possibile definire anche viste, queries, triggers, constraints,
stored procedure e stored function. Per affrontare questi argomenti
bisognerebbe aprire un discorso troppo lungo, relativo alla programmazione
SQL, pertanto mi soffermo solo sugli aspetti che descrivono la interazione tra i
files del database e le classi.

UML come generatore di classi


Windev propone un sistema di UML (Unified Modeling Language) molto
completo che affronta tutti gli aspetti della rappresentazione di un progetto
realizzato da un team di sviluppatori. Dei vari UML di Windev, qui parleremo
solo del più ovvio e banale: il Class Diagram. In un progetto complesso
andrebbero però realizzati almeno l'Object Diagram, l'Activity Diagram, Il
Deployment Diagram e, spesso, il Collaboration Diagram.
Il Class Diagram serve per mantenere organizzate le nostre classi. Questo
UML è in grado di analizzare i files del nostro database e di generare le
corrispondenti classi. Le modifiche al database (ad esempio l'aggiunta di un
nuovo campo) modifica anche la corrispondente classe generata dal Class
Diagram.
Esempio. Poniamo che nel nostro database esista un file Clienti con dentro
alcuni campi (ClientiID, Nome, Cognome, Indirizzo). Tramite il Class Diagram
possiamo generare automaticamente la classe Clienti:
ClassClienti is Class
ClientiID is int
Nome is string
Cognome is string
Indirizzo is string
... procedure constructor e destructor
END
Già questo ci permette di generare un bel po' di codice in automatico. Ed
ora che abbiamo la classe Clienti (che in pratica rappresenta un record del
nostro database) possiamo scrivere una procedura che carica un record dal file.
Ad esempio una Procedure GetCliente alla quale passerò un parametro che mi
identifica il record da caricare (tipo l'ID del cliente). Non importa come carico il
record (posso usare un data source ed usare uno statement sql, posso fare una
HReadSeekFirst, posso usare una query definita nell'analisi...): alla fine della
fiera ciò che scriverò sarà qualcosa simile a questo:
cli is ClassClienti
FileToMemory(cli,Clienti)
In pratica ho preso il mio record 'Clienti' (poteva anche essere un data
source o altro) ed ho infilato i dati nella istanza della mia ClassClienti. E, per
inciso, la ClassClienti la potevo chiamare anch'essa 'Clienti' (ci pensa WinDev
ad evitare ambiguità)
Fino a qui niente di straordinario. Ma cosa succede se la mia classe clienti
non coincide perfettamente con l'insieme dei dati letto? Poniamo ad esempio
che nel file Clienti esista un campo Provincia dove salvo solo i 2 caratteri della
provincia e poi ho un altro file Provincie dove ai 2 caratteri associo il nome
completo della città. Potrei costruire una query SQL che prende i dati da
entrambe le tabelle e mi troverei nel datasource un campo 'NomeProvincia' che
non esiste nella mia classe clienti...
Bene, in questo caso non succede proprio niente. WinDev esegue
un'associazione by name. Se i nomi coincidono, vengono associati e
valorizzati, altrimenti vengono saltati.
Questo significa che posso aggiungere alla mia ClassClienti un membro
“NomeProvincia is string” che non esiste nel file Clienti ma che viene
comunque valorizzato dalla mia query. E, soprattutto, il generatore UML non si
confonde. Aggiunge semplicemente il nuovo campo alla suo diagramma UML
come campo aggiuntivo.

Lavorando sui database, capita molto spesso di dover caricare più record
alla volta. Ad esempio caricare il gruppo di record che costituiscono le linee di
una fattura. Poniamo di aver definito nell'analisi una query che carica le linee di
una fattura con un determinato ID
HEXECUTE(QRY_CaricaLineeFatt, hQueryDefault, FatturaID)
arrLinee is array of ClassLineaFattura
FileToArray(QRY_CaricaLineeFatt, arrLinee)
Et voilà: tutte le linee di quella fattura sono dentro il nostro array. Ed ogni
elemento dell'array è una classe, con tutti i suoi membri, proprietà e
procedure...
2. Programmazione 3-tier
La programmazione Three-Tier vede una netta separazione tra 3 elementi:
interfaccia (presentazione), elaborazione e accesso ai dati.
1. L'interfaccia non accede ai dati nè dovrebbe elaborarli. Riceve dati dal
secondo livello e li presenta.
2. Il secondo livello esegue l'elaborazione sui dati. Riceve i dati dal terzo
livello, li elabora, li passa al primo livello se serve visualizzarli, li raccoglie
dal primo livello, eventualmente li verifica e li passa al terzo livello per il
salvataggio.
3. Il terzo livello è l'unico che conosce come accedere ai dati. Lui sa come
"parlare" al database, o al sistema di dati da cui leggere / scrivere
(normalmente un database, ma potrebbe essere un webservice, un foglio
di calcolo, un file di testo...)
Ovviamente un sistema 3-tier perfetto nel mondo reale spesso non esiste,
talvolta esigenze elaborative o di performance costringono a mischiare un po' i
levelli e pertanto è possibile incontrare sistemi 3-tier in cui il secondo livello
esegue accessi in sola lettura direttamente ai dati senza passare per il terzo
livello.

Come si traduce tutto questo in classi?


Il primo livello (presentazione) è abbastanza facile. In pratica in un
programma è costituito dalle finestre (o pagine se web) le quali visualizzano i
dati che provengono da una classe (o più classi). Le finestre mettono a video i
dati, probabilmente aspettano input da parte dell'operatore e poi rimettono i
dati nelle classi che dovranno elaborarli.
Un esempio. Prendiamo una finestra per la gestione dell'anagrafica di un
cliente. Nella finestra avremo una classe Cliente che contiene i dati
dell'anagrafica (ok, probabilmente avremo un insieme di classi in quanto il
cliente potrebbe avere più telefoni, più indirizzi etc ma per il momento
facciamola semplice). Attualmente non ci importa sapere come la classe riceve
i dati (del resto, è compito della classe) ma sappiamo che la classe viene
"caricata" di dati e questi dati vanno visualizzati (di solito con sourcetoscreen).
L'operatore esegue le sue modifiche e clicca sul bottone "Salva". I dati vengono
rimessi nella classe (screentosource) e la classe (che costituisce il secondo
livello) eseguirà le necessarie operazioni.
Il secondo livello (elaborazione) come abbiamo visto è costituito dalla
classe, il quale ha lo scopo di elaborare il dati. Ad esempio chiede dei dati al
terzo livello, li passa al primo (finestra), riceve da esso i dati modificati, esegue
tutti i controlli e le elaborazioni del caso e se tutto va bene li passa al terzo
livello affinché vengano salvati. Il secondo livello "parla" col terzo ma non
accede direttamente ai dati. Tecnicamente non dovrebbe nemmeno sapere
come fare, è compito del terzo livello eseguire le operazioni CRUD "fisiche" sui
dati.
Il terzo livello (dati) è teoricamente l'unico che dovrebbe sapere dove
sono i dati e quali operazioni deve eseguire per leggere, scrivere, cancellare o
modificare. Il suo compito è quello di eseguire le letture / scritture dei dati e
passare/ricevere i dati al/dal secondo livello.
Utilizzando il 3-tier non solo diventa molto facile costruire codice altamente
riutilizzabile. Dovete fare una versione ridotta della finestra dell'anagrafica
cliente? Ci mettete 5 minuti in quanto tutto il "codice operativo" è comunque
ad un livello più alto. Cambiate completamente sistema di database? Le uniche
modifiche che dovete fare sono nel terzo livello...

Il terzo livello in pratica


Il concetto è chiaro, ma praticamente come si realizza in windev il terzo
livello? In wxFileManager (wsPerts, windev-us) c'è un ottimo esempio, ma è
opportuno spiegare un po' di concetti e per farlo iniziamo con un misto di classi
e procedure .
Prendiamo il codice che esegue la lettura di un record dal file Clienti:
id is 8 byte int = 123
IF HReadSeekFirst(Clienti, ClientiID, id) THEN...
Se lo volessi generalizzare potrei scrivere un codice come questo (e le
performances sarebbero identiche):
dbName is string = "Clienti"
indexName is string = "ClientiID"
id is 8 byte int = 123
IF HReadSeekFirst(dbName, indexName, id) THEN...
A questo punto posso anche scrivere un procedura (globale) estremamente
generica che esegue la lettura di un record
PROCEDURE LeggiRecord(dbName is string,indexname is string, id, rec)
VariableReset(rec)
IF HReadSeekFirst(dbName, indexName, id) THEN
FileToMemory(rec, dbName)
RESULT True
END
RESULT False

Questa procedura è in grado di leggere un record da un qualsiasi file e


inserire i dati letti in un generico record "rec".
Cli is ClientiClass
IF LeggiRecord("Clienti","ClientiID", 123, Cli) THEN ...

Quanto sopra è il concetto di base, ma se lavoriamo con un minimo di


progettazione possiamo fare molto (ma moooolto!) di meglio.
Anzitutto quandio progettiamo un database, facciamolo con coerenza e
dandoci delle regole. Ad esempio
• il nome del file deve essere sempre al singolare (o al plurale, come
preferite) e quindi il file che contiene i clienti si chiamerà Cliente e non
Clienti.
• Ogni file deve avere un indice univoco automatico di 4 (o 8, come prefite
ma meglio 8) bytes che si chiama come il nome del file seguito da ID:
ClienteID in questo esempio.
• Ogni file deve avere una classe "base" che si chiama come il file che
contiene unicamente la "mappatura" (uml) ed eventualmente delle
proprietà e/o dei membri "di servizio" non presenti nel file ma utili a
scopi elaborativi) vedi sopra il membro NomeProvincia
• Ogni file deve avere una classe per l'elaborazione dei dati che istanzia la
classe base con un nome sempre identico (ad esempio io uso "Rec") ed
espone di base alcuni metodi che hanno lo stesso nome in tutte le classi
(ad esempio Salva per salvare, Elimina, Modifica, Leggi, Nuovo...)
• Ogni classe "elaborazione" deve avere dei membri privati che contengono
il nome del file e il nome dell'indice univoco
Applicando le regole sopra esposte avremo quindi:
Cliente is Class //questa classe "mappa" il file Cliente
ClienteID is 8 byte int
Nome is string
Cognome is string
DataDiNascita is date
NomeProvincia is string //membro non presente nel file
END

ClienteClass is Class
Rec is Cliente
PRIVATE
dbName = "Cliente"
pkIndex is string = "ClienteID" //Primary Key
END

PROCEDURE Leggi(id is 8 byte int)


RESULT LeggiRecord(dbname, pkIndex, id, Rec)
END
PROCEDURE Elimina(id is 8 byte int)
RESULT EliminaRecord(dbname, pkIndex, id)
END
PROCEDURE Nuovo()
VariableReset(Rec)
....altre procedure

In questo modo anzitutto tutte le nosre classi avranno la stessa struttura.


Se devo leggere un record e metterlo a video:
Cli is ClienteClass
IF Cli.Leggi(123) THEN SourceToScreen(MyWindow, "Cli.Rec")
E visto che andrò a costruire tutte le classi nello stesso modo, se devo
operare su un articolo di magazzino avrò la classe ArticoloClass e scriverò
praticamente lo stesso codice
Art is ArticoloClass
IF Art.Leggi(456) THEN SourceToScreen(MyWindow, "Art.Rec")
Praticamente avrò una classe "stub" che contiene già le procedure di base
(Leggi, Elimina, Nuovo...) che semplicemente inserisco nel progetto e vado a
cambiare solo le tre righe dell'intestazione. In pratica per la casse
ArticoloClass:
ArticoloClass is Class
Rec is Articolo
PRIVATE
dbName = "Articolo"
pkIndex is string = "ArticoloID" //Primary Key
END

Classi uguali, metodi uguali, vita più semplice...

Attualmente il nostro terzo livello è però costituito da procedure e non da


classi. Il secondo livello è sufficientemente astratto: non sa come accedere ai
dati ma chiama delle procedure (globali) generiche le quali si occupano di
leggere e scrivere nel database. Tecnicamente è un terzo livello (un po' troppo
"esposto", magari).
Ma se volessimo infilare le nostre procedure generiche in una classe?
Possiamo certamente farlo. Poniamo dunque di creare una classe
"AccessoDBClass" la quale contiene le nostre procedure LeggiRecord,
ScriviRecord, CancellaRecord etc e questa classe la possiamo istanziare (nella
sezione Private) di ogni nostra classe di gestione. La nostra ArticoloClass
pertanto diventerebbe:
ArticoloClass is Class
Rec is Articolo
PRIVATE
dbName = "Articolo"
pkIndex is string = "ArticoloID" //Primary Key
Gest is AccessoDBClass
END
e le relative procedure per l'accesso ai dati diventerebbero qualcosa tipo:
PROCEDURE Leggi(id is 8 byte int)
RESULT Gest.LeggiRecord(dbname, pkIndex, id, Rec)
END

Abbiamo già migliorato il nostro codice. Con questa modifica anzitutto


l'accesso al database può essere fatto solamente attraverso la "Gest" presente
in ogni classe (e quindi da una finestra non posso arrivare al database) e
inoltre le procedure della Gest sono tutte private internamente alla classe. In
pratica non potrò mai scrivere un codice tipo
Art is ArticoloClass
Art.Gest.Leggirecord(...
In quanto Gest è privato, quindi visibile solamente all'interno della classe e
non all'esterno di essa.
Sicuramente un bel passo avanti. Ma abbiamo un problema. Se lavoriamo
con moltissime classi andremo a creare una nuova istanza di Gest
(AccessoDbClass) per ogni classe che usiamo e questo ai compilatori jit (come
quello di windev) non piace molto in quanto andrebbe spesso a ricompilarsi la
tabella delle dipendenze. Fino ad un migliaio di classi va bene, ma poi i tempi
di debug diventano piuttosto alti (una noia dover continuamente aspettare il
compilatore).

Ma per fortuna la gestione OOP prevede anche l'ereditarietà, quindi invece


che istanziarre la classe AccesssoDBClass, la possiamo semplicemente
ereditare:
ArticoloClass is Class
inherits from AccessoDBClass
Rec is Articolo
PRIVATE
dbName = "Articolo"
pkIndex is string = "ArticoloID" //Primary Key
END

Per il compilatore questo fa una grossa differenza. La classe di accesso ai


dati non è più istanziata ma semplicemente ereditata. E' come se copiassimo il
codice della classe dentro la nostra classe (ok, magari sarebbe stato meglio
metterla nella Private in modo che i metodi pubblici della AccessoDBClass siano
privati alla classe ArticoloClass).
L'ereditarietà ci apre inoltre le porte ai metodi virtuali ed ai metodi protetti,
i quali in sostanza eseguono un override rispetto alla classe ereditata. Questi
metodi risultano molto utili se vogliamo che (alcuni) metodi della classe
ereditata siano direttamente accessibili all'esterno cella classe che la eredita,
magari eseguendo del codice specifico prima o dopo l'esecuzione del codice
nella classe ereditata.
Ma qui il discorso sarebbe un po' lungo. Sono stanco e me ne vado a letto.

Potrebbero piacerti anche