Αντικειμενοστρεφής σχεδιασμός με UML

Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr

Εισαγωγή

Η ενοποιημένη γλώσσα σχεδιασμού (unified modeling language) (UML) είναι μια γραφική γλώσσα για την οπτική παράσταση, τη διαμόρφωση προδιαγραφών και την τεκμηρίωση συστημάτων που βασίζονται σε λογισμικό. Η UML στοχεύει στο σχεδιασμό αντικειμενοστρεφών συστημάτων. Το σχέδιο είναι μια απλοποιημένη παράσταση της πραγματικότητας.

Σχεδιάζουμε για να μπορέσουμε να καταλάβουμε το σύστημα που αναπτύσσουμε. Έτσι δημιουργώντας ένα σχέδια επιτυγχάνουμε τέσσερις στόχους:

  1. παριστάνουμε οπτικά το σύστημα που έχουμε ή θέλουμε να κατασκευάσουμε,
  2. προσδιορίζουμε τη δομή και τη συμπεριφορά του συστήματος,
  3. δημιουργούμε ένα πρότυπο για να βασίσουμε την κατασκευή του συστήματος,
  4. τεκμηριώνουμε τις αποφάσεις που λάβαμε.

Σε όλους τους τεχνολογικούς τομείς ο σχεδιασμός βασίζεται σε τέσερις βασικές αρχές:

  1. η επιλογή του είδους του σχεδίου έχει επίπτωση στον τρόπο και την μορφή επίλυσης του προβλήματος,
  2. όλα τα σχέδια εκφράζονται σε διαφορετικές βαθμίδες ακρίβειας,
  3. τα καλύτερα σχέδια σχετίζονται με την πραγματικότητα,
  4. ένα είδος σχεδίων δεν είναι ποτέ αρκετό.

Η UML περιλαμβάνει τρία βασικά στοιχεία:

  1. Οντότητες
  2. Σχέσεις
  3. Διαγράμματα
Η UML είναι μια πλήρης και πλούσια γλώσσα με εξαιρετικά ευρύ πεδίο εφαρμογής. Στο μάθημα αυτό θα εξετάσουμε εξαιρετικά συνοπτικά τον τρόπο παράστασης ορισμένων αντικειμενοστρεφών δομών σε UML.

Κλάσεις

Σχέσεις

Στη UML ορίζονται τρεις βασικές σχέσεις:
  1. εξάρτηση (dependency)
  2. γενίκευση (generalisation)
  3. σύνδεση (association)

Εξάρτηση

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

Γενίκευση

Η γενίκευση δηλώνει μια σχέση ανάμεσα σε κάτι γενικό (τη βασική κλάση ή αλλιώς γονέα) και κάτι ειδικό (μιαν υποκλάση ή αλλιώς παιδί της). Παριστάνεται με μια συνεχή γραμμή με κλειστό βέλος που δείχνει προς τη βασική κλάση:

Σύνδεση

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

Αν σε μια σχέση τα αντικείμενα απαρτίζουν τμήματα ενός όλου, τότε αυτή απεικονίζεται ως συγκρότημα (aggregation) με την παράσταση ενός διαμαντιού στην άκρη του "όλου".

Αν σχέση τα αντικείμενα που απαρτίζουν τμήματα ενός όλου έχουν την ίδια διάρκεια ζωής με το όλο, τότε αυτή απεικονίζεται ως σύνθεση (composition) με την παράσταση ενός γεμάτου διαμαντιού στην άκρη του "όλου".

Είδη εξαρτήσεων

