□ M I C R O FO CUS Programmazione Orientata agli Oggetti per Sviluppatori COBOL Introduzione ii Micro Focus The Lawn 22-30 Old Bath Road Newbury, Berkshire RG14 1QN UK http://www.microfocus.com Copyright © Micro Focus 1984-2013. All rights reserved. MICRO FOCUS, the Micro Focus logo and Visual COBOL are trademarks or registered trademarks of Micro Focus IP Development Limited or its subsidiaries or affiliated companies in the United States, United Kingdom and other countries. All other marks are the property of their respective owners. 2013-05-09 2 | An Introduction to Object-Oriented Programming for COBOL Developers Contents An Introduction to Object-Oriented Programming for COBOL Developers ....................................................................................................................... 4 Classes and Methods ......................................................................................................... 4 Objects ............................................................................................................................... 7 Creating an Instance of a Class .......................................................................................... 7 Constructors ....................................................................................................................... 9 Properties ........................................................................................................................... 9 Method Visibility ............................................................................................................... 10 Local Data ........................................................................................................................ 10 Data Types ....................................................................................................................... 11 Inheritance ....................................................................................................................... 12 Interfaces ......................................................................................................................... 15 Class Names .................................................................................................................... 17 Intrinsic Types .................................................................................................................. 17 The .NET and JVM Frameworks ....................................................................................... 18 Reflection ......................................................................................................................... 20 Calling COBOL From Other Languages ........................................................................... 20 What Next? ...................................................................................................................... 24 Contents | 3 Una Introduzione alla Programmazione Object-Oriented per Sviluppatori COBOL Panoramica Questa guida fornisce una introduzione di base alla Object-Oriented Programming (OOP) per sviluppatori COBOL che usano Micro Focus Visual COBOL o Micro Focus Enterprise. Nella guida ci sono sezioni per ogni concetto chiave della programmazione ad oggetti. Il COBOL gestito è il termine per indicare il COBOL procedural per .NET COBOL e JVM COBOL con le estensioni per avvalersi delle caratteristiche dei frameworks gestiti. Questo include la sintassi objectoriented (OO) che consente l’accesso a librerie contenenti una gran quantità di funzioni da usare nelle applicazioni e molto altro. Per ottenere il massimo risultato del COBOL gestito, bisogna capire i concetti della programmazione object-oriented. Codice di Esempio Questa guida contiene frammenti di codice semplice per illustrare alcuni concetti della programmazione orientate agli oggetti. In COBOL. Durante la lettura puoi scrivere e compilare il codice ed usare il debug del Visual COBOL o dell’Enterprise Developer. Devi aver installato uno dei seguenti prodotti: • • • • Micro Focus Visual COBOL per Visual Studio 2010 o per Visual Studio 2012 Micro Focus Visual COBOL per Eclipse per Windows Micro Focus Enterprise Developer per Visual Studio 2010 Micro Focus Enterprise Developer per Eclipse Nota: Visual COBOL e Enterprise Developer non possono essere installati sulla stessa macchina. Per eseguire gli esempi, crea un progetto JVM COBOL in Eclipse o una applicazione console .NET COBOL gestito in Visual Studio. Classi e Metodi Il cuore della Programmazione Object-Oriented Programming è la nozione di una classe. Una classe è definite per incapsulare le informazioni di una particola re entità. Una classe contiene dati associate con l’entità e le operazioni, chiamate metodi, che consentono l’accesso e la manipolazione dei dati. A parte l’incapsulamento dei dati, le classis ono molto utili per convertire I programmi procedurali alle nuove tecnologie di codifica gestita. Questo è un semplice esempio di classe COBOL: class-id MyClass. method-id SayHello static. linkage section. 01 your-name pic x(10). procedure division using by value your-name. display "hello " & your-name end method. end class. 4 | An Introduction to Object-Oriented Programming for COBOL Developers Prima di guardare ai dettagli della classe, vediamo come puoi invocare il singolo metodo contenuto in questa classe: program-id. TestMyClass. procedure division. invoke type MyClass::SayHello(by value Scot") end program. Nota: Per eseguire questo esempio in Visual Studio, hai bisogno di specificare uno Startup object per la tua applicazione di console gestita. Seguire i passi: 1. 2. 3. 4. 5. 6. 7. In Visual Studio, click-destro la soluzione in Solution Explorer. Click Add > New Item, e click COBOL class. Specifica un nome come MyClass.cbl, e click Add. Allo stesso modo, aggiungi un programma COBOL con il nome TestMyClass.cbl. Aggiungi I due pezzi di codice esempio rispettivamente alla classe e al programma. Click Project > ProjectName Properties e click la linguetta Application. Imposta Startup object a TestMyClass: 8. Click Debug > Start Without Debugging per eseguire il programma. Come ti puoi aspettare, il risultato del programma è: Hello Scot In questo esempio puoi vedere come un programma procedural COBOL può anche usare la semantica objectoriented semantics anche se esso non è una classe. Diamo un’occhiata ai dettagli della classe, class-id MyClass. MyClass è il nome della classe. Quando referenzi una classe, devi specificare il nome, nello stesso modo di quando referenzi un programma COBOL. An Introduction to Object-Oriented Programming for COBOL Developers | 5 La nostra clesse non contiene dati, ma ha un metodo chiamato SayHello: method-id SayHello static. Notare che c’è una clausola static associate a questo metodo. Questa keyword è importante perchè ci consente di chiamare il metodo senza dover creare una istanza della classe. Le istanze della classe sono chiamate objects e li vedremo tra poco. Metodi statici e dati statici possono essere utili al momento, ma valgono solo per una istanza dei dati. Metodi statici possono operare soltanto su dati statici. Il resto della dichiarazione del metodo dovrebbe essere familiar perchè identico al programma procedurale che prende un singolo parametron come argomento per il programma: linkage section. 01 your-name pic x(10). procedure division using by value your-name. display "hello " & your-name end method. Diamo un’occhiata al programma procedural che invoca questo metodo: invoke type MyClass::SayHello(by value "Scot") In merito al c dice, notiamo quanto segue: • • La keyword invoke è sinonimo di CALL ma è usata nel contesto di una chiamata ad un metodo di una classe. La keyword type ci permette di specificare il nome della classe a cui ci riferiamo. • La sintassi :: ci permette di riferirci al metodo specifico della classe che vogliamo invocare. Prima di approfondire, vediamo altri aspetti della sintassi: invoke type MyClass::SayHello(by value "Scot") La keyword type è una parte nuova del linguaggio COBOL introdotta con il Visual COBOL e semplifica il modo di riferirsi ed invocare I metodi. Per illustrare quanto detto. Di seguito c’è il programma equivalente conforme alla sintassi ISO: program-id. TestMyClass repository. class MyClass as "MyClass". procedure division. invoke MyClass "SayHello" using by value "Scot" end program. L’ISO richiede l’uso della sezione Repository e le virgolette ai nomi dei metodi. In questo semplice esempio la sintassi addizionale, sebbene verbose, non degrada segnificativamente la leggibilità del programma. Comunque nella realtà, la sistassi addizionale insieme ad altri requisiti dell’ISO, diventa molto velocemente illeggibile e rende il COBOL difficile da usare per applicazioni COBOL gestite. Il Visual COBOL semplifica anche altri aspetti del linguaggio – Diamo un’occhiata ad un paio di casi nel nostro esempio: invoke type MyClass::SayHello(by value "Scot") Può diventare: invoke type MyClass::SayHello("Scot") Se il metodo contiene ulteriori argomenti, questi possono apparire come: invoke type MyClass::SayHello("Scot", 37, "Bristol Street") Anche se le virgole che separano i parametri sono opzionali. Nei prossimi esempi, useremo questa sintassi abbreviata. Il metodo può essere semplificato anche come segue: 6 | An Introduction to Object-Oriented Programming for COBOL Developers method-id SayHello static. linkage section. 01 your-name pic x(10). procedure division using by value your-name. display "hello " & yourname end method. Può diventare: method-id SayHello static. procedure division using by value your-name as string. display "hello " & yourname end method. Due cose importanti sono cambiate in questo esempio: • • La linkage section hesplicita è stata rimossa e l’argomento della linkage è stato definito inline con l’istruzione procedure division using. L’argomento della pic x(10) è stato sostituito da una referenza alla stringa. String è un tipo COBOL predefinito che troviamo nelle classi stringa di JVM e .NET. Strings contiene una varietà di metodi e sono usate per contenere dati Unicode di lunghezza arbitraria. Il compilatore può convertire molti dei tipi predefiniti come stringhe in tipi COBOL come pic x – questo lo vedremo in dettaglio più avanti. Per i prossimi esempi, adottiamo questa convenzione di definizione degli argomenti inline. Questo è possibile solo quando usiamo tipi predefiniti gestiti. I record COBOL devono essere definiti sempre nel modo usuale. Oggetti Il nostro semplice esempio fin’ora ci ha aiutato a dimostrare i concetti base di una classe, ma il valore della Programmazione Orientata agli Oggetti non è ancora del tutto evidente. La potenza della OOP vien fuori quando incapsuliamo dati in una classe, definiamo metodi che eseguono azioni su questi dati, e quindi creiamo isanze della classe al momento dell’esecuzione. Creare un’istanza della classe corrisponde alla creazione di un oggetto. Ogni oggetto mantiene un insieme separato di dati sui quali agiscono i metodi.. Puoi creare molte istanze di una classe, e quindi puoi avere molti oggetti, ognuno distinto da tutti gli altri definiti nel sistema. Questi dati sono chiamati dati istanza (instance data). Per esempio, se consideriamo il tipo di dati di cui possiamo aver bisogno in una semplice classe per un conto bancario, questo può essere il numero di conto, il saldo e qualche modalità per poter memorizzare le transazioni. Al momento dell’esecuzione, possiamo creare un unico oggetto per ogni cliente su cui dobbiamo operare, dove ogni oggetto ha dati distinti da tutti gli altri clienti della banca. Creare una Instanza di una Classe Cambiamo un po’ la nostra classe e vediamo come possiamo creare una istanza di un oggetto: class-id MyClass. working-storage section. 01 your-name pic x(10). method-id SayHello. procedure division. display "hello" & yourname end method. An Introduction to Object-Oriented Programming for COBOL Developers | 7 end class. La variabile your-name definite nella working-storage section è il dato instanza per questa classe: working-storage section. 01 your-name pic x(10). Per invocare il metodo SayHello ora lo facciamo usando un oggetto piuttosto che la classe. Di seguito vediamo come creiamo questa istanza: program-id. TestMyClass 01 an-obj type MyClass. procedure division. set an-obj to new MyClass invoke an-obj::SayHello end program. Questa è la dichiarazione dell’oggetto, formalmente nota come object reference: 01 an-obj type MyClass. Se proviamo ad invocare il metodo SayHello a questo punto, otterremmo un errore di Sistema al runtime poichè l’oggetto non è stato ancora creato. Questa è la line ache crea l’oggetto: set an-obj to new MyClass La parola chiave NEW crea il nostro oggetto. NEW richiede che noi specifichiamo il tipo di oggetto che vogliamo crear. Questo può sembrare strano poichè abbiamo già detto il tipo dell’oggetto quando lo abbiamo dichiarato, ma più Avanti potremo vedere che un oggetto può essere dichiarato di un certo tipo, ma al momento dell’esecuzione referenziato come un tipo diverso. L’istruzione SET è usata frequentemente in OOP ed è il sinonimo della move ma è applicata agli oggetti. E’ possibile dichiarare un’altra referenza all’oggetto ed assegnargli il valore di an-obj come segue: set another-obj to an-obj In questo caso, another-obj ora contiene un riferimento a an-obj. E’ importante notare che mentre abbiamo due riferimenti ad un oggetto, abbiamo soltanto una istanza di titpo MyClass, e entrambi gli oggetti another-obj e an-obj si riferiscono ad esso. Se invochiamo il metodo SayHello su an-obj e another- object, essi potrebbero operare entrambi in antagonismo sugli stessi dati nelle workingstorage section. L’unico modo per creare un nuovo oggetto è di usare la NEW: set another-obj to new MyClass Al momento la nostra classe ha un problema. Se invochiamo il metodo SayHello, potrebbe stampare Hello, poiché il dato your-name non ha ricevuto ancora alcun valore. Ci son diverse soluzioni a questo problema. Una soluzione è durante la creazione dell’oggetto altrimenti conosciuta come construction. La nostra classe non fa nulla in fase di costruzione, ma può farlo se creiamo un metodo chiamato New. Costruttori method-id New. procedure your-name method. division to a- set endusing by value a-name as string. -name 8 | An Introduction to Object-Oriented Programming for COBOL Developers Ogni volta che è creato un oggetto, il Sistema al run-time invoca automaticamente il metodo New sulla classe. Se non lo codifichi, il compilatore lo crea automaticamente. Nel nostro metodo di sopra, non solo abbiamo definito un costruttore, ma abbiamo anche specificato che esso ha un parametro. Quindi dobbiamo cambiare il nostro codice che crea l’oggetto: set an-obj to new MyClassC'Scot") Questo codice potrebbe essere scritto anche come segue: set an-obj to MyClass::New("Scot") Quello che abbiamo fatto è stato di fornire un modo per poter inizializzare il nostro oggetto e di prendere l’argomento passato al costruttore ogni volta che è creato un oggetto del tipo MyClass. Overloading di Metodo In ogni caso è possibile avere versioni multiple del metodo New, ognuna corrispondente a differenti argomenti che possono essere passati quando è creato l’oggetto. Questo è chiamato overloading di metodo poichè il nome del metodo rimane lo stesso, ma gli argomenti accettati da ogni metodo sono differenti. Possiamo usare questa caratteristica dell’overloading di metodo per reintegrare il cosiddetto default constructor (costruttore di default), altrimenti conosciuto come il parameterless constructor (costruttore senza parametri). Per ottenere ciò, dobbiamo codificare un nuovo metodo New. method-id New. procedure division. move all 'x' to yourname end method. Questo ci ha permesso di creare l’oggetto sia fornendo un parametron, sia usando il costruttore di default che non ha argomenti, ma ci permette ancora di inizializzare I nostri dati in working-storage section. Proprietà La nostra classe ha alcuni dati ad essa associate, una stringa chiamata your-name. Questo dato non è direttamente accessibile dal programma usando la classe, così come la working-storage di un programma non è accessibile ad un altro programma. Le Proprietà consentono di rendere visibile i dati all’utente della classe. Attualmente il nostro unico dato è il seguente: 01 your-name pic x(10). Possiamo trasformare il dato in Proprietà, come segue: 01 your-name pic x(10) property. Come tale, puoi accedere a questa proprietà attraverso una referenza all’oggetto: display an-obj::your-name La parola chiave property ci consente non solo di prendere il valore del dato, ma anche di impostarlo: set an-obj::your-name to "Scot" Comunque possiamo impedire di far impostare il valore a chiunque scrivendo come segue: 01 your-name pic x(10) property with no set. I caratteri maiuscolo/minuscolo sono importanti nel COBOL gestito. Il maiuscolo/minuscolo del nome della proprietà è determinato nella dichiarazione. Attualmente è scritta tutta in minuscolo. Possiamo cambiare il An Introduction to Object-Oriented Programming for COBOL Developers | 9 nome come segue: 01 your-name pic x(10) property as "Name" display an-obj::Name La clausola static si applica anche alle proprietà: 01 dataitem pic x(10) property as "DataItem" static. Quando viene richiamato il dato, di esso esiste sempre e solo una istanza indipendentemente da quandi oggetti sono stati create. I dati statici sono usati nella stessa classe, non abbiamo bisogno di una istanza per potervi accedere: set MyClass:DataItem to "some text" Visibilità del Metodo I metodi che abbiamo definito fin’ora sono tutti pubblici, e questo è il default in COBOL. Un metodo pubblico significa che esso può essere invocato mediante la referenza all’oggetto. Comunque, per molte classi abbiamo bisogno di metodi che non vogliamo che siano visibili all’esterno della classe. Questi netodi son chiamati metodi privati. Per dichiarare un metodo privato, si usa la parola chiave private: method-id ProcessData private. Questo metodo non può essere invocato tramite la referenza all’oggetto, il che genererebbe un errore del compilatore. E’ possibile richiamare questo metodo dall’interno della stessa classe, dentro ad un metodo pubblico: method-id DoSomething public. procedure division using by value a-name as string. invoke self::ProcessData end method. end class. Da notare l’uso della parola chiave special self. In questo caso significa "richiamare un metodo chiamato ProcessData che è definito in questa classe". Notiamo inoltre che dichiariamo esplicitamente questo metodo come public nella sua dichiarazione. Ciò non è richiesto poichè è la visibilità di default, ma può essere utile da specificare nelle fasi iniziali. Dati Locali Quando scriviamo programmi in COBOL procedurale dobbiamo dichiarare tutti I dati nella workingstorage section. Quando lavoriamo con le classi, usiamo ancora la working-storage section per I dati associate alla classe, ma possiamo anche definire dati che sono usati solo dai metodi, o i cosiddetti dati locali. Ci sono tre modi per definire variabili locali: Nella LocalSection Nel seguente esempio, mylocalvar è una variabile locale ed esiste solo per questo metodo: method-id ProcessData private. local-storage section. 01 mylocalvar binary-short. 10 | An Introduction to Object-Oriented Programming for COBOL Developers procedure division. Usando l’istruzione DECLARE end method. Nell’esempio seguente, mylocalvar è definito usando l’istruzione DECLARE. La visibilità della variabile così definita è solo all’interno del metodo dopo la dichiarazione: method-id ProcessData private. local-storage section. procedure division. declare mylocalvar as binary-short Definire una variabile inline end method. Con questo Sistema possiamo creare una variabile locale chiamata counter come parte dell’istruzione PERFORM. Il tempo di vita e la visibilità di questa variabile sono associati con l’esecuzione dell’istruzione PERFORM. In altre parole, non possiamo riferirci a counter dopo l’istruzione END PERFORM. method-id ProcessData private. local-storage section. procedure division. perform varying counter as binary-long from 1 by 1 until counter > 10 display counter end perform. end method. Tipi di Dati Fin’ora le nostre classi hanno usato i tipi di dati COBOL come ad esempio pic X. Tutti i tipi di dati che si usano nel COBOL procedurale sono supportati nel COBOL gestito. Alcuni tipi di dati, quali pic X o gruppi di record non sono riconosciuti in .NET e JVM. Per favorire la transizione dai tipi COBOL algi altri linguaggi, c’è un insieme di tipi predefiniti che sono riconosciuti da .NET e JVM, e mappati direttamente in tipi di .NET e JVM. Questi tipi sono elencati nell’articolo Type Compatibility of Managed COBOL with Other Managed Languages nella Visual COBOL documentation. Qui ci sono tre esempi di dichiarazione dello stesso tipo, un intero con segno a 16 bit: 01 vall binary-short. 01 val2 pic s9(4) comp-5. 01 val3 type System.Int16. In C# e in Java, la dichiarazione equivalente potrebbe usare un tipo chiamato short. Per specificare un intero senza segno a 16 bit, puoi usare: 01 val1 binary-short unsigned. 01 val3 type System.UInt16. In C#, la dichiarazione equivalente potrebbe usare un tipo ushort. In Java, comunque questo non ha equivalenti poichè Java non supporta i tipi senza segno. La cosa da tener presente quando lavori con le classi, è che qualsiasi dato vuoi usare nelle chiamate alle classi, che sia un argomento per un metodo o una proprietà, la cosa migliore da fare è di usare I tipi predefiniti del COBOL riportati nella tabella dell’articolo Type Compatibility of Managed COBOL with Other An Introduction to Object-Oriented Programming for COBOL Developers | 11 Managed Languages nella documentazione. Comunque in uno degli esempi precedenti, non abbiamo fatto come spiegato prima. Infatti abbiamo definito un item pic X come proprietà. Così facendo il compilatore la traduce in un tipo intrinseco String, non nel campo pic X. Quando un utente della proprietà legge o imposta il dato, questo è convertito implicitamente dal tipo nativo COBOL al tipo .NET o JVM, in questo caso, un string. Dichiarando un item di gruppo come proprietà, viene convertito l’intero gruppo come un tipo string di .NET o JVM. I tipi numerici come comp-5 sono convertiti all’equivalente più prossimo. Ereditarietà L’Ereditarietà è una parte importante della OOP. Essa consente di creare una nuova classe estendendo le funzionalità di una classe già esistente. Se lo desideriamo, possiamo anche modificare il comportamento della classe che stiamo estendendo. Consideriamo l’esempio del conto bancario precedente. Possiamo immaginare che conti di vario tipo (conto corrente, libretto di risparmio, ecc.) condividono dati comuni come il numero di conto ed il saldo, ma l’operazione di prelievo può essere diversa a seconda del tipo di conto. Un conto corrente può aver la necessità di controllare se c’è un limite dell’importo che si può prelevare e un libretto di risparmio dovrà controllare altri fattori che influenzano gli interessi maturati, come ad esempio la quantità di denaro che può essere ritirata entro un determinato periodo. Una considerazione importante vedremo più avanti è che tutto ciò che è l'utilizzo di questi oggetti, diciamo il bancomat, non dovrebbe essere necessario per determinare il tipo di conto è affrontare e quindi eseguire l'elaborazione diverso. Si vuole semplicemente eseguire un'azione di ritiro contro qualunque conto oggetto che sta usando Una considerazione importante che vedremo più Avanti sarà riferita al bancomat, il quale potrebbe non avere la necessità di determinare il tipo di conto che sta facendo un prelievo per eseguire l’operazione in modo diverso. Si vuole semplicemente eseguire l’operazione di prelievo indipendemente dal conto in questione.. Per ora vediamo come estendere una classe e modificarne il comportamento. Per testare questo in Visual Studio, creiamo un progetto Class Library gestita e aggiungiamo le classi come file separate al progetto – click destro sul progetto in Solution Explorer e click Add > New Item > COBOL Class. Questa è una classe di conto bancario semplificato: class-id BankAccount. working-storage section. 01 account-number pic 9(8) property as "AccountNumber". 01 balance float-long property. method-id Withdraw. procedure division using amount as float-long returning result as condition-value. *> Process for general withdrawal from a bank account end method. end class. Questo tipo di classe chiamata BankAccount, è spesso riferita come base class poichè essa definisce la base della gerachia di classi che nascono da essa. Creiamo ora una nuova classe per rappresentare una specializzazione di un conto bancario, un libretto di 12 | An Introduction to Object-Oriented Programming for COBOL Developers risparmio: class-id SavingsAccount inherits type BankAccount. method-id Withdraw override. procedure division using amount as float-long returning result as condition-value. end method. *> Specialized process for Savings withdrawal. end class. Nel definire una nuova classe per libretto di risparmio, abbiamo usato la clausola inherits per estendere una classe esistente. Tutti I membri pubblici (metodi, proprietà, campi define come public) della classe base diventano parte della nuva classe. Quindi un oggetto che è di tipo SavingsAccount, ha anche la proprietà AccountNumber, e un metodo Withdraw che sono stati ereditati dalla classe base BankAccount. La nostra classe SavingsAccount ha anche un metodo Withdraw che gestirà il modo differente in cui sarà effettuato il prelievo rispetto al conto corrente. Per ottenere queste differenze di comportamento del metodo della classe base usiamo la parola chiave override. Il significato di questa parola chiave, diventerà evidente più avanti. Creiamo un’altra specializzazione della classe BankAccount, un conto di debito: class-id DebitAccount inherits BankAccount. method-id Withdraw override. procedure division using amount as float-long returning result as condition-value. end method. *> Specialized process for Debit withdrawal. end class. Questa classe per il conto di debito è creato esattamente nello stesso modo della classe per il libretto di risparmio. Essa estende la classe conto bancario e tutti i membri pubblici della classe conto bancario diventano parte della nuova classe. La classe DebitAccount ha anche un metodo Withdraw che sovrascrive il metodo Withdraw della classe BankAccount e definisce come eseguire il prelievo nel conto di debito. Questo metodo comunque è completamente differente dal metodo Withdraw della classe SavingsAccount. Ora abbiamo tre classe nella nostra gerarchia – una classe base (BankAccount) e due classi (SavingsAccount and DebitAccount) che sono derivate da essa, ognuna delle quali ha un metodo differente Withdraw. Guardiamo gli effetti dell’istanziazione degli oggetti e dell’invocazione dei metodi: program-id. TestBankAccounts. 01 accountl type BankAccount. 01 account2 type BankAccount. procedure division. set account1 to new SavingsAccount set account1::AccountNumber to 12345678 set account1::balance to 500.00 An Introduction to Object-Oriented Programming for COBOL Developers | 13 set account2 to new DebitAccount set account2::AccountNumber to 87654321 set account2::balance to 100.00 end program TestBankAccounts. Notiamo la dichiarazione del nostro tipo di oggetto, BankAccount, e la creazione di esso una volta come SavingsAccount ed un’altra come DebitAccount. Possiamo fare ciò perchè sia SavingsAccount che DebitAccount ereditano (o discendono) da BankAccount. La validità di questo modo di procedure non è molto evidente in questo esempio, ma il prossimo esempio sarà di maggiore aiuto: method-id PerformWithdrawal. procedure division using by value amount as floatlong account as type BankAccount. if not account::Withdraw(amount) *> perform error condition display "not true" else display "true" end-if end method. In questo caso, un metodo riceve un argomento di tipo BankAccount sul quale esegue un operazione di prelievo. Il metodo non ha bisogno di conoscere I diversi tipi di conto, ma qualunque sia il tipo di conto ad esso passato è eseguito il corretto tipo di metodo di prelievo (Withdraw) associato ad ogni tipologia di conto, sia esso un conto corrente o un libretto di risparmio.. Possiamo chiamare quel metodo con un argomento oppure account1 o account2: invoke SomeClass::PerformWithdrawal(100,account1) invoke SomeClass::PerformWithdrawal(200,account2) Abbiamo passato account1 e account2 e per ognuno di essi è eseguito il metodo Withdraw appropriato con SavingsAccount e DebitAccount. Questa è una caratteristica molto utile della OOP e separa i dettagli di implementazione per i clenti che usano le classi. In alternative possiamo dire che ci permette di estendere il Sistema aggiungendo nuovi tipi di conto bancari minimizzando l’impatto sul codice esistente. Sia per JVM che per .NET, puoi ereditare da una classe base, ma ovviamente la classe base, a sua volta può ereditare da un’altra classe e così via. Se una classe derivata deve chiamare un metodo definito nella classe base, può farlo usando la parola chiave super. Per esempio, possiamo chiamare il metodo BankAccount WithDraw da dentro la classe SavingsAccount in questo modo: invoke super::Withdraw(100) super può essere usata non solo per chiamare un metodo che abbiamo sovrascritto nella classe derivata, ma possiamo chiamare ogni metodo pubblico definito nella gerarchia delle classi che abbiamo ereditato.. Interfaces Le classi e l’ereditarietà ci consentono di separare I dettagli di implementazione dagli utenti della classe, ma c’è un altro aspetto della OOP che ci può aiutare ulteriormente a separare l’implementazione - 14 | An Introduction to Object-Oriented Programming for COBOL Developers l’interfaccia. Una interfaccia, come una classe, definisce una serie di metodi e possibili dati, ma a differenza di una classe, non fornisce l’implementazione nel metodo. Questo perchè lo scopo dell’interfaccia è semplicemente quello di definire il comportamento che deve avere una classe – comportamento inteso nel senso dei metodi e proprietà sulla classe. Qui c’è un esempio di interfaccia: interface-id ErrorHandler. method-id notifyError. procedure division using by value error-code as binary-short. end method. method-id notifyWarning. procedure division using by value warning-code as binary-short. end method. end interface. Questa interfaccia definisce giusto due metodi che, come possiamo intuire, possono essere usati per gestire un qualche tipo di errore. Per definire una classe che gestisce questa interfaccia, dobbiamo realizzare l’interfaccia: class-id MyErrorHandler implements type ErrorHandler. method-id notifyError. procedure division using by value error-code as binary-short. *> display message box to the user end method. method-id notifyWarning. procedure division using by value warning-code as binary-short. *> depending on the configuration, ignore this or print *> it to the console end method. end class. La parola chiave implements definisce l’interfaccia che vogliamo realizzare per questa classe e il compilatore controllerà che che tutti i metodi siano realizzati correttamente. Diversamente dalla derivazione di una classe, che può essere effettuata su una singola classe, puoi implementare tutte le interface che desideri in una singola classe. Possiamo creare una istanza della nostra classe della nostra classe e, poichè abbiamo realizzato l’interfaccia ErrorHandler, possiamo passare una referenza all’oggetto di questa classe ad ogni codice che si aspetta di lavorare con l’interfaccia ErrorHandler. class-id ProcessData. working-storage section. 01 error-handler-list List[type ErrorHandler] value null static. An Introduction to Object-Oriented Programming for COBOL Developers | 15 method-id RegisterErrorHandler static. procedure division using by value error-handler as type ErrorHandler. if error-handler-list = null create error-handler-list end-if. write error-handler-list from error-handler end method. method-id NotifyErrorHandlers static. local-storage section. 01 error-handler type ErrorHandler. procedure division using by value error-code as binary-short. perform varying error-handler thru error-handler-list invoke errorhandler::notifyError(error-code) end-perform end method. method-id DoProcessing. 01 error-code binary-short value 1. procedure division. *> do something and, possibly, call NotifyErrorHandlers when something *> goes wrong *> ... invoke self::NotifyErrorHandlers(error-code) *> ... end method. end class. program-id. TestProgram. working-storage section. 01 error-handler type MyErrorHandler. 01 processData type ProcessData. procedure division. set error-handler to new MyErrorHandler invoke type ProcessData::RegisterErrorHandler(by value errorhandler) set processData to new ProcessData invoke processData::"DoProcessing" end program TestProgram. Guardiamo meglio questa codifica poichè ci sono alcuni concetti nuovi. Prima di tutto, abbiamo una classe, ProcessData. Qualche volta, durante l’esecuzione del metodo DoProcessing, ProcessData informeremo a chi è interessato che c’è stato un errore. Questo può essere fatto chiamando l’interfaccia ErrorHandler usando il metodo NotifyErrorHandlers. Questa classe può notificare a diversi clienti per registrar la loro implementazione di interfaccia usando il metodo RegisterErrorHandler. Ogni interfaccia è memorizzata in una lista di oggetti. Non Andiamo a vedere come è fatta questa lista, ma assumiamo che sia una classe fornitaci dalle classi del framework di .NET o JVM. Quando si verifica un errore ed è chiamato il metodo NotifyErrorHandlers, il codice usa le caratteristiche della sintassi del COBOL gestito che consente di iterare attraverso la collezione delle interfacce di gestione degli errori nella lista. Ogni iterazione risulta essere nel reference della memoria locale error-handler impopstata al prossimo elemento della lista. Il codice semplicemente richiama il metodo NotifyError e l’implementazione di questo decide cosa fare. Il TestProgram costruisce una istanza di MyErrorHandler e lo passa come argomento al metodo RegisterErrorHandler. Questa chiamata implica una conversion implicita dal tipo MyErrorHandler, una classe, al tipo ErrorHandler, una interfaccia. Nomi di Classe Finora, le nostre classi hanno avuto nomi semplici ma questo può presto portare a scontri con le classi 16 | An Introduction to Object-Oriented Programming for COBOL Developers create da altre persone. Per risolvere questo, creiamo semplicemente classi con nomi lunghi impiegando l'uso di namespace (spazio dei nomi), che non è altro che una convenzione per la denominazione classi. Qui c’è un nome di classe corretto com.acme.MyClass MyClass è una classe differente dalla seguente: com.yourcompany.MyClass Ogno cosa che riporti al nome della classe è considerate un namespace, se lavoriamo in .NET, o un package name, se lavoriamo in JVM. In questo caso, i namespace sono com.acme e com.yourcompany. Questa convenzione ci consente di creare classi con lo stesso nome, ma senza conflitti tra di loro. Mentre questa è una convenzione, il Compilatore fornisce regole e sintassi per poter lavorare con i namespace in modo semplice e, infatti vi sono alcune regole che riguardano l’accessibilità delle classi nell’ambito dei namespace. Quando ti riferisci ad una classe che ha un namespace, devi usare il suo nome intero. Per esempio: 01 an-obj type com.acme.MyClass. 01 another-obj type com.yourcompany.MyClass. Il compilatore COBOL fornisce la regola ILUSING che ti consente di usare il nome abbreviato della classe: $set ILUSING(com.acme) Quando usi questa regola in un file sorgente, importi il namespace nel tuo progetto e quindi referenzi la classe con il suo nome abbreviato, come segue: 01 an-obj type MyClass. Mentre questo è generalmente una pratica accettata, poicè I nomi delle classi possono diventare molto lunghi, dovresti evitare di importare namespace. Inoltre, se si verifica che due classi hanno lo stesso nome è necessario distinguerle specificando il nome completo di esse. Tipi Intrinsici Il Compilatore COBOL riconosce molte classi in .NET e JVM e non è necessario specificare il loro completo. Le due classi di cui parleremo sono Object (System.Object in .NET e java.lang.Object in JVM) e String (System.String in .NET e java.lang.String in JVM). Object è importante percè in definitive tutte le classi ereditano da questo tipo, sia che lo specifichi o meno. Perciò un oggetto può essere convertito a questo tipo. String è comunemente usata per memorizzare dati Unicode. Sia in JVM che in .NET, la stringa, una volta creata, è considerata immutabile. Qualunque metodo invochi sulla classe String, il risultato è un nuovo oggetto stringa: 01 strl type System.String. 01 str2 String. 01 str string. Tutte le dichiarazioni di sopra sono equivalenti in .NET. Notiamo che non vi è la necessità di chiamare il metodo New quando creiamo una stringa: set strl to "Hello World" Puoi combinare l’uso delle stringhe con I campi pic X come segue: 01 a-pic-x pic X(10) value "something". display a-pic-x & str1 & "blah" Qui c’è un esempio dell’uso di uno dei molti metodi sulle stringhe in .NET COBOL: set str1 to str1::Replace("foo", "bar") Notiamo che assegniamo il risultato di questo metodo all’oggetto originale. Se non lo facciamo, str1 potrebbe restare inalterata. Lo stesso esempio, per JVM COBOL è il seguente: set str1 to str1::replace("foo", "bar") In .NET e JVM, la sola differenza tra questi metodi è il maiuscolo - String.Replace per .NET e String.replace per JVM. The .NET and JVM Frameworks .NET e JVM sono entrambi ricchi di classi per ogni tipo di funzionalità. Conoscerle tutte è molto impegnativo, anche in termini di tempo, ma ve ne sono alcune che dovresci conoscere facilemtente, in particolare le classi collection. Per aiutarci ad illustrare l’utilità di questi frameworks, vediamo come l’aritmetica per la data ed il tempo diventa semplice nel seguente esempio in .NET COBOL: working-storage section. 01 dt1 type System.DateTime. 01 dt2 type System.DateTime. 01 ts type System.TimeSpan. set dt1 to type System.DateTime::Now invoke System.Threading.Thread::Sleep(1000) set dt2 to type System.DateTime::Now set ts to dt2 - dt1 display ts Vediamo cosa abbiamo realizzato. Prima di tutto abbiamo dichiarato tre riferimenti ad oggetti, due oggetti DateTime e un oggetto TimeSpan. La classe DateTime fornisce un esteso insieme di routine per la manipolazione delle date e del tempo. Per avere un’idea di queste possibilità, vedi la descrizione delle classi in Microsoft's MSDN. La classe TimeSpan è usata quando calcoliamo la differenza tra due oggetti DateTime. Nella prima linea di codice, inizializziamo l’oggetto dti usando un metodo statico sulla classe System.DateTime, Now. Ci sono molti altri modi per inizializzare un oggetto DateTime ma questo è un modo conveniente per ottenere la data e l’orario corrente: set dti to type System.DateTime::Now Nella linea seguente, usiamo di nuovo un metodo static, questa volta per fermare il thread corrente per un periodo specificato. Puoi invocare la routine Micro Focus CBL_THREAD_SLEEP per ottenere lo stesso risultato, come segue: invoke System.Threading.Thread::Sleep(1000) La linea successive inizializza il nostro secondo oggetto DateTime: set dt2 to type System.DateTime::Now La linea successive dimostra una caratteristica del compilatore del codice gestito COBOL Compiler 18 | An Introduction to Object-Oriented Programming for COBOL Developers chiamata opertore di overloading: set ts to dt2 - dt1 L’operatore di overloading è una caratteristica avanzata della ed è opportuno dargli un’occhiata. Quando definiamo una classe, è anche possibile fornire una implementazione di alcuni operatori aritmetici, come add e subtract. La classe DateTime definisce diversi operatori per le date ed il tempo ed il loro confronto. Mentre puoi eseguire operazioni aritmetiche sugli oggetti usando l’operatore di overload, le classe generalmente fornisco metodi equivalenti che puoi usare direttamente, come nel caso di DateTime. La linea seguente fornisce lo stesso risultato della precedente: set ts to dt2::Subtract(dt1) Entrambi gli approcci sono un oggetto TimeSpan. Questo oggetto contiene il risultato di una espressione aritmetica. Infine, visualizziamo il risultato. Ogni volt ache usi la parola DISPLAY in riferimento ad un oggetto, il COmpilatore invoca automaticamente il metodo ToString che è definito nella classe base Object. Se ricordi, tutte le classi ultimamente discendono da Object. Normalmente il metodo ToString ritorna semplicemente il nome del Type, ma la classe TimeSpan sovrascrive il metodo ToString e ritorna una stringa significativa riguardante l’oggetto TimeSpan: display ts Vediamo ora lo stesso esempio tradotto in JVM COBOL: $set ilusing"java.util.concurrent" program-id. Program1 as "Program1". data division. working-storage section. 01 start-time binary-double. 01 end-time binary-double. 01 interval binary-double. 01 01 01 01 hr binary-double. min binary-double. sec binary-double. ms binary-double. procedure division. *> get the start time set start-time to type System::currentTimeMillis() *> wait a second invoke type Thread::sleep(1000) *> get the end time *> calculate the difference between start and end time set interval to end-time - start-time set hr to type TimeUnit::MILLISECONDS::toHours(interval) set min to type TimeUnit::MILLISECONDS::toMinutes(interval - type TimeUnit::HOURS::toMillis(hr)) set sec to type TimeUnit::MILLISECONDS::toSeconds(interval - type TimeUnit::HOURS::toMillis(hr) - type TimeUnit::MINUTES::toMillis(min)) display String::format("%02d:%02d:%02d", hr, min, sec) goback. end program Program1. Qui ci sono un po’ di cose diverse. La classe equivalente di System.DateTime nel framework .NET è Date in JVM e non ha ubn metodo subtract che può essere sovrascritto. Per questo dobbiamo usare set. La classe .NET System.TimeSpan non ha una alternativa in JVM, perciò dobbiamo usare TimeUnit per calcolare il tempo tra due date. Riflessione Quando compili un programma COBOL per .NET o per JVM, il Compilatore crea un eseguibile conforme alle specifiche della piattaforma. Queste specifiche devono aderire ad ogni specifica di linguaggio che supporta .NET o JVM. Poichè tutti i linguaggi sono conformi allo stesso protocollo, le classi scritte in ogni linguaggio possono essere facilmente integrate. Un altro vantaggio di questa conformità è qualcosa chiamata riflessione. La riflessione è la capacità di esaminare i dettagli sottostanti di un determinato tipo. Con questa capacità è possibile esaminare I metodi forniti da un tipo, gli argomenti per ogni metodo e anche il codice di un metodo. Questa è una caratteristica molto potente del framework ed apre molte possibilità per lo sviluppo di applicazioni. Sebbene possa sembrare non immediatamente ovvio come la riflessione possa essere valutabile, capiamo che essa puà aiutare quando consideriamo come varie tecnologie nel codice gestito possono fare quello chef anno senza conoscere come sono definite le classi. Un esempio dell’uso delle riflessione è il cosidetto Intellisense che è una caratteristica di Visual Studio e Eclipse IDEs che ti assiste mostrandoti una lista di metodi e proprietà disponibili su un dato oggetto. 20 | An Introduction to Object-Oriented Programming for COBOL Developers