Κλάσεις

Εισαγωγή

Ο OOP(Object-Oriented Programming ή Αντικειμενοστρεφής προγραμματισμός) είναι ένας τρόπος ανάπτυξης κώδικα. Πριν τον OOP χρησιμοποιούνταν ο συναρτησιακός προγραμματισμός μέχρι που ο αντικειμενοστρεφής έλυσε πολλά από τα προβλήματα του πρώτου. Στον συναρτησιακό, η ανάπτυξη του προγράμματος γραφόταν σε μερικές ενότητες (modules) κώδικα ή μπορεί και σε μία μόνο (ανάλογα με το πρόγραμμα) και αυτές οι ενότητες εξαρτιόνταν από άλλες. Αν άλλαζες μια γραμμή του κώδικα θα έπρεπε να ξαναγράψεις όλη την ενότητα ξανά και ίσως και όλο το πρόγραμμα. Στον αντικειμενοστρεφή προγραμματισμό, οι developers γράφουν ανεξάρτητα κομμάτια προγράμματος, τις λεγόμενες κλάσεις και κάθε κλάση αναπαριστά μια λειτουργία ενός κομματιού του συστήματος. Έτσι σε ενδεχόμενο πρόβλημα, το μόνο που χρειάζεται να γίνει είναι να αλλάξει αυτό το κομμάτι χωρίς να επηρεάζει τα άλλα τμήματα.

Κλάσεις και αντικείμενα

Μπορεί να μην σας είναι εύκολο να καταλάβετε την ιστορία με τις κλάσεις και τα αντικείμενα αλλά θα κάνω το καλύτερο για να τα εξηγήσω. Πραγματικά, οι κλάσεις και τα αντικείμενα είναι στενά συνδεδεμένα μεταξύ τους και μερικοί αρχάριοι δεν νοιάζονται για να το καταλάβουν καθαρά, οπότε και περνάνε δύσκολα όταν μελετάν την c#!

Η έννοια του αντικειμενοστρεφούς προγραμματισμού παίρνει το μεγαλύτερο μέρος της λειτουργικότητας της από έννοιες της πραγματικής ζωής. Θα παρουσιάσω την έννοια των κλάσεων και των αντικειμένων του κόσμου πρώτα και μετά θα καταλάβετε τις κλάσεις και τα αντικείμενα των υπολογιστών προτού γράψω τίποτα περί αυτών.

Οι κλάσεις και τα αντικείμενα στον κόσμο

Τα πάντα στον κόσμο μας θεωρούνται αντικείμενα. Για παράδειγμα, οι άνθρωποι είναι αντικείμενα, τα ζώα επίσης, τα ορυκτά είναι αντικείμενα, τα πάντα είναι αντικείμενα. Εύκολο, σωστά; Τι γίνεται όμως με τις κλάσεις; Στον κόσμο μας πρέπει να διαφοροποιούμε τα αντικείμενα με τα οποία εμείς ζούμε. Έτσι πρέπει να καταλάβουμε πως υπάρχουν κατατάξεις (classifications, έτσι πήραν το όνομα και την έννοια της κλάσης) για όλα αυτά τα αντικείμενα. Για παράδειγμα, εγώ είμαι αντικείμενο, ο Νίκος επίσης, η Μαρία είναι άλλο αντικείμενο γι αυτό και είμαστε όλοι από την κλάση (η τύπος) άνθρωπος. Έχω έναν σκύλο που ονομάζεται Ricky έτσι είναι αντικείμενο. Ο σκύλος του φίλου μου ονομάζεται Doby, επίσης αντικείμενο και τα δυο από την κλάση Σκύλος. Ένα τρίτο παράδειγμα. Έχω έναν υπολογιστή Pentium III. Αυτό είναι το αντικείμενο. Ο φίλος μου έχει επίσης ένα Pentium IV οπότε και ένα άλλο αντικείμενο από την κλάση Υπολογιστής. Τώρα νομίζω πιάσατε την έννοια της κλάσης και του αντικειμένου αλλά αφήστε με να σας το ξεκαθαρίσω λίγο παραπάνω. Στον κόσμο μας έχουμε μια κατάταξη για τα αντικείμενα και κάθε αντικείμενο πρέπει να είναι από κάποια κατάταξη. Έτσι, μια κλάση είναι ένας τρόπος για να περιγράψουμε μερικές ιδιότητες και λειτουργίες ή συμπεριφορές για μια ομάδα από αντικείμενα. Με άλλα λόγια, η κλάση θεωρείται ως πρότυπο για μερικά αντικείμενα. Έτσι μπορεί να φτιάξω μια κλάση που ονομάζεται «άτομο» έτσι ώστε να είναι ένα πρότυπο που να περιλαμβάνει τις ιδιότητες και τις λειτουργίες ενός ατόμου (πχ σαν ιδιότητες ηλικία, φύλλο κλπ).

Κλάσεις και αντικείμενα του υπολογιστή

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

Κλάσεις και αντικείμενα στην C#

Μια κλάση στην c# θεωρείται ως το πρωταρχικό στοιχείο για οικοδόμηση της γλώσσας. Αυτό που εννοώ είναι ότι κάθε φορά που δουλεύετε με την C# θα δημιουργείτε κλάσεις για να σχηματίζουν το πρόγραμμα. Χρησιμοποιούμε κλάσεις ως ένα πρότυπο για να βάλουμε μέσα τις ιδιότητες και τις λειτουργίες σε ένα δομικό στοιχείο (building block) για μερικά γκρουπ αντικειμένων και μετά τις χρησιμοποιούμε ως πρότυπα για να φτιάξουμε τα αντικείμενα που χρειαζόμαστε. Αλλά αρκετά με την θεωρία. Πάμε να δούμε τα πράγματα στον κώδικα.

Έστω λοιπόν πως θέλουμε να φτιάξουμε το πρότυπο για έναν άνθρωπο. Στο πρόγραμμα μας θέλουμε να μπορούμε να κρατάμε το όνομα του και την ηλικία του. Έχουμε λοιπόν:

class person
    {
        public string Νame;
        public string ΗairColor;
    }

Για την δήλωση λοιπόν της κλάσης person γράφουμε την λέξη class, το όνομα της κλάσης και τις αγγύλες {}. Μέσα στις αγκύλες υπάρχει όλη η υλοποίηση της κλάσης. Πέραν της γνωστής δήλωσης της μεταβλητής name (string name;) παρατηρούμε και το keyword public. Μην ανησυχείτε τώρα για αυτό, θα δούμε αργότερα τον σκοπό του.

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

static void Main(string[] args)
        {
            Person Michael = new Person(); //δημιουργία αντικειμένου τύπου Person με το όνομα Michael
            Person Mary = new Person(); //αντίστοιχα
 
            // Ορισμός τιμών για τις ιδιότητες του Michael
            Michael.Age = 20;
            Michael.HairColor = "Brown";
 
            // Ορισμός τιμών για τις ιδιότητες της Mary
            Mary.Age = 25;
            Mary.HairColor = "Black";
 
            // εκτύπωση της ηλικίας τους
            Console.WriteLine("Michael’s age = {0}, and Mary’s age= {1}", Michael.Age, Mary.Age);
            Console.ReadLine();
        }

Έτσι αρχίζουμε την Main μέθοδο μας φτιάχνοντας 2 αντικείμενα του τύπου Person. Στην συνέχεια αρχικοποιούμε (δίνουμε αρχικές τιμές στις μεταβλητές) για τα αντικείμενα Michael και Mary. Στο τέλος εκτυπώνουμε μερικές τιμές στην κονσόλα.

Όταν δημιουργούμε το αντικείμενο Michael, ο compiler της c# δεσμεύει χώρο για τις 2 μεταβλητές της κλάσης person, για να μπορούμε να κρατήσουμε τις τιμές εκεί. Το ίδιο συμβαίνει και για το αντικείμενο Mary. Έτσι, κάθε αντικείμενο περιέχει διαφορετικά δεδομένα.

 

Properties

Όπως παρατηρήσατε στο προηγούμενο παράδειγμα χρησιμοποιήσαμε τις μεταβλητές με άμεσο τρόπο και τους βάλαμε ότι τιμή θέλαμε, σωστά? Οπότε κάποιο άτομο θα μπορούσε να βάλει στην ηλικία την τιμή 120. Για αυτό το πρόβλημα υπάρχει λύση μέσω των properties.

Οι ιδιότητες προσφέρουν έναν τρόπο να αποκτήσουμε πρόσβαση στις μεταβλητές μιας κλάσης με ασφαλή τρόπο. Ας δούμε το ίδιο παράδειγμα χρησιμοποιώντας properties.

class Person
    {
        private int age;
        private string hairColor;
        public int Age
        {
            get
            {
                return age;
            }
            set
            {
                if (value <= 65 && value >= 18)
                {
                    age = value;
                }
                else
                    age = 18;
            }
        }
        public string HairColor
        {
            get
            {
                return hairColor;
            }
            set
            {
                hairColor = value;
            }
        }
    }

Έκανα μερικές αλλαγές αλλά απλά δείτε τις 2 νέες ιδιότητες που πρόσθεσα εδώ. Μια ιδιότητα αποτελείται από 2 μέρη (accessors). Τον get accessor ο οποίος είναι υπεύθυνος για την ανάκτηση της τιμής της μεταβλητής και τον set accessor ο οποίος είναι υπεύθυνος για την τροποποίηση της τιμής της μεταβλητής. Ο κώδικας του get accessor είναι πολύ απλός και απλά χρησιμοποιούμε το keyword return με το όνομα της μεταβλητής για την επιστροφή της τιμής της. Έτσι ο κώδικας:

         get
            {
                return hairColor;
            }

Επιστρέφει την τιμή της μεταβλητής hairColor.

Ας δούμε πως δουλεύει ο κώδικας με τις properties και μετά ας αναλύσουμε τον set accessor.

static void Main(string[] args)
        {
            Person Michael = new Person();
            Person Mary = new Person();
 
            Michael.Age = 20;
            Michael.HairColor = "Brown";
            Mary.Age = 25;
            Mary.HairColor = "Black";
 
            Console.WriteLine("Michael’s age = {0}, and Mary’s age= {1}", Michael.Age,
            Mary.Age);
            Console.ReadLine();
        }

Δημιούργησα λοιπόν τα ίδια αντικείμενα όπως και πριν. Η μόνη διαφορά είναι πως τώρα χρησιμοποιώ properties για την ανάθεση/τροποποίηση της τιμής αντί για απευθείας πρόσβαση. Κοιτάξτε σε αυτήν την γραμμή κώδικα:

Michael.Age = 20;

Όταν ορίζουμε μια τιμή σε μια property όπως εδώ, η c# χρησιμοποιεί τον set accessor. Το καλό με τον set accessor είναι πως μπορεί να ελέγξει την τιμή πριν την ορίσει στην μεταβλητή και ίσως και να την αλλάξει σε μερικές περιπτώσεις. Όταν ορίζεις μια τιμή σε μια property η c# τοποθετεί αυτήν την τιμή σε μια δεσμευμένη μεταβλητή με το keyword value. Ας ξαναδούμε τον set accessor:

        set
            {
                if (value <= 65 && value >= 18)
                {
                    age = value;
                }
                else
                    age = 18;
            }

Άρα για το παράδειγμα μας η μεταβλητή value έχει την τιμή 20. Αν είχαμε γράψει

Michael.Age = 15;

Η value θα είχε τιμή 15 και άρα στην μεταβλητή age θα έμπαινε η τιμή 18.

This Keyword

Πολλές φορές θέλουμε να αποκτήσουμε πρόσβαση στο ίδιο το αντικείμενο της κλάσης. Αυτό μπορεί να είναι είτε για να δούμε τις ιδιότητες, μεταβλητές και μεθόδους που περιέχει η κλάση μας είτε για να περάσουμε το ίδιο αντικείμενο που δουλεύουμε σαν παράμετρο σε μια μέθοδο. Το keyword this μας επιτρέπει να το επιτύχουμε αυτό.

Άρα στην ουσία
Το keyword this αναφέρεται στο τρέχων στιγμιότυπο της κλάσης. Τα στοιχεία που έχουν δηλωθεί ως static δεν μπορούν να αναφερθούν με το this. To this μπορούμε να το χρησιμοποιήσουμε και για να ξεχωρίσουμε παραμέτρους όπου κρύβουν γενικότερες μεταβλητές. Πχ

private string name;
private string alias;
public Employee(string name, string alias) 
{
   this.name = name;
   this.alias = alias;
}

Variable Scope & Access modifiers

Variable Scope - Εμβέλεια μεταβλητών

Η εμβέλεια των μεταβλητών καθορίζει την ορατότητα της μεταβλητής μέσα στο υπόλοιπο πρόγραμμα. Όταν μια μεταβλητή δηλώνεται μέσα σε μια μέθοδο είναι ορατή μόνο μέσα σε αυτήν. Αυτό σημαίνει πως η μεταβλητή είναι διαθέσιμη μέσα στην ίδια μέθοδο αλλά δεν μπορεί να προσπελαστεί μέσα σε μια άλλη. Υπάρχουν και περιπτώσεις όπου η μεταβλητή δηλώνεται μέσα σε μια loop ή άλλη δομή με braces {}. Σε αυτήν την περίπτωση πάλι είναι ορατή σε εκείνην την περιοχή. Μια ευρύτερη εμβέλεια θα έχουμε όταν δηλώσουμε την μεταβλητή στο επίπεδο της κλάσης. Σε αυτήν την περίπτωση η μεταβλητή θα είναι ορατή σε όλη την κλάση και σε όλες τις μεθόδους.
Γενικότερα, η μεταβλητή είναι ορατή μέσα στα blocks στα οποία έχει δηλωθεί.

class person { 
    string fullScopeString;//ορατό σε όλη την κλάση. 
    void testMethod() { 
        string str;//ορατό μόνο στην μέθοδο test method 
    } 
    void exampleMethod2() { 
        fullScopeString = "test";//μπορώ εδώ να δω το string fullScopeString 
        //str="test2";//ΛΑΘΟΣ - η str είναι ορατή μόνο στην μέθοδο testMethod     } 
    void example3() 
    { 
        int i; 
        for (int i = 0; i < 10; i++) 
        { string s;//ορατό μόνο στην for 
            s = i + "s"; Debug.Write(s);
        } 
    } 
}

Access Modifiers

Όταν δηλώνουμε μια μεταβλητή σε επίπεδο κλάσης μπορούμε να ορίσουμε την προσβασιμότητα της. Έχουμε 3 Access Modifiers. Public, protected, private και μια επιπλέον, την internal. Το παρακάτω πινακάκι μας δείχνει την προσβασιμότητα:

Δήλωση Σημασία
public Η πρόσβαση δεν περιορίζεται
protected Η πρόσβαση περιορίζεται στην ίδια την κλάση και σε τύπους που την κληρονομούν
private Η μεταβλητή είναι προσβάσιμη μόνο μέσα στην ίδια κλάση

 

class person
    {
        private string testStr; //προσβάσιμη μόνο μέσα στην κλάση person 
        private void testMethod()// -- // -- 
        {

        }
        public string str2;//Προσβάσιμο σε όσα μέρη χρησιμοποιούν την κλάση 
        public string getHello()
        {
            return "hello!";
        }

        protected string str3;//προσβάσιμο στην κλάση και σε αυτές που την κληρονομούν. 
    }
    class test
    {
        //contructor 
        public test()
        {
            person p = new person(); p.str2 = "Hello World";//το str2 είναι προσβάσιμο 
            string s = p.getHello();//σωστό. Η get hello είναι public και άρα προσβάσιμη
// p.testStr="smthng";//ΛΑΘΟΣ - το testStr δεν είναι προσβάσιμο } }