Με τη χρήση μιας εξάρτησης εκφράζουμε σημασιολογικές (semantic) σχέσεις ανάμεσα σε στοιχεία του μοντέλου. Σε τέτοιες περιπτώσεις μια αλλαγή σε ένα στοιχείο της εξάρτησης μπορεί να έχει επίπτωση στο άλλο. Διακρίνουμε τα παρακάτω είδη εξάρτησης:
πρόσβαση (access)
Άδεια σε κάποια στοιχεία από ένα τμήμα να έχουν πρόσβαση σε στοιχεία από άλλο τμήμα (access).
σύνδεση (binding)
Παροχή τιμών σε ένα πρότυπο για να δημιουργήσει ένα νέο στοιχείο (bind).
κλήση (call)
Μια μέθοδος καλεί μια άλλη (call).
απόρρεια (derivation)
Ένα στοιχείο που μπορεί να υπολογιστεί από κάποιο άλλο (derive).
friend
Ένα στοιχείο μπορεί να έχει πρόσβαση σε στοιχεία άλλης κλάσης παρά τους όποιους περιορισμούς (friend).
εισαγωγή (import)
Άδεια σε ένα τμήμα να εισάγει και να χρησιμοποιήσει τα στοιχεία ενός άλλου τμήματος (import).
δημιουργία (instantiation)
Μια μέθοδος μιας κλάσης δημιουργεί αντικείμενα μιας άλλης κλάσης (instantiate).
παράμετρος (parameter)
Σχέση ανάμεσα σε μια λειτουργία και τις παραμέτρους της (parameter).
δημιουργία (realization)
Σχέση ανάμεσα σε μια προδιαγραφή και την υλοποίησή της (realize).
εκλέπτυνση (refinement)
Δήλωση για την ύπαρξη απεικόνισης ανάμεσα σε δύο σημασιολογικά επίπεδα (refine).
αποστολή (send)
Σχέση ανάμεσα στον αποστολέα και τον παραλήπτη ενός μηνύματος (send).
ίχνος (trace)
Σχέση ανάμεσα σε δύο στοιχεία δύο διαφορετικών μοντέλων που δεν αποτελεί όμως απεικόνιση (trace).
χρήση (usage)
Ένα στοιχείο απαιτεί την ύπαρξη ενός άλλου στοιχείο για τη λειτουργία του (π.χ. call, instantiate, parameter, send) (use).
Στο διάγραμμα της UML γράφουμε μέσα σε εισαγωγικά το είδος της εξάρτησης (αυτό που εμφανίζεται στο τέλος κάθε ορισμού στον παραπάνω πίνακα) πάνω από την αντίστοιχη γραμμή με το βέλος:

Διάγραμμα κλάσεων

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

Διάγραμμα αντικειμένων

Τα διαγράμματα αντικειμένων χρησιμοποιούνται για το σχεδιασμό της στατικής κατάστασης του συστήματος κατά μια συγκεκριμένη χρονική στιγμή. Κάθε αντικείμενο σχεδιάζεται ως ένα ορθογώνιο με την παρακάτω μορφή:

Το σύνολο των αντικειμένων σχεδιάζεται με βάση τους συνδέσμους που ορίζονται πάνω σε αυτό.

Βιβλιογραφία

Άσκηση

Άσκηση 5

