Τεχνολογίες Υλοποίησης Αλγορίθµων Χρήστος Ζαρολιάγκης Καθηγητής Τµήµα Μηχ/κων Η/Υ & Πληροφορικής Πανεπιστήµιο Πατρών email: zaro@ceid.upatras.gr Γρηγόρης Πράσινος Υποψήφιος ιδάκτωρ Τµήµα Μηχ/κων Η/Υ & Πληροφορικής Πανεπιστήµιο Πατρών Σχεδίαση κλάσεων βασισµένη σε πολιτικές (Policy based class design) 1 / 19
Σχεδιασµός κλάσεων: το πρόβληµα Κάθε σχεδιαστικό πρόβληµα αναλύεται σε υποπροβλήµατα. Για κάθε υποπρόβληµα είναι δυνατόν να υπάρχουν πολλές διαφορετικές απαντήσεις (ενδεχοµένως και µε διαφορετική πολυπλοκότητα). Η δυσκολία έγκειται στο σχεδιασµό κλάσεων που να εµπεριέχουν ήδη κάποιες σχεδιαστικές αποφάσεις αλλά και που να είναι αρκετά ευέλικτες ώστε να µπορούν να προσαρµοστούν στις ανάγκες των χρηστών. 2 / 19
Πιθανή λύση 1: Υλοποίηση όλων των επιλογών σε µία κλάση Η προσέγγιση δεν δουλεύει γιατί: Σε πολλές περιπτώσεις απλά δεν είναι δυνατή µια τέτοια υλοποίηση. ηµιουργούνται µεγάλες κλάσεις που είναι δύσκολο να χρησιµοποιηθούν, να τροποποιηθούν και να υλοποιηθούν αποδοτικά. Χάνεται η ασφάλεια τύπων (type safety): δεν είναι δυνατόν να εξασφαλιστεί η χρήση ορισµένων σχεδιαστικών αποφάσεων. 3 / 19
Πιθανή λύση 2: Πολλές µικρές κλάσεις Η προσέγγιση δεν δουλεύει γιατί: Απαιτεί τη δηµιουργία πολλών κλάσεων (για κάθε δυνατό συνδυασµό!) Είναι πολύ δύσκολο να τροποποιηθούν. 4 / 19
Πιθανή λύση 3: Πολλαπλή κληρονοµικότητα Κατασκευάζονται πολλές µικρές κλάσεις οι οποίες συνδυάζονται µε πολλαπλή κληρονοµικότητα για τη δηµιουργία µίας σύνθετης. Η προσέγγιση αυτή είναι πολλές ϕορές ικανοποιητική, αλλά: εν προσφέρει πάντα έναν καθορισµένο τρόπο για την κατασκευή των σύνθετων κλάσεων. Πολλές ϕορές λειτουργεί δύσκολα γιατί οι κλάσεις χρειάζεται να γνωρίζουν πληροφορία που παρέχεται από κάποια κλάση. 5 / 19
Πιθανή λύση 4: Χρήση αρχετύπων Η υπό σχεδιασµό κλάση κατασκευάζεται σαν template class και η προσαρµογή της γίνεται µέσω partial/full specialization των µεθόδων ή της ίδιας της κλάσης. Η προσέγγιση αυτή αποδίδει κάποια πλεονεκτήµατα: Είναι αρκετά ευέλικτη. Προσφέρει τη δυνατότητα ύπαρξης προκαθορισµένων τιµών Εχει όµως και µειονεκτήµατα: εν είναι δυνατόν να γίνει προσαρµογή της δοµής της κλάσης παρά µόνο των µεθόδων της. Υπάρχουν όρια της γλώσσας στην εφαρµογή partial/full template specialization (π.χ. δεν επιτρέπεται η εξειδίκευση µίας µόνο µεθόδου σε µια κλάση µε περισσότερα του ενός template parameters). εν επιτρέπει την ύπαρξη περισσότερων της µίας προκαθορισµένων τιµών. 6 / 19
Λύση: Σχεδιασµός ϐασισµένος σε πολιτικές Η ϐασική ιδέα είναι η ανάλυση της υπό σχεδιασµό κλάσης σε πολιτικές (policies), δηλαδή σε σηµεία στα οποία απαιτείται η λήψη µίας απόφασης. Κάθε πολιτική είναι µία περιγραφή της διασύνδεσης (interface) που απαιτεί η υπό σχεδιασµό κλάση και περιλαµβάνει τον ορισµό µεθόδων, πεδίων και εσωτερικών τύπων. Η περιγραφή της πολιτικής δεν απαιτείται να είναι εντελώς αυστηρή. 7 / 19
Σχεδιασµός ϐασισµένος σε πολιτικές - παράδειγµα Ως παράδειγµα, στην κλάση Array µία πολιτική είναι ο έλεγχος των ορίων του αριθµοδείκτη. Η πολιτική σε αυτήν την περίπτωση µπορεί να ορίζει ότι πρέπει να υπάρχει µία µέθοδος: bool checkindex(int i, int low, int high); Προφανώς κάθε κλάση µπορεί να διαχωριστεί σε πολιτικές µε διαφορετικούς τρόπους. Στο συγκεκριµένο παράδειγµα µια άλλη περιγραφή της πολιτικής ϑα ήταν ότι η κλάση πολιτικής πρέπει να έχει πεδία για τα όρια του αριθµοδείκτη. Ο σχεδιαστής της κλάσης αποφασίζει για το διαχωρισµό και καθορίζει τις πολιτικές. 8 / 19
Κλάσεις πολιτικής Για κάθε πολιτική µπορούν να παρέχονται πολλές διαφορετικές υλοποιήσεις, µε διαφορετικά πλεονεκτήµατα και µειονεκτήµατα. Κάθε υλοποίηση ονοµάζεται κλάση πολιτικής (policy class). Για το παράδειγµα του Array µπορούν να υλοποιηθούν δύο κλάσεις πολιτικής, ανάλογα µε το αν γίνεται έλεγχος των ορίων ή όχι : struct RangeCheck { static bool checkindex(int i, int low, int high) { return i <= high && i >= low; } }; struct NoRangeCheck { static bool checkindex(int i, int low, int high) { return true; } }; 9 / 19
Συνδυασµός των κλάσεων πολιτικής Οι κλάσεις πολιτικής κατασκευάζονται έτσι ώστε να χρησιµοποιούνται για την δηµιουργία πιο σύνθετων κλάσεων. Η υπό σχεδιασµό κλάση ορίζει τον τρόπο µε τον οποίο συνδυάζονται και χρησιµοποιούνται οι κλάσεις πολιτικής. Η κλάση που δηµιουργείται ονοµάζεται host class. Η host class περιέχει κλάσεις πολιτικής ή κληρονοµεί από αυτές: template<typename Element, typename RangeChecker> class Array : public RangeChecker {... }; Ο χρήστης της κλάσης αρκεί να δώσει σαν παράµετρο την κλάση πολιτικής που επιθυµεί: Array<int, NoRangeCheck> myarray(10); myarray[5] = 10;... 10 / 19
Λειτουργία των κλάσεων πολιτικής Η host class αναθέτει στις εκάστοτε κλάσεις πολιτικής κάποιες από τις λειτουργίες της: template<typename Element, typename RangeChecker> class Array : public RangeChecker { public: Element &operator[](int i) { if (checkindex(i, low, high)) return _ia[i]; else throw OutOfRangeEx; } private: int low; int high; }; 11 / 19
Προεπιλεγµένα ορίσµατα Ο σχεδιαστής της κλάσης µπορεί να παρέχει προεπιλεγµένα ορίσµατα για την ευκολία των χρηστών: template<typename Element, typename RangeChecker = NoRangeCheck> class Array : public RangeChecker {... }; Array<int> myarray; // myarray does not have // range checking 12 / 19
Συνδυασµός των κλάσεων πολιτικής Η πραγµατική δύναµη της τεχνικής των κλάσεων πολιτικής γίνεται ϕανερή όταν συνδυάζονται για την κατασκευή πολύπλοκων κλάσεων. Τότε ο χρήστης µπορεί να απαιτήσει συγκεκριµένη συµπεριφορά επιλέγοντας τις κατάλληλες κλάσεις πολιτικής. Εστω π.χ. ότι η κλάση Array έχει µία δεύτερη πολιτική που ορίζει τον τρόπο µε τον οποίο γίνεται η ταξινόµηση των στοιχείων του πίνακα: template<typename Element, typename RangeChecker, typename Sorter> class Array : public RangeChecker, public Sorter {... }; Πιθανές υλοποιήσεις είναι οι κλάσεις NoSort, InsertionSorter, κτλ. Array<int, NoRangeCheck, InsertionSorter> myarray; 13 / 19
Εµπλουτισµένες κλάσεις πολιτικής Οι κλάσεις πολιτικής δεν είναι ανάγκη να ανταποκρίνονται πιστά στην περιγραφή της πολιτικής αλλά µπορούν να παρέχουν και επιπλέον λειτουργίες όπου αυτές χρειάζονται. Καθώς η host class κληρονοµεί από τις κλάσεις πολιτικής οι επιπλέον λειτουργίες είναι διαθέσιµες µέσω αυτής. Αν ο χρήστης προσπαθήσει να χρησιµοποιήσει λειτουργία που δεν παρέχεται από την κλάση πολιτικής που έχει επιλέξει, παίρνει µήνυµα λάθους κατά τη µεταγλώττιση. 14 / 19
Συναρτήσεις κατάργησης και κλάσεις πολιτικής - 1 Καθώς η host class κληρονοµεί από τις κλάσεις πολιτικής είναι συντακτικά σωστό να µετατραπεί ένας δείκτης στην σύνθετη κλάση σε δείκτη σε κλάση πολιτικής. Αν γίνει κατάργηση της σύνθετης κλάσης µέσω αυτού του δείκτη, το αποτέλεσµα είναι απροσδιόριστο. Array<int, RangeCheck> myarray; RangeCheck *prc = &myarray; delete prc; 15 / 19
Συναρτήσεις κατάργησης και κλάσεις πολιτικής - 2 εν είναι σκόπιµο να δηλωθεί η συνάρτηση κατάργησης ως εικονική γιατί κάτι τέτοιο προσθέτει κόστος στην απόδοση. Το πρόβληµα ϑα λυνόταν µε τη χρήση ιδιωτικής κληρονοµικότητας (private inheritance) αντί για δηµόσιας (public) αλλά έτσι δεν ϑα ήταν δυνατή η χρήση εµπλουτισµένων πολιτικών (η ιδιωτική κληρονοµικότητα ϑα απέκρυπτε τις επιπλέον µεθόδους). Η λύση είναι να δηλωθεί η συνάρτηση κατάργησης ως protected: class RangeCheck {... protected: ~RangeCheck() { } } 16 / 19
Χρήση παραµετροποιηµένων αρχετύπων Αν κάποια πολιτική απαιτείται να είναι κι αυτή µία παραµετροποιηµένη κλάση η σύνταξη της δήλωσης της σύνθετης κλάσης γίνεται αρκετά πολύπλοκη. Εστω για παράδειγµα ότι η πολιτική του Array για την ταξινόµηση πρέπει να παραµετροποιείται µε τον τύπο του στοιχείου. Τότε ο χρήστης ϑα πρέπει να δηλώνει: Array<int, NoRangeCheck, InsertionSorter<int> > myarray; Ο χρήστης πρέπει να γράψει δύο ϕορές τον τύπο int. Αυτό µπορεί να αποφευχθεί µε χρήση παραµετροποιηµένων αρχετύπων (template template parameters). template<typename Element, typename RangeCheck, typename <typename Elem> Sorter > class Array: public RangeCheck, public Sorter<Element> {... }; 17 / 19
Χρήση παραµετροποιηµένων αρχετύπων Το όρισµα Element για το Sorter δεν χρησιµοποιείται, οπότε µπορεί να παραληφθεί: template<typename Element, typename RangeCheck, typename <typename> Sorter > class Array: public RangeCheck, public Sorter<Element> {... }; Ο χρήστης αρκεί πλέον να δηλώνει π.χ.: Array<int, NoRangeCheck, InsertionSorter> myarray; 18 / 19
Περισσότερες πληροφορίες Η τεχνική του σχεδιασµού µε πολιτικές έχει τα καλύτερα αποτελέσµατα όταν είναι δυνατός ο διαχωρισµός της λειτουργικότητας µιας κλάσης σε πολιτικές που είναι ανεξάρτητες µεταξύ τους. Ο σωστός τρόπος διαχωρισµού είναι προφανώς δύσκολη υπόθεση και απαιτεί σχετική εµπειρία. Η γλώσσα δεν παρέχει προς το παρόν έναν τρόπο για αυστηρή περιγραφή των κλάσεων πολιτικής. Με την ενσωµάτωση των concepts στο νέο πρότυπο της C++ ϑα υπάρχει δυνατότητα τέτοιας αυστηρής περιγραφής. Υπάρχει συνάφεια του policy-based design µε το strategy design pattern. Η τεχνική αναλύεται στο ϐιβλίο του Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied (Addison Wesley, 2001). 19 / 19