Το προφίλ μας στο Google Plus
0

Ανάπτυξη εφαρμογών σε C# για το .NET [μέρος 3ο]

Μετά από τη βαρετή για κάποιους αναφορά στις βασικές προγραμματιστικές δομές, μπορούμε να προχωρήσουμε σε πιο σύνθετα και πιο ενδιαφέροντα ζητήματα. Θυμόσαστε που λέγαμε ότι η C# αποτελεί μια κατεξοχήν αντικειμενοστρεφή γλώσσα προγραμματισμού; Σε αυτό το άρθρο θα ασχοληθούμε αποκλειστικά με αυτή την πλευρά της και θα δούμε τα πιο ενδιαφέροντα χαρακτηριστικά που προσφέρει.

Στο ξεκίνημα της σειράς διατυμπανίσαμε ότι η C# τιμά με το παραπάνω το χαρακτηρισμό της αντικειμενοστρεφούς γλώσσας. Μη νομίζετε ότι πρόκειται για μια ψευδή υπερβολή, με την οποία προσπαθήσαμε να σας δελεάσουμε. Στο παρόν άρθρο θα εξετάσουμε τα αντικείμενα και το χειρισμό τους στη C# και, στο τέλος, είμαστε σίγουροι ότι θα έχετε πειστεί για την ειλικρίνειά μας ;) Μάλιστα, επειδή μέχρι στιγμής το έχουμε παρακάνει με τη θεωρία, θα περάσουμε αμέσως στο προκείμενο.

Όλα σε …τάξεις
Κάθε φορά που θέλουμε να δημιουργήσουμε ένα αντικείμενο επιστρατεύουμε τον τελεστή new. Ουσιαστικά, με τη βοήθειά του κατασκευάζουμε ένα στιγμιότυπο της κλάσης στην οποία ανήκει το επιθυμητό αντικείμενο. Η δημιουργία μιας κλάσης στη C# γίνεται με τη λέξη κλειδί class:

class Dog {
} 

Στο παράδειγμα ορίσαμε μια κλάση με το όνομα Dog, η οποία ωστόσο δεν περιλαμβάνει κανένα μέλος. Κατά τα γνωστά, μέλη μιας κλάσης μπορούν να αποτελούν μεταβλητές ή και συναρτήσεις, που ονομάζονται μέθοδοι. Οι μεταβλητές ορίζονται με τον τρόπο που εξετάσαμε στο προηγούμενο μέρος της σειράς. Με την ίδια λογική δηλώνονται και οι μέθοδοι, μόνο που εξαιτίας της πιο σύνθετης φύσης τους απαιτούνται μερικά παραπάνω στοιχεία. Δείτε τη γενική μορφή της δήλωσης μιας μεθόδου:

τροποποιητής_πρόσβασης τύπος_επιστροφής όνομα_μεθόδου (τύπος_παραμέτρου1 όνομα_παραμέτρου1 ...)

Οι τροποποιητές πρόσβασης είναι ίδιοι με αυτούς που χρησιμοποιούνται στις μεταβλητές και δεν χρειάζεται να προσθέσουμε κάτι. Αν η μέθοδος δεν επιστρέφει τιμές, ο τύπος επιστροφής πρέπει να οριστεί σε void. Διαφορετικά, ο τύπος επιστροφής πρέπει να ταυτίζεται με τον τύπο της επιστρεφόμενης μεταβλητής. Ας εμπλουτίσουμε την κλάση Dog με μερικά μέλη:

class Dog {
   public int legs = 4 ;
   public void Bark(int times){
      // code to make some noise ;)
   }
}

Τώρα σίγουρα η κλάση μας είναι πιο ολοκληρωμένη, αν και εξακολουθεί να έχει αμφίβολη χρησιμότητα. Η πρόσβαση στα μέλη ενός αντικειμένου πραγματοποιείται με τον τελεστή της τελείας, σε αντίθεση με την PHP στην οποία χρησιμοποιείται ο τελεστής “βελάκι” (->, προέρχεται από τη C++). Ας δούμε τώρα τη δημιουργία ενός αντικειμένου Dog, καθώς και την πρόσβαση στα μέλη του.

Dog myDog = new Dog();
myDog.Bark(3);
int howManyLegs = myDog.legs;
myDog.legs = 3;

Όπως στις περισσότερες γλώσσες αντικειμενοστρεφούς προγραμματισμού, για την αρχικοποίηση των αντικειμένων στη C# προσφέρονται ειδικές μέθοδοι που ονομάζονται constructors — τουλάχιστον στο χωριό του γράφοντα (Σ.τ.Ε. Α, κι άλλος από το Λευκαντί Ευβοίας!) Οι μέθοδοι κατασκευής (constructors) έχουν το ίδιο όνομα με την κλάση, είναι δημόσια προσβάσιμες, δέχονται παραμέτρους, αλλά δεν έχουν τύπο επιστροφής. Ας δούμε ένα παράδειγμα, εξελίσσοντας περαιτέρω την κλάση Dog:

class Dog{
   public int legs;

   public Dog() { legs = 4; }

   public Dog(int legs) { this.legs = legs; }

   public void Bark(int times) {
      // code to bark for "times" times ;)
   }
}

Στις παραπάνω γραμμές, εκτός από τη δημιουργία του constructor συναντάμε και τη λέξη κλειδί this. Με τη βοήθειά της μπορούμε να αναφερόμαστε στις μεταβλητές του εκάστοτε στιγμιότυπου της κλάσης. Στις ίδιες γραμμές κώδικα βλέπουμε κι ένα παράδειγμα υπερφόρτωσης (overloading). Να θυμίσουμε ότι η υπερφόρτωση αποτελεί ένα χαρακτηριστικό που απαντάται σε αρκετές γλώσσες αντικειμενοστρεφούς προγραμματισμού. Η υπερφόρτωση επιτρέπει να ορίσουμε μεθόδους με το ίδιο όνομα μέσα στην ίδια κλάση, οι οποίες όμως θα πρέπει να διαφοροποιούνται είτε ως προς το πλήθος είτε ως προς τον τύπο των παραμέτρων που δέχονται. Η Python και η PHP, παρ’ ότι υλοποιούν χαρακτηριστικά του αντικειμενοστρεφούς προγραμματισμού, δεν υποστηρίζουν την υπερφόρτωση μεθόδων. Αυτή την έλλειψη προσπαθούν να την αντισταθμίσουν με τη χρήση των προαιρετικών παραμέτρων, κάτι που βελτιώνει την κατάσταση αλλά σε καμία περίπτωση δεν μπορεί να αντικαταστήσει πλήρως τη λειτουργικότητα της υπερφόρτωσης. Από την έκδοση 4.0 η C# υποστηρίζει κι αυτή τις προαιρετικές παραμέτρους και, σε συνδυασμό με την υπερφόρτωση, γίνεται ιδιαίτερα ευέλικτη γλώσσα. Αξιοποιώντας τη δυνατότητα ορισμού προαιρετικών παραμέτρων, οι δύο constructors που είδαμε προηγουμένως θα μπορούσαν να γραφτούν ως εξής:

   ...
   public Dog(int legs = 4) { this.legs = legs; }
   ...

Έχοντας ορίσει κατ’ αυτόν τον τρόπο τη μέθοδο constructor, μπορούμε να δημιουργήσουμε αντικείμενα της κλάσης Dog με τους ακόλουθους τρόπους:

...
// δημιουργία σκύλου με 4 πόδια (προεπιλογή)
Dog myDog1 = new Dog() ; 

// δημιουργία σκύλου με 6 πόδια
Dog myDog2 = new Dog(6) ;

// δημιουργία σκύλου με 5 πόδια
Dog myDog3 = new Dog(legs: 5) ; 
... 

Παρατηρείστε ότι στο τρίτο παράδειγμα (δημιουργία αντικειμένου myDog3) επιλέξαμε να κατονομάσουμε την παράμετρο στην οποία δίνουμε τιμή. Αυτή η δυνατότητα παρέχεται προς αποφυγή των πιθανών μπερδεμάτων, όταν μια μέθοδος έχει δύο ή περισσότερες προαιρετικές παραμέτρους. Αξιοποιώντας την μπορούμε να παραλείπουμε κάποιες από τις προαιρετικές παραμέτρους, χωρίς να ανησυχούμε για το που θα καταλήξουν οι τιμές που δίνουμε.

Φυσικά, εκτός από τις μεθόδους δημιουργίας, η C# προβλέπει και τις μεθόδους καταστροφής (destructors). Το όνομα των συγκεκριμένων μεθόδων ξεκινά πάντα με το χαρακτήρα ~ (tilde) και κατά τα άλλα είναι πανομοιότυπο με εκείνο της εκάστοτε κλάσης. Ας επιστρέψουμε όμως στο παράδειγμα με την κλάση Dog, για να δούμε στην πράξη τον ορισμό ενός destructor. Όπως θα δείτε, η μέθοδος καταστροφής αξιοποιεί τη μέθοδο γαυγίσματος. Έτσι, κάθε φορά που θα καταστρέφουμε ένα αντικείμενο της κλάσης Dog, αυτό θα γαυγίζει δύο φορές. Κάπως έτσι πρέπει να είχαν προγραμματίσει και τα σκυλάκια στο παλιό αλλά αξέχαστο Command and Conquer ;)

...
   public ~Dog() {
      Bark(2);
}
...

Η άμεση πρόσβαση στις μεταβλητές ενός αντικειμένου μπορεί να αποτελέσει το αίτιο για πολλά ύπουλα bugs και, τέλος πάντων, εγκυμονεί σοβαρούς κινδύνους. Αυτό το πρόβλημα μπορεί να αποφευχθεί με τη χρήση κατάλληλων μεθόδων, οι οποίες τροποποιούν τις “εσωτερικές” στο αντικείμενο μεταβλητές ή επιστρέφουν τις τιμές τους. Προφανώς, αυτές οι μέθοδοι χειρίζονται τις μεταβλητές του αντικειμένου με τον προβλεπόμενο τρόπο και η χρήση τους εξασφαλίζει τον ασφαλή χειρισμό των μεταβλητών. Αν προγραμματίσατε ποτέ σε Java, είναι σίγουρο ότι θα γνωρίζετε τις ειδικές μεθόδους με τα προθέματα get και set, που είτε επιστρέφουν είτε τροποποιούν τις τιμές των μεταβλητών ενός αντικειμένου. Η C# υποστηρίζει τη χρήση αυτών των μεθόδων, αλλά προσφέρει και έναν ακόμα πιο χρήσιμο μηχανισμό. Αναφερόμαστε στις λεγόμενες ιδιότητες (properties), που επιτρέπουν τη φαινομενικά ελεύθερη πρόσβαση στην τιμή μιας μεταβλητής, ενώ ταυτόχρονα προσφέρουν την ασφάλεια των μεθόδων get και set. Με τις ιδιότητες, λοιπόν, απολαμβάνουμε τα οφέλη των get και set, αλλά με έναν τρόπο που δεν θυμίζει την κλήση μεθόδων. Ουσιαστικά, οι ιδιότητες αποτελούν κατάλληλα κατασκευασμένες μεθόδους, οι οποίες δεν δέχονται παραμέτρους και χρησιμοποιούνται σαν μεταβλητές! Αυτές οι “ειδικές” μέθοδοι μπορούν να περιλαμβάνουν έως και δύο μπλοκ κώδικα, από τα οποία το ένα ξεκινά με τη δεσμευμένη λέξη get και το άλλο με τη λέξη set. Ας αφήσουμε όμως τη θεωρία κι ας δούμε τον ορισμό μιας ιδιότητας στην πράξη:

class Dog{
   private int legs; 
   ...   
   // constructor, destructor and other methods
   ...   
   public int Legs {
      set {
         this.legs = value;
      }
      get {
         return this.legs;
      }
   }
}

Στο παράδειγμα ορίζουμε την ιδιότητα Legs που, όπως βλέπετε, αναλαμβάνει το χειρισμό της μεταβλητής legs. Ακριβώς γι’ αυτό το λόγο, ο τύπος επιστροφής της μεθόδου-ιδιότητας ταυτίζεται με τον τύπο της μεταβλητής. Σημειώστε ότι η μεταβλητή legs είναι private και δεν εκτίθεται στο περιβάλλον του αντικειμένου, ενώ η ιδιότητα Legs είναι public. Έτσι, η πρόσβαση στη μεταβλητή legs θα είναι δυνατή μόνο μέσω της ιδιότητας Legs. Στον ορισμό της εν λόγω ιδιότητας έχουμε τοποθετήσει τα μπλοκ set και get, που τροποποιούν ή επιστρέφουν την τιμή της μεταβλητής. Στο μπλοκ set αξίζει να παρατηρήσετε τη χρήση της λέξης κλειδί value, η οποία αντιπροσωπεύει την οποιαδήποτε εισερχόμενη τιμή. Ας δούμε τώρα κι ένα παράδειγμα χρήσης της συγκεκριμένης ιδιότητας:

Dog myDog = new Dog();
int myLegs = myDog.Legs; // λήψη της τιμής της μεταβλητής legs
myDog.Legs = 5; // καθορισμός τιμής στην μεταβλητή legs

Στην πρώτη γραμμή δημιουργούμε το αντικείμενο myDog και στη δεύτερη χρησιμοποιούμε την ιδιότητα Legs, για να πάρουμε την τιμή της μεταβλητής legs. Με τη σύνταξη που χρησιμοποιούμε, θα πίστευε κανείς ότι το αντικείμενο myDog περιλαμβάνει κάποια μεταβλητή τύπου public με το όνομα Legs. Στην πραγματικότητα, όμως, το συγκεκριμένο αντικείμενο περιλαμβάνει μια μεταβλητή τύπου private και μάλιστα με διαφορετικό όνομα (legs). Στην τρίτη γραμμή τροποποιούμε τη μεταβλητή legs, κάνοντας και πάλι χρήση της ιδιότητας Legs.

Ελπίζουμε η ομοιότητα των ονομάτων Legs και legs να μη σας μπέρδεψε. Επιλέξαμε αυτά τα ονόματα επειδή οι ιδιότητες έχουν κατά παράδοση το ίδιο όνομα με τις αντίστοιχες μεταβλητές, διαφοροποιώντας μόνο το αρχικό γράμμα. Δεν θέλαμε να σας παιδέψουμε :)

Κληρονομικότητα
Από μια γνήσια αντικειμενοστρεφή γλώσσα δεν θα μπορούσε να λείπει το χαρακτηριστικό της κληρονομικότητας. Η C#, όπως και η Java, υποστηρίζει την κληρονομικότητα από μια γονική κλάση προς τις κλάσεις-απογόνους (υποκλάσεις), που ονομάζεται κι απλή κληρονομικότητα. Για να δηλώσουμε ότι μια κλάση αποτελεί επέκταση ή απόγονο μιας άλλης κλάσης, χρησιμοποιούμε το χαρακτήρα της άνω και κάτω τελείας. Συγκεκριμένα, κατά τη δήλωση την κλάσης-απογόνου, αμέσως μετά το όνομά της τοποθετούμε το χαρακτήρα colon και στη συνέχεια το όνομα της γονικής κλάσης. Ας εξετάσουμε λοιπόν την κλάση Labrador, η οποία αποτελεί απόγονο της κλάσης Dog:

class Labrador : Dog {
   public void Bark(int times) {
       // code to imitate the voice of Labradors...
   }
}

Η κλάση Labrador διαθέτει όλα τα μέλη της κλάσης Dog, όπως για παράδειγμα τη μεταβλητή legs με την ομώνυμη ιδιότητα.

Βιβλιοθήκες
Από τη συναναστροφή μας με τις γλώσσες προγραμματισμού Python και PHP έχουμε εξοικειωθεί με την ιδέα της χρήσης και της δημιουργίας βιβλιοθηκών. Οι βιβλιοθήκες δεν είναι τίποτε άλλο παρά μια συλλογή από αντικείμενα και μεθόδους, έτοιμες προς χρήση. Κατά την ορολογία της C#, οι συλλογές αυτού του είδους δεν ονομάζονται βιβλιοθήκες αλλά namespaces (χώροι ονομάτων) και συγγενεύουν με τα λεγόμενα πακέτα (packages) της Java. Για να ενσωματώσουμε μια κλάση σε ένα namespace, αρκεί να περικλείσουμε ολόκληρο τον ορισμό της μέσα σε brackets, να χρησιμοποιήσουμε τη δεσμευμένη λέξη namespace και να δώσουμε ένα επιθυμητό όνομα. Ας δούμε για μια ακόμα φορά τις κλάσεις Dog και Labrador, τις οποίες θα υπάγουμε στο namespace ονόματι Animals. Στα ονόματα των namespace, όπως και σ’ αυτά των κλάσεων, συνηθίζεται το πρώτο γράμμα να είναι κεφαλαίο:

namespace Animals {
   class Dog {
      // κώδικας της κλάσης Dog
   }
   class Labrador : Dog {
      // κώδικας της κλάσης Labrador
   }
}

Για λόγους οργάνωσης και τάξης, ένα namespace μπορεί να χωρίζεται σε επιμέρους τμήματα. Στο προηγούμενο παράδειγμα θα μπορούσαμε να τοποθετήσουμε τις κλάσεις μας σε έναν υποχώρο του namespace Animals, με το όνομα Mammals (θηλαστικά). Κάτι τέτοιο θα μπορούσε να γίνει ως εξής:

namespace Animas.Mammals {
   class Dog {
      // κώδικας της κλάσης Dog
   }
   class Labrador : Dog {
      // κώδικας της κλάσης Labrador
   }
}

Για να χρησιμοποιήσουμε μια κλάση που ανήκει σε κάποιο namespace, διαφορετικό από αυτό στο οποίο ανήκει ο κώδικας που γράφουμε τη δεδομένη στιγμή, πρέπει να χρησιμοποιήσουμε την λέξη κλειδί using, ακολουθούμενη από το όνομα του “ξένου” namespace. Πρακτικά, η λειτουργία της using είναι παρόμοια με τη λειτουργία του import που χρησιμοποιούμε σε άλλες γλώσσες για την εισαγωγή μια βιβλιοθήκης:

using Animals.Mammals;

namespace MyProgram {
   class Program {
      public static void Main(string[] args) {
         Dog aDog = new Dog();
         aDog.Bark();
      }
   }
}

Προφανώς, για να χρησιμοποιήσουμε τις κλάσεις ενός namespace αυτό πρέπει να διαθέτει τον αντίστοιχο κώδικα — είτε στην πηγαία του μορφή είτε μεταγλωττισμένο. Στη δεύτερη περίπτωση, τα μεταγλωττισμένα namespaces διατίθενται σε πακέτα DLL που περιλαμβάνουν κώδικα CIL. (Περισσότερα γι’ αυτό το είδος κώδικα αναφέρουμε στο πρώτο μέρος της παρούσας σειράς.) Αυτά τα πακέτα όμως δεν έχουν καμία σχέση με τα γνωστά DLL που συνοδεύουν τα Windows και τα οποία περιέχουν binary code.

Δημιουργία βιβλιοθήκης
Η δημιουργία μιας βιβλιοθήκης μέσα από το περιβάλλον του SharpDevelop είναι πανεύκολη υπόθεση. Αφού ανοίξουμε το περιβάλλον ανάπτυξης, επιλέγουμε το “New Solution” από το μενού File και από την αριστερή περιοχή του παραθύρου εμφανίζεται, κάνουμε κλικ στον κόμβο C#.

Η δημιουργία της βιβλιοθήκης ξεκινάει επιλέγοντας την κατάλληλη μορφή project, μέσα από το περιβάλλον του SharpDevelop.

Κατόπιν, στο δεξιό τμήμα του παραθύρου επιλέγουμε το “Class Library”. Στη συνέχεια αρκεί να ορίσουμε το όνομα και την τοποθεσία αποθήκευσης της βιβλιοθήκης μας. Όταν πατήσουμε το κουμπί Create μεταφερόμαστε στο περιβάλλον συγγραφής, όπου μπορούμε ν’ αρχίσουμε την ουσιαστική δουλειά. Όπως θα δείτε, στο ξεκίνημα του κώδικα προστίθενται από προεπιλογή κάποιες βιβλιοθήκες του .Net. Μπορούμε να γράψουμε τον κώδικά μας λίγες γραμμές χαμηλότερα. Εμείς αφαιρέσαμε το τμήμα κώδικα MyClass, που είχε προστεθεί αυτόματα, και δημιουργήσαμε μια κλάση με όνομα ArrayUtils. Μέσα σε αυτή την κλάση γράψαμε μια μέθοδο που “αναποδογυρίζει” τα στοιχεία ενός πίνακα, καθώς και μια κλάση ονόματι Calcs. Μέσα στη δεύτερη κλάση δημιουργήσαμε μια μέθοδο που υπολογίζει το τετράγωνο του δοσμένου αριθμού. Ας αφήσουμε τις περιγραφές κι ας δούμε τον κώδικα που γράψαμε.

namespace MyLib {

   public class ArrayUtils {
      public int[] Reverse(int[] array) {
         int len = array.Length;
         int[] reversedArray = new int[len];
         for(int i=0; i<len; i++) {
            reversedArray[i] = array[len - i - 1];
         }
         return reversedArray;
      }
   }
   public class Calcs {
      public int Squared(int number) {
         return number * number;
      }
   }
}

Ο παραπάνω κώδικας δεν έχει κάτι το ιδιαίτερο σε σχέση με όσα έχουμε πει ως τώρα και δεν χρειάζεται να σπαταλήσουμε χρόνο για την εξέτασή του. Θα προχωρήσουμε απευθείας στη διαδικασία της μεταγλώττισης της βιβλιοθήκης μας. Για να πετύχουμε κάτι τέτοιο, μεταβαίνουμε στο μενού Build κι επιλέγουμε είτε το “Build Solution” είτε το “Build MyLib”.

Η διαδικασία της μεταγλώττισης ξεκινά αμέσως μόλις επιλέξουμε το χτίσιμο του έργου. Τα μηνύματα του compiler εμφανίζονται στην περιοχή Output, του παραθύρου του SharpDevelop.

Σε αυτή τη φάση, βλέπετε, καθώς δεν έχουμε δημιουργήσει πάνω από ένα Project μέσα στο ίδιο Solution, οι δύο επιλογές είναι πρακτικά ισοδύναμες. Η μεταγλώττιση θα ξεκινήσει αμέσως κι εφόσον δεν υπάρχουν συντακτικά σφάλματα στον κώδικα, θα εμφανιστεί ένα μήνυμα επιτυχίας στην περιοχή Output του παραθύρου του SharpDevelop. Η βιβλιοθήκη μας μόλις δημιουργήθηκε! Μπορούμε να βρούμε το σχετικό DLL μέσα στον κατάλογο \bin\Debug, στην τοποθεσία όπου επιλέξαμε για την αποθήκευση του Project. Όπως δηλώνει το όνομα του φακέλου, η βιβλιοθήκη που θα βρούμε εκεί δεν έχει την τελική μορφή (προς διανομή), καθώς ενσωματώνει κώδικα και δεδομένα που διευκολύνουν το debug. Για να δημιουργήσουμε την τελική εκδοχή της βιβλιοθήκης, πρέπει να μεταβούμε στο μενού Set Configuration εντός του μενού Build, και να επιλέξουμε το Release αντί του προεπιλεγμένου Debug. Μετά, αρκεί να πραγματοποιήσουμε μια εκ νέου μεταγλώττιση. Η τελική μορφή της βιβλιοθήκης μας θα βρίσκεται στον κατάλογο \bin\Release, στην τοποθεσία αποθήκευσης του Project.

Το DLL με τη βιβλιοθήκη μας μοιάζει με τα κλασσικά DLLs των Windows. Στο εσωτερικό του, ωστόσο, διαφοροποιείται σε μεγάλο βαθμό. Αντί για κώδικα binary περιέχει κώδικα CIL, για το .Νet framework.

Στο επόμενο τεύχος θα ξεκινήσουμε την κατασκευή ολόκληρων προγραμμάτων, στηριζόμενοι στη θεωρία που παρουσιάσαμε μέχρι τώρα. Κατά τα γνωστά, θα σας προτείναμε να μην περιμένετε άπραγοι αλλά να ξεκινήσετε από τώρα τα δικά σας πειράματα. Είμαστε σίγουροι άλλωστε ότι όλο και κάποια ιδέα θα έχετε προς υλοποίηση.

Leave a Reply

You must be logged in to post a comment.

Σύνδεση

Αρχείο δημοσιεύσεων