Η άσκηση αυτή πρέπει να παραδωθεί σε γραπτή μορφή στη Γραμματεία μέχρι την Τρίτη 27 Μαρτίου 2001 11:59 πμ. Για κάθε μέρα καθυστέρησης θα αφαιρείται ένας βαθμός από τον τελικό βαθμό της άσκησης. Στη βαθμολογία θα ληφθεί υπόψη και η εμφάνιση του παραδοτέου.
  1. Να σχεδιάσετε σε UML τις κλάσεις icsd_student και math_student. Οι κλάσεις αυτές πρέπει να έχουν ως ιδιότητα τον αριθμό μαθημάτων που έχει περάσει ένας φοιτητής, αντίστοιχες μεθόδους πρόσβασης στην ιδιότητα αυτή και μια μέθοδο που να επιστρέφει αληθές αν ο φοιτητής μπορεί να πάρει πτυχίο (έχοντας περάσει 40 μαθήματα για την icsd_student και 38 για την math_student). Να χρησιμοποιήσετε κληρονομικότητα για να ελαχιστοπιήσετε τις μεθόδους των κλάσεων.
  2. Ορισμένες φορές χρειάζεται να ανακτήσουμε το σχεδιασμό ενός συστήματος από την υλοποίησή του. Η διαδιακασία αυτή καλείται αντίστροφος σχεδιασμός (reverse engineering) και απαιτείται όταν δεν υπάρχει ο αρχικός σχεδιασμός ή αυτός δεν έχει συντηρηθεί κατά τη διάρκεια ζωής του έργου. Για το παρακάτω σύνολο από αρχεία C++ που δηλώνουν αντίστοιχες κλάσεις να σχεδιάσετε σε UML ένα διάγραμμα που να απεικονίζει την κάθε κλάση και τη σχέση της με τις υπόλοιπες. (Τα αρχεία αυτά υλοποιούν ένα σύστημα μετατροπής χαρακτήρων ανάμεσα σε διάφορα πρότυπα κωδικοποίησης, μεταγραφής και μεταγραμματισμού.) Προσοχή: για την διαδικασία του αντίστροφου σχεδιασμού δεν απατείται η πλήρης κατανόηση του κώδικα.

    filter.h

    #ifndef FILTER_
    #define FILTER_

    class filter {
    protected:
            filter *input;
    public:
            virtual int getcharacter() = 0;
            void setinput(filter *i) {input = i;};
    };
    #endif

    htmll1.h

    #ifndef HTMLL1_
    #define HTMLL1_
    #include "filter.h"
    #include "queue.h"

    class htmll1: public filter, public queue {
    private:
            void fillbuff();                 // Fill the queue buffer
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif


    lex.h

    #ifndef LEX_
    #define LEX_
    #include "filter.h"
    #include "queue.h"

    class lex: public filter, public queue {
    private:
            void (*lexfun)(lex *lf);
            void fillbuff() { lexfun(this); }; // Fill the queue buffer
    public:
            lex(void (*lf)(lex *l)) { lexfun = lf; };
            inline int getinputcharacter() { return input->getcharacter(); };
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    map.h

    #ifndef MAP_
    #define MAP_
    #include "filter.h"

    class map: public filter {
    private:
            int mapsize;                    // Size of character map
            int *charmap;                   // Map from source to target
            char default_char;              // Default map character
    public:
            map(char *source, char *target, char dflt);
            int getcharacter();
    };
    #endif

    queue.h

    #ifndef QUEUE_
    #define QUEUE_
    #include "filter.h"

    class queue {
    private:
            int *q;                 // Output queue
            int qlen;               // Characters in output queue
            int qhead;
            int qtail;
            virtual void fillbuff() = 0;    // Fill the queue buffer
    public:
            void nq(int c);         // Add a character to the output queue
            unsigned int getcharacter();    // Get a character from the output queue
            queue(const int qlen = 100);
            ~queue() { delete[] q; };
    };
    #endif

    stdinput.h

    #ifndef STDINPUT_
    #define STDINPUT_
    #include "filter.h"
    #include <iostream.h>

    class stdinput: public filter {
    public:
            int getcharacter()
            {
                    unsigned char c;
                    cin.read(&c, 1);
                    return (cin.eof() ? EOF : c);
            };
    };
    #endif

    translit.h

    #ifndef TRANSLIT_
    #define TRANSLIT_
    #include "filter.h"
    #include "queue.h"

    class translit: public filter, public queue {
    private:
            void fillbuff();                 // Fill the queue buffer
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    ucs2i.h

    #ifndef UCS2I_
    #define UCS2I_
    #include "filter.h"

    class ucs2i: public filter {
    public:
            int getcharacter()
            {
                    int c1, c2;

                    c1 = input->getcharacter();
                    c2 = input->getcharacter();
                    return ((c1 << 8) | c2);
            }
    };
    #endif

    ucs2o.h

    #ifndef UCS2O_
    #define UCS2O_
    #include "filter.h"
    #include "queue.h"

    class ucs2o: public filter, public queue {
    private:
            void fillbuff()          // Fill the queue buffer
            {
                    unsigned int c;

                    c = input->getcharacter();
                    nq(c >> 8);
                    nq(c & 0xff);
            }
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    utf7.h

    #ifndef UTF7_
    #define UTF7_

    // See RFC 1642
    class utf7 {
    protected:
            static char base64[];
            static short invbase64[128];
            static char direct[];
            static char optional[];
            static char spaces[];           /* space, tab, return, line feed */
            static char mustshiftsafe[128];
            static char mustshiftopt[128];
            static const int SHIFT_IN;
            static const int SHIFT_OUT;

            unsigned long BITbuffer;
            unsigned long buffertemp;
            int bufferbits;

            inline void write_n_bits(int x, int n)
            {
                    BITbuffer |= ((x & ~(-1L<<n)) << (32-n-bufferbits));
                    bufferbits += n;
            }

            inline int read_n_bits(int n)
            {
                    buffertemp = (BITbuffer >> (32-n));
                    BITbuffer <<= n;
                    bufferbits -= n; 
                    return (buffertemp);
            }
    public:
            utf7();
    };


    #endif

    utf7i.h

    #ifndef UTF7I_
    #define UTF7I_
    #include "filter.h"
    #include "queue.h"
    #include "utf7.h"

    // See RFC 1642
    class utf7i: public filter, public queue, public utf7 {
    private:
            void fillbuff();
            int shifted, first, wroteone;
    public:
            utf7i() { shifted = first = wroteone = 0; };
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    utf7o.h

    #ifndef UTF7O_
    #define UTF7O_
    #include "filter.h"
    #include "queue.h"
    #include "utf7.h"

    // See RFC 1642
    class utf7o: public filter, public queue, public utf7 {
    private:
            char *mustshift;
            int shifted, needshift, done;

            void fillbuff();
    public:
            int getcharacter() { return (queue::getcharacter()); };
            void optional(bool opt);
            utf7o()
            { 
                    mustshift = mustshiftopt;
                    shifted = needshift = done = 0;
            }
    };
    #endif

    utf8i.h

    #ifndef UTF8I_
    #define UTF8I_
    #include "filter.h"

    class utf8i: public filter {
    public:
            int getcharacter()
            {
                    int c1, c2, c3, c4;
                    int cout;

                    c1 = input->getcharacter();
                    if ((c1 & 0x80) == 0) {
                            cout = c1;
                    } else if ((c1 & 0xe0) == 0xc0) {
                            c2 = input->getcharacter();
                            cout = (c2 & 0x3f) | ((c1 & 0x1f) << 6);
                    } else if ((c1 & 0xf0) == 0xe0) {
                            c2 = input->getcharacter();
                            c3 = input->getcharacter();
                            cout = ((c1 & 0xf) << 12) |
                                   ((c2 & 0x3f) << 6) |
                                   (c3 & 0x3f);
                    } else {
                            error("UTF-8 input: UCS characters above 0xffff are not supported\n");
                    }
                    return (cout);
            }
    };
    #endif

    utf8o.h

    #ifndef UTF8O_
    #define UTF8O_
    #include "filter.h"
    #include "queue.h"

    class utf8o: public filter, public queue {
    private:
            void fillbuff()          // Fill the queue buffer
            {
                    unsigned int c;

                    c = input->getcharacter();
                    if (c < 0x80) {
                            nq(c);
                    } else if (c < 0x800) {                 // bbb bbbb bbbb
                            nq(0xC0 | c>>6);                // 110b bbbb
                            nq(0x80 | c & 0x3F);            // 10bb bbbb
                    } else if (c < 0x10000) {               // bbbb bbbb bbbb bbbb
                            nq(0xE0 | c>>12);               // 1110 bbbb
                            nq(0x80 | c>>6 & 0x3F);         // 10bb bbbb
                            nq(0x80 | c & 0x3F);            // 10bb bbbb
                    } else {
                            error("UCS characters above 0xffff are not supported\n");
                    }
            }
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif