ΑΡΙΣΤΟΤΕΛΕΙΟ ΠΑΝΕΠΙΣΤΗΜΙΟ ΘΕΣΣΑΛΟΝΙΚΗΣ Τμήμα Πληροφορικής Πρόγραμμα Μεταπτυχιακών Σπουδών του τμήματος Πληροφορικής Κατεύθυνση "Πληροφοριακά Συστήματα Εμπειρική μελέτη χρήσης αντικειμενοστραφών προτύπων σχεδίασης σε λογισμικό ανοικτού κώδικα Καραμάνου Ευγενία Επιβλέπων καθηγητής: Ιωάννης Σταμέλος Θεσσαλονίκη 2009
Πρόλογος Η εφαρμογή των αντικειμενοστραφών προτύπων σχεδίασης στην ανάπτυξη λογισμικού και τα αποτελέσματα της είναι ένα πολυσυζητημένο θέμα στις μέρες μας. Αν και είναι παραδεκτό ότι τα πρότυπα σχεδίασης βελτιώνουν τη σχεδίαση του λογισμικού, τι γίνεται όταν το λογισμικό είναι παιχνίδια ανοικτού κώδικα; Αντικείμενο της παρούσας μεταπτυχιακής εργασίας είναι η συγκέντρωση λογισμικού με τη μορφή ενός repository, που προέρχεται από στιγμιότυπα υλοποίησης προτύπων σχεδίασης σε α- νοικτού κώδικα παιχνίδια. Έπειτα, χρήση του λογισμικού αυτού ως δείγμα για εμπειρική μελέτη σχετικά με τα αντικειμενοστραφή πρότυπα σχεδίασης, ώστε να εξαχθούν αποτελέσματα που να δείχνουν αν υπάρχουν πρότυπα σχεδίασης που είναι πιο κατάλληλα για χρήση σε κώδικα παιχνιδιού και αν υπάρχουν είδη παιχνιδιών που είναι πιο προσοδοφόρα για εφαρμογή προτύπων σχεδίασης. 2
Αbstract Use of Object-Oriented design patterns in game development and its effects appears to be an extremely well-known and discussed issue. Although it is widely accepted that design patterns are beneficial during the software development, what happens if the software under development is an open-source game? The objective of this master thesis is the introduction of a web repository recording object-oriented design patterns employed in open-source games. Additionally, use of this repository in order to carry out an empirical study concerning object-oriented design patterns and retrieve results that show if there exist any design patterns that are more applicable in game development than others and if some game genres are more fertile to the use of design patterns than others. 3
Ευχαριστίες Σε αυτό το σημείο θα ήθελα να ευχαριστήσω κάποια άτομα που το καθένα από αυτά συνέβαλλε με διαφορετικό τρόπο στη δημιουργία αυτής της μεταπτυχιακής εργασίας. Πρώτα από όλους θα ήθελα να ευχαριστήσω τον επιβλέπων καθηγητή μου κ. Σταμέλο Ιωάννη για την αρχική εμπιστοσύνη και ανάθεση της μεταπτυχιακής εργασίας και για την κατανόηση που έδειξε κατά τη διάρκεια ολοκλήρωσής της. Στη συνέχεια ένα μεγάλο ευχαριστώ αξίζει ο υποψήφιος διδάκτορας Αμπατζόγλου Αποστολής για την άριστη συνεργασία μας, την καθοδήγηση, και κυρίως για την εμπιστοσύνη του. Επίσης, θα ήθελα να ευχαριστήσω τη φίλη και συνεργάτιδα Μίχου Όλγα για όλη μας τη συνεργασία, με τις εύκολες αλλά κυρίως τις δύσκολες μας στιγμές. Τέλος, θα ήθελα να ευχαριστήσω την οικογένειά μου, και ιδιαίτερα την αδελφή μου για όλη τη βοήθεια και συμβολή της στις μέρες άγχους και πίεσης. 4
Περιεχόμενα 1 Εισαγωγή-Βασικές Έννοιες... 8 1.1 Τι είναι ένα πρότυπο σχεδίασης...8 1.2 Ταξινόμηση προτύπων σχεδίασης...11 1.3 Πρότυπα Σχεδίασης Design Patterns...14 1.3.1 Προσαρμογέας-Adapter...14 1.3.1.1 Γενικά...15 1.3.1.2 Γενική Δομή - Εφαρμογή...15 1.3.2 Πρωτότυπο Prototype...16 1.3.2.1 Γενικά...16 1.3.2.2 Γενική Δομή - Εφαρμογή...17 1.3.3 Σύνθετο-Composite...17 1.3.3.1 Γενικά...17 1.3.3.2 Γενική Δομή - Εφαρμογή...18 1.3.4 Διακοσμητής-Decorator...19 1.3.4.1 Γενικά...19 1.3.4.2 Γενική Δομή - Εφαρμογή...20 1.3.5 Μοναδιαίο-Singleton...21 1.3.5.1 Γενικά...21 1.3.5.2 Γενική Δομή Εφαρμογή...22 1.3.6 Επισκέπτης-Visitor...22 1.3.6.1 Γενικά...22 1.3.6.2 Γενική Δομή Εφαρμογή...23 1.3.7 Παρατηρητής-Observer...24 1.3.7.1 Γενικά...24 1.3.7.2 Γενική Δομή Εφαρμογή...25 1.3.8 Κατάσταση-State...25 1.3.8.1 Γενικά...25 1.3.8.2 Γενική Δομή Εφαρμογή...26 1.3.9 Στρατηγική-Strategy...27 1.3.9.1 Γενικά...27 1.3.9.2 Γενική Δομή Εφαρμογή...28 1.3.10 Template Method...28 1.3.10.1 Γενικά...28 1.3.10.2 Γενική Δομή Εφαρμογή...29 1.3.11 Εργοστάσιο-Factory Method...29 5
1.3.11.1 Γενικά...30 1.3.11.2 Γενική Δομή Εφαρμογή...31 1.4 Ποιότητα Προτύπων Σχεδίασης...31 1.5 Σπουδαιότητα Προτύπων Σχεδίασης...32 2 Ηλεκτρονικά Παιχνίδια... 34 2.1 Ιστορική Αναδρομή Ηλεκτρονικών Παιχνιδιών...34 2.2 Ιστορία Ηλεκτρονικών Παιχνιδιών στην Ελλάδα...39 2.3 Είδη Ηλεκτρονικών Παιχνιδιών...41 2.3.1 Παιχνίδια Δράσης Action Games...42 2.3.2 Παιχνίδια Βολής Shooter Games...42 2.3.3 Παιχνίδια Περιπέτειας...43 2.3.4 Παιχνίδια Στρατηγικής Strategy Games...43 2.3.4.1 Real Time Strategy Games...44 2.3.4.2 Turn Based Strategy Games...44 2.3.5 Παιχνίδια Ρόλων Role Playing Games...45 2.3.6 Παιχνίδια Προσομοίωσης Simulation Games...45 2.3.7 Arcade Games...46 2.3.8 Puzzle Games...46 2.3.9 Board Games...47 2.3.10 Card Games...47 2.4 Πρότυπα Σχεδίασης και Είδος Παιχνιδιού...47 2.5 Λόγοι Μελέτης Προτύπων Σχεδίασης σε Παιχνίδια...48 3 Ελεύθερο και Ανοικτού Κώδικα Λογισμικό... 52 3.1 Ιστορική Αναδρομή και Ορισμός Ελεύθερου και Ανοικτού Κώδικα Λογισμικού...52 3.2 Ελεύθερο Λογισμικό και Βιομηχανία Παιχνιδιών...55 3.3 Συμμετέχοντες και Τρόπος Ανάπτυξης Ελεύθερου Λογισμικού...58 3.4 Ελεύθερου και Ανοικτού Κώδικα Repositories...60 4 DePRe- OSS Project... 65 4.1 Ιστοσελίδα DePRe...65 4.2 Χρησιμοποιούμενο Εργαλείο...69 4.3 Προηγούμενη Έρευνα...70 4.4 Σκοπός Έρευνας...72 4.5 Περιγραφική Στατιστική...73 4.6 Μεθοδολογία Ανάλυσης Δεδομένων...76 4.7 Συμπεράσματα...81 4.8 Περιορισμοί Απειλές...86 6
4.9 Θέματα για Μελλοντική Έρευνα...87 5 Βιβλιογραφία... 94 ΠΑΡΑΡΤΗΜΑ Α... 88 7
1 Εισαγωγή - Βασικές Έννοιες Στο κεφάλαιο αυτό παρουσιάζονται έννοιες που αφορούν το γνωστικό υπόβαθρο για την ανάγνωση και κατανόηση της παρούσης μεταπτυχιακής εργασίας. Αρχικά παρουσιάζονται έννοιες που αφορούν τα πρότυπα σχεδίασης. Στη συνέχεια ακολουθεί ο ορισμός και περιγραφή των προτύπων σχεδίασης που μελετήθηκαν. Τέλος, παρουσιάζεται η έννοια της ποιότητας των προτύπων σχεδίασης καθώς και η σπουδαιότητα χρήσης των προτύπων σχεδίασης. 1.1 Τι είναι ένα πρότυπο σχεδίασης Ο σχεδιασμός αντικειμενοστραφούς λογισμικού είναι μια αρκετά δύσκολη διαδικασία για τους προγραμματιστές. Η διαδικασία αυτή γίνεται αρκετά πιο επίπονη, εάν οι προγραμματιστές επιθυμούν να δημιουργήσουν λογισμικό το οποίο θα είναι επαναχρησιμοποιήσιμο ή θα συντηρείται εύκολα. Αυτό οφείλεται στο γεγονός ότι η σχεδίαση λογισμικού που γίνεται μια δεδομένη στιγμή, θα πρέπει να είναι τόσο συγκεκριμένη ώστε να επιλύει το υπάρχον πρόβλημα, όσο και γενική ώστε να μπορέσει να επιλύσει πιθανά προβλήματα και απαιτήσεις που θα προκύψουν στο μέλλον [1]. Τα προβλήματα ωστόσο, που αντιμετωπίζει ένας προγραμματιστής κατά τη διάρκεια σχεδίασης και υλοποίησης ενός συστήματος λογισμικού, πολύ σπάνια εμφανίζονται για πρώτη φορά στο συγκεκριμένο έργο. Αν και η φύση του λογισμικού δίνει στους προγραμματιστές την αίσθηση ότι κάθε πρόβλημα είναι ξεχωριστό και διαφορετικό από τα υπόλοιπα, συνήθως τα προβλήματα που εμφανίζονται, θα έχουν παρουσιαστεί και αντιμετωπισθεί επιτυχώς και σε κάποιο προηγούμενο έργο λογισμικού από κάποιον άλλον προγραμματιστή [2]. Αυτό γίνεται εμφανές και από τον τρόπο με τον οποίο επιλύουν τα προβλήματα που ανακύπτουν οι έμπειροι προγραμματιστές. Σε αντίθεση με τους υπόλοιπους προγραμματιστές που επιλύουν κάθε πρόβλημα ξεκινώντας από μηδενική βάση, οι έμπειροι προγραμματιστές ανατρέχουν σε παρόμοια προβλήματα και λύσεις που έχουν αντιμετωπίσει σε προηγούμενα έργα λογισμικού και 8
επαναχρησιμοποιούν κάποια επιτυχημένη σχεδίαση. Η στρατηγική επίλυσης πολλών προβλημάτων, ειδικά όταν αυτή αποτυπώνεται στη στατική δομή ενός αντικειμενοστραφούς συστήματος λογισμικού, είναι κοινή ή παρόμοια σε πολλές περιπτώσεις [1]. Τα πρότυπα σχεδίασης έχουν ως στόχο να συστηματοποιήσουν συνηθισμένες λύσεις σε συνηθισμένα προβλήματα λογισμικού. Κάθε πρότυπο σχεδίασης κατονομάζει τη συγκεκριμένη λύση, παρέχει μια περιγραφή του προβλήματος στο οποίο μπορεί να εφαρμοστεί, προδιαγράφει τη λύση και τις συνέπειες της, συνήθως σε επίπεδο αρχιτεκτονικής σχεδίασης. Σύμφωνα με [1], ένα πρότυπο σχεδίασης αποτελείται από τέσσερα βασικά στοιχεία: Το όνομα κάθε προτύπου σχεδίασης είναι ο τρόπος με τον οποίο μπορούμε να περιγράψουμε το πρόβλημα, τη λύση του και τις συνέπειες της, με μία ή δύο λέξεις. Δίνοντας ένα όνομα σε ένα πρότυπο σχεδίασης, άμεσα δημιουργούμε ένα εξειδικευμένο λεξικό που διευκολύνει την επικοινωνία μεταξύ των προγραμματιστών. Το πρόβλημα σε κάθε πρότυπο περιγράφει ένα γενικότερο πλαίσιο όπου υπό κανονική αντιμετώπιση χωρίς τη χρήση προτύπων θα προέκυπταν ανεπιθύμητες συνέπειες αναφορικά με την λειτουργία, τον έλεγχο και τη συντήρηση του λογισμικού. Μερικές φορές το πρόβλημα μπορεί να περιλαμβάνει κάποιες προϋποθέσεις που θα πρέπει να συναντώνται προκειμένου να έχει αποτέλεσμα η χρήση ενός προτύπου σχεδίασης. Η λύση περιγράφει τα στοιχεία από τα οποία πρέπει να συγκροτείται η σχεδίαση, τις μεταξύ τους σχέσεις, τις ιδιότητες και τις αρμοδιότητες αυτών. Ωστόσο, η λύση δεν περιγράφει μια συγκεκριμένη σχεδίαση ή υλοποίηση, επειδή το πρότυπο είναι σαν πλαίσιο και μπορεί να εφαρμοστεί σε πολλές διαφορετικές περιπτώσεις. Οι συνέπειες είναι το αποτέλεσμα της εφαρμογής των προτύπων σχεδίασης. Αν και δεν αναφέρονται ρητά, όταν περιγράφουμε τρόπους σχεδίασης, είναι κρίσιμες για την εκτίμηση των εναλλακτικών που υπάρχουν στη σχεδίαση και για την κατανόηση του κόστους και της ωφέλειας από τη χρήση του προτύπου. Οι συνέπειες συχνά αφορούν ζητήματα χώρου μνήμης και χρόνου εκτέλεσης. Επιπλέον, επειδή η επαναχρησιμοποίηση ενός κώδικα είναι συχνά ένας 9
παράγοντας που δηλώνει καλή αντικειμενοστραφή σχεδίαση, οι συνέπειες συχνά αναφέρονται στην επεκτασιμότητα και την ευελιξία του συστήματος. Η έμπνευση για τη χρήση προτύπων στην Τεχνολογία Λογισμικού προήλθε από το χώρο της Αρχιτεκτονικής και την σημαντική προσφορά του αρχιτέκτονα Christopher Alexander με το βιβλίο A Pattern Language: Towns, Buildings, Construction (1977). Στο βιβλίο αυτό, γίνεται μια προσπάθεια να δοθεί απάντηση στο ερώτημα εάν η ποιότητα (στην αρχιτεκτονική) είναι μια αντικειμενική ιδιότητα. Αν γίνει δεκτό ότι είναι δυνατόν να αναγνωρίσει και να περιγράψει κάποιος ένα αρχιτεκτονικό σχέδιο, τότε το ερώτημα που προκύπτει σύμφωνα με τον Alexander είναι τι υπάρχει σε ένα σχέδιο καλής ποιότητας το οποίο δεν εμφανίζεται σε ένα σχέδιο κακής ποιότητας; Έπειτα, από μελέτη πληθώρας αρχιτεκτονικών κατασκευασμάτων, παρατηρήθηκε ότι αυτές οι κατασκευές που θεωρούνταν καλές είχαν κοινά στοιχεία μεταξύ τους. Τα κοινά αυτά στοιχεία, συνήθως αφορούν κοινές λύσεις ή λύσεις σε κοινά προβλήματα. Σύμφωνα με τον Alexander [3], κάθε πρότυπο περιγράφει ένα πρόβλημα το οποίο προκύπτει ξανά και ξανά στο περιβάλλον, και μετά περιγράφει τον πυρήνα της λύσης αυτού του προβλήματος, με τέτοιο τρόπο που να μπορεί η λύση να χρησιμοποιηθεί πολλές φορές, χωρίς να επαναλαμβάνεται αυτούσια. Αν και ο Alexander διατύπωνε τις ιδέες του σκεπτόμενος την αρχιτεκτονική των κτηρίων, το ίδιο συμβαίνει και με τα αντικειμενοστραφή πρότυπα σχεδίασης του λογισμικού. Πολλοί ερευνητές και ιδιαίτερα στα πλαίσια του αντικειμενοστραφούς προγραμματισμού είχαν θετική άποψη και πρότειναν διάφορες στρατηγικές επίλυσης γνωστών προβλημάτων. Το 1987, ο Kent Beck και ο Ward Cunningham άρχισαν να πειραματίζονται με την ιδέα εφαρμογής προτύπων στο προγραμματισμό και παρουσίασαν τα αποτελέσματα τους στο συνέδριο OOPSLA εκείνου του έτους. Ωστόσο, ο πρώτος συστηματικός κατάλογος προτύπων σχεδίασης λογισμικού προτάθηκε το 1995 από τους Gamma, Helm, Johnson και Vlissides, οι οποίοι είναι γνωστοί ως η Ομάδα των Τεσσάρων (Gang of Four GoF). Στο βιβλίο τους Design Patterns: Elements of Reusable Object-Oriented Software (1995) δημιούργησαν έναν κατάλογο με 23 πρότυπα σχεδίασης δίνοντας για το καθένα από αυτά το όνομα, το πρόβλημα, τη λύση και τις συνέπειες από την εφαρμογή τους, χρησιμοποιώντας παραδείγματα από τη C++ και τη Smalltalk. 1.2 Ταξινόμηση Προτύπων Σχεδίασης 10
Ορισμένα από τα πρότυπα σχεδίασης είναι προφανή ή αποτελούν την εξ ορισμού επιλογή ενός σχεδιαστή λογισμικού. Άλλα πρότυπα αποτελούν λιγότερο προφανείς λύσεις και απαιτείται προσπάθεια για την κατανόηση του προβλήματος που επιλύουν όσο και του τρόπου υλοποίησης τους. Επειδή τα πρότυπα σχεδίασης ποικίλουν ως προς τη χρησιμότητα και το επίπεδο αφαιρετικότητας τους, χρειαζόμαστε έναν τρόπο για να τα οργανώσουμε σε ομάδες. Η πρώτη προσπάθεια ταξινόμησης των προτύπων σχεδίασης έγινε από την Ομάδα των Τεσσάρων [1] με βάση δύο κριτήρια. Το πρώτο κριτήριο είναι ο σκοπός (purpose) που αντανακλά το τι κάνει ένα πρότυπο σχεδίασης. Σύμφωνα με το κριτήριο αυτό τα πρότυπα σχεδίασης χωρίζονται σε τρεις κατηγορίες: Κατασκευαστικά (creational), τα οποία διαπραγματεύονται τη διεργασία δημιουργίας αντικειμένων Δομικά (structural), τα οποία ασχολούνται με τη σύνθεση κλάσεων/αντικειμένων Πρότυπα συμπεριφοράς (behavioral), που χαρακτηρίζουν τους τρόπους με τους οποίους οι κλάσεις αλληλεπιδρούν και κατανέμουν τις αρμοδιότητες. Το δεύτερο κριτήριο αφορά το αντικείμενο επίδρασης (scope), δηλαδή πού κυρίως θα εφαρμοστεί το πρότυπο σχεδίασης σε κλάσεις ή σε αντικείμενα. Τα πρότυπα κλάσεων διαπραγματεύονται τις σχέσεις μεταξύ των κλάσεων και των υποκλάσεων. Αυτές οι σχέσεις δημιουργούνται μέσω κληρονομικότητας, για αυτό είναι στατικές, σταθερές κατά τη διάρκεια μεταγλώττισης (compile-time). Τα πρότυπα αντικειμένων διαπραγματεύονται τις σχέσεις των αντικειμένων που είναι πιο δυναμικές και μπορούν να αλλάξουν κατά τη διάρκεια εκτέλεσης (run-time). Στον Πίνακα 1 εμφανίζεται η ταξινόμηση των προτύπων σχεδίασης σύμφωνα με τα δύο κριτήρια. 11
Purpose Creational Structural Behavioral Interpreter Factory Class Adapter(class) Template Method Method Chain of Responsibility Command Adapter(object) Abstract Iterator Scope Bridge Factory Mediator Composite Object Builder Memento Decorator Prototype Flyweight Facade Singleton Observer Proxy State Strategy Visitor Πίνακας 1: Ταξινόμηση των προτύπων σχεδίασης σύμφωνα με Gamma et all [1] Τα κατασκευαστικά πρότυπα διαχωρίζουν τη διαδικασία δημιουργίας στιγμιότυπων από τις υπόλοιπες διαδικασίες. Ειδικότερα, βοηθούν το σύστημα να είναι ανεξάρτητο από τη δημιουργία, σύνθεση και αναπαράσταση των αντικειμένων του. Ένα κατασκευαστικό πρότυπο κλάσης χρησιμοποιεί την κληρονομικότητα για να διαφοροποιήσει την κλάση που δημιουργείται σαν στιγμιότυπο, ενώ ένα κατασκευαστικό πρότυπο αντικειμένου αναθέτει τη δημιουργία στιγμιότυπου σε κάποιο άλλο αντικείμενο. Τα κατασκευαστικά πρότυπα γίνονται σημαντικά όσο περισσότερο τα συστήματα εξελίσσονται και βασίζονται στη σύνθεση των αντικειμένων, παρά στην κληρονομικότητα των κλάσεων. Προσθέτουν ευελιξία στο «τι» κατασκευάζεται, «ποιος» το κατασκευάζει, «πώς» κατασκευάζεται και «πότε». Επιπλέον, επιτρέπουν τη διαμόρφωση ενός συστήματος με αντικείμενα-προϊόντα τα οποία ποικίλουν σε δομή και λειτουργικότητα. Η διαμόρφωση αυτή μπορεί να είναι είτε στατική (κατά τη διάρκεια μεταγλώττισης), είτε δυναμική (κατά τη διάρκεια εκτέλεσης). 12
Τα δομικά πρότυπα σχεδίασης, ασχολούνται με το πώς συνθέτονται οι κλάσεις και τα αντικείμενα προκειμένου να σχηματίσουν ευρύτερες δομές. Τα δομικά πρότυπα κλάσεων χρησιμοποιούν κληρονομικότητα ώστε να συνθέσουν διεπαφές ή υλοποιήσεις. Αντίθετα, τα δομικά πρότυπα αντικειμένων περιγράφουν τρόπους για τη σύνθεση αντικειμένων που προσθέτουν νέα λειτουργικότητα. Όλη αυτή η επιπρόσθετη ευελιξία της σύνθεσης αντικειμένων προέρχεται από τη δυνατότητα αλλαγής της σύνθεσης στη διάρκεια εκτέλεσης, γεγονός που είναι αδύνατο να συμβεί με τη στατική σύνθεση των κλάσεων. Τα πρότυπα συμπεριφοράς ασχολούνται με τους αλγόριθμους και την ανάθεση των αρμοδιοτήτων ανάμεσα στα αντικείμενα. Τα πρότυπα συμπεριφοράς δεν περιγράφουν απλά πρότυπα αντικειμένων ή κλάσεων αλλά επιπλέον πρότυπα επικοινωνίας μεταξύ τους. Τέλος, μεταφέρουν το ενδιαφέρον από την ροή της εκτέλεσης στο τρόπο ενδοεπικοινωνίας των αντικειμένων. Τα πρότυπα συμπεριφοράς κλάσης χρησιμοποιούν κληρονομικότητα για να ελέγξουν θέματα συμπεριφοράς μεταξύ των κλάσεων. Τα πρότυπα συμπεριφοράς αντικειμένων εστιάζουν περισσότερο στη σύνθεση αντικειμένων παρά στην κληρονομικότητα. Ενδεικτικό είναι το γεγονός ότι, μερικά πρότυπα συμπεριφοράς αντικειμένων περιγράφουν τον τρόπο με τον οποίο μια ομάδα από ομότιμα αντικείμενα συνεργάζονται, ώστε να εκτελέσουν μια εργασία ο- πού ένα μοναδικό αντικείμενο δεν θα μπορούσε να εκτελέσει. Μια άλλη προσέγγιση στη ταξινόμηση των προτύπων σχεδίασης δόθηκε από τον Frank Buschman στο βιβλίο [4]. Στην προσέγγιση αυτή τα πρότυπα σχεδίασης ταξινομούνται σύμφωνα με τρία κριτήρια, τη λειτουργικότητα τους (functionality), τις δομικές αρχές τους (structural principles) και το βαθμό λεπτομέρειας που θα περιλαμβάνει ένα αντικείμενο (granularity). Σε αντίθεση με την ταξινόμηση των Gamma et. al, στην ταξινόμηση του Buschman, ένα πρότυπο σχεδίασης μπορεί να ανήκει σε παραπάνω από μια κατηγορία ανά κριτήριο, γι αυτό δεν είναι και τόσο διαδεδομένη. Τέλος, η ταξινόμηση του Zimmer [5] αναφέρεται στις σχέσεις των προτύπων σχεδίασης που περιγράφονται στο βιβλίο των Gamma et. al. Πιο συγκεκριμένα, η ταξινόμηση αυτή καλύπτει όλες τις σχέσεις μεταξύ των προτύπων όπως ένα πρότυπο χρησιμοποιεί ένα άλλο για τη λύση του ή ένα πρότυπο είναι ίδιο με ένα άλλο στην δημιουργία δομής αντικειμένων. 13
1.3 Πρότυπα σχεδίασης - Design Patterns Στο βιβλίο των Gamma et al [1], καταρτίζεται ένας κατάλογος από 23 πρότυπα σχεδίασης δίνοντας για το καθένα τις παρακάτω πληροφορίες. Όνομα και Κατηγόρια Σκοπός Συνώνυμα Κίνητρο (παράδειγμα προβλήματος) Εφαρμοσιμότητα Δομή (διάγραμμα κλάσεων) Συμμετέχοντες Συνεργασία Συνέπειες (επιτυγχανόμενα πλεονέκτηματα) Υλοποίηση (language specific) Sample Code Γνωστές Χρήσεις (παραδείγματα σε πραγματικά συστήματα) Σχετιζόμενα Πρότυπα Στα πλαίσια της εργασίας, τα παιχνίδια ανοιχτού λογισμικού εξετάζονται για την εμφάνιση 10, από τα συνολικά 23 πρότυπα σχεδίασης, που είναι τα Adapter, Prototype, Composite, Decorator, Singleton, Visitor, Observer, State/Strategy, Template Method, Method Factory. Τα πρότυπα σχεδίασης περιγράφονται αναλυτικότερα παρακάτω. 1.3.1 Προσαρμογέας - Adapter Το πρότυπο σχεδίασης Προσαρμογέας (Adapter) έχει ως στόχο τη μετατροπή της διασύνδεσης μιας κλάσης σε μια άλλη που αναμένει το πρόγραμμα πελάτης. Ο προσαρμογέας επιτρέπει τη συνεργασία κλάσεων, η οποία σε διαφορετική περίπτωση θα ήταν αδύνατη λόγω ασύμβατων διασυνδέσεων. 14
1.3.1.1 Γενικά Συχνά, ο κώδικας μιας κλάσης προσφέρεται για επαναχρησιμοποίηση, αλλά αυτή δεν είναι δυνατή λόγω του ότι τα προγράμματα που επιθυμούν να χρησιμοποιήσουν τις λειτουργίες της, αναμένουν διαφορετική διασύνδεση. Έστω για παράδειγμα ότι μια κλάση Σχεδίασης είναι σε θέση να σχεδιάσει γραμμές, αλλά απαιτεί ως παραμέτρους τις συντεταγμένες στη μορφή (x1, y1, x2, y2), ενώ τα προγράμματα πελάτες είναι σε θέση να παρέχουν τις συντεταγμένες στη μορφή (x1, x2, y1, y2). Στη συνήθη περίπτωση όπου τα προγράμματα πελάτες δεν είναι δυνατόν να τροποποιηθούν (καθώς βρίσκονται ήδη εγκατεστημένα σε διάφορα πεδία εφαρμογών), ενώ η κλάση Σχεδίασης είναι επιθυμητό να χρησιμοποιηθεί χωρίς τροποποίηση (καθώς οποιαδήποτε επέμβαση στον κώδικα μιας μεθόδου είναι δυνατόν να προκαλέσει σφάλματα στις υπόλοιπες μεθόδους), βρίσκει εφαρμογή το πρότυπο Adapter. 1.3.1.2 Γενική Δομή - Εφαρμογή Ένας προσαρμογέας κλάσης (class adapter) χρησιμοποιεί πολλαπλή κληρονομικότητα για να προσαρμόσει μια διασύνδεση σε μια άλλη. Εικόνα 1: Δομή του Προσαρμογέας Κλάσης Ένας προσαρμογέας αντικειμένου (object adapter) βασίζεται στη σύνθεση αντικειμένων και στη διαβίβαση μηνυμάτων (delegation). 15
Εικόνα 2: Δομή του Προσαρμογέα Αντικειμένου 1.3.2 Πρωτότυπο - Prototype Το πρότυπο σχεδίασης Πρωτότυπο (Prototype) καθορίζει το είδος των αντικειμένων που θα δημιουργηθούν χρησιμοποιώντας ένα στιγμιότυπο-πρωτότυπο και δημιουργεί νέα αντικείμενα αντιγράφοντας αυτό το πρωτότυπο. 1.3.2.1 Γενικά Το Prototype είναι ένα σχεδιαστικό πρότυπο με το οποίο ένα νέο αντικείμενο κατασκευάζεται με "κλωνοποίηση" κάποιου υπάρχοντος. Η κλωνοποίηση αυτή γίνεται μέσω μίας μεθόδου clone η οποία παρέχεται από μία αφηρημένη κλάση ή διασύνδεση Α και υλοποιείται σε κάθε παραγόμενη κλάση Β η οποία κληρονομεί την Α. Έτσι η κλήση της clone σε ένα στιγμιότυπο της Β επιστρέφει ένα αντίγραφο του εν λόγω στιγμιότυπου, το οποίο αναλόγως με την υλοποίηση μπορεί να είναι είτε ρηχό, δηλαδή να περιέχει δείκτες προς τις εσωτερικές δομές δεδομένων του αρχικού στιγμιότυπου, είτε βαθύ, δηλαδή να περιέχει πλήρη, νεοδημιουργηθέντα αντίγραφα αυτών των δομών δεδομένων. Το Prototype χρειάζεται σε περιπτώσεις όπου πρέπει να κατασκευαστεί ένα αντίγραφο ενός αντικειμένου, αλλά με κάποιον άλλον τρόπο, π.χ. στην Java με χρήση του τελεστή new και ενός κατασκευαστή αντιγράφου (copy constructor), προσβάλλεται η αρχή Ανοικτής Κλειστής Σχεδίασης. Η μέθοδος clone μπορεί να επικαλύπτει την κλήση του εκάστοτε κατασκευαστή αντιγράφου με ένα κοινό επίπεδο αφαίρεσης έτσι ώστε να μη χρειάζεται το εξωτερικό πρόγραμμα να γνωρίζει ό- λους τους παραγόμενους τύπους δεδομένων που υλοποιούν τη διασύνδεση Α, καθώς η clone επιστρέφει αναφορά του αφηρημένου τύπου Α. 16
1.3.2.2 Γενική Δομή Εφαρμογή To πρότυπο σχεδίασης Prototype θα πρέπει να χρησιμοποιείται όταν ένα σύστημα θα πρέπει να είναι ανεξάρτητο από το πώς κατασκευάζονται, συνθέτονται και αναπαριστώνται τα προϊόντα του και όταν οι κλάσεις που δημιουργούν στιγμιότυπα καθορίζονται στη διάρκεια εκτέλεσης. Εναλλακτικά, χρησιμοποιείται όταν αποφεύγεται η δημιουργία μιας ιεραρχίας κλάσεων «εργοστασίου», που παραλληλίζει την ιεραρχία κλάσεων των προϊόντων ή όταν τα στιγμιότυπα μιας κλάσης μπορούν να έ- χουν μόνο μία από μερικούς διαφορετικούς συνδυασμούς καταστάσεων. Είναι πιο εύκολο να δημιουργηθεί εξαρχής ένας σχετικός αριθμός από πρωτότυπα τα οποία κλωνοποιούνται, παρά να δημιουργούνται στιγμιότυπα από μια κλάση κάθε φορά, για κάθε επιλεγμένη κατάσταση. Εικόνα 3: Δομή του προτύπου σχεδίασης Prototype 1.3.3 Σύνθετο - Composite Το πρότυπο σχεδίασης Σύνθετο (Composite) επιτρέπει τη σύνθεση αντικειμένων σε δενδροειδείς δομές για την αναπαράσταση ιεραρχιών τμήματος-όλου. Το πρότυπο σχεδίασης Σύνθετο επιτρέπει στα προγράμματα πελάτες να διαχειρίζονται με ενιαίο τρόπο τόσο τα ανεξάρτητα αντικείμενα όσο και συνθέσεις αντικειμένων. 1.3.3.1 Γενικά Συχνά, σε μία εφαρμογή, εκτός από μεμονωμένα αντικείμενα (π.χ. μαξιλάρι, ξύλο), υφίστανται και σύνθετα αντικείμενα που περιέχουν ή περιλαμβάνουν άλλα α- ντικείμενα (π.χ. καναπές). Η σχέση περιεκτικότητας μπορεί να είναι ασθενούς μορ- 17
φής (συσσωμάτωση) είτε ισχυρής μορφής (σύνθεση). Η συνήθης αντιμετώπιση μίας τέτοιας περίπτωσης με τη χρήση μιας σχέσης περιεκτικότητας μεταξύ της κλάσης που αντιπροσωπεύει το όλον (περικλείουσα κλάση) και των κλάσεων που αντιπροσωπεύουν τα τμήματα, έχει το μειονέκτημα ότι δεν επιτρέπει τον ομοιόμορφο χειρισμό των αντικειμένων από ένα πρόγραμμα πελάτη. Για παράδειγμα, μία άλλη εφαρμογή θα πρέπει να διατηρεί δείκτες και προς αντικείμενα τύπου Μαξιλάρι, Ξύλο, αλλά και δείκτες προς αντικείμενα τύπου Καναπές. Λαμβάνοντας υπόψη τους πρωταρχικούς στόχους του αντικειμενικού προγραμματισμού, αν προστεθεί στο σύστημα μία νέα σύνθετη κλάση (π.χ. Κρεβάτι), τότε ο κώδικας του προγράμματος πελάτη, θα πρέπει να τροποποιηθεί για να είναι δυνατός ο χειρισμός των νέων αντικειμένων τύπου Κρεβάτι. Η αντιμετώπιση αυτού του προβλήματος, επιτυγχάνεται με έξυπνο τρόπο με το πρότυπο σχεδίασης Composite. Ένα άλλο παράδειγμα που μπορεί να δοθεί αφορά τις εφαρμογές που σχεδιάζουν πολύπλοκα ψηφιακά κυκλώματα VLSI (όπως για παράδειγμα το κύκλωμα ενός επεξεργαστή). Οι εφαρμογές αυτές αντιμετωπίζουν οποιαδήποτε οντότητα ως αντικείμενο. Κάθε αντικείμενο έχει ορισμένες λειτουργίες, μεταξύ αυτών η σχεδίαση του. Ορισμένα από τα αντικείμενα είναι πρωταρχικά και δεν αναλύονται περαιτέρω (π.χ. λογικές πύλες AND, OR, NOT), ενώ άλλα αντικείμενα είναι σύνθετα και αποτελούνται από πρωταρχικές πύλες (π.χ. ένας πλήρης αθροιστής). Ο χρήστης είναι σε θέση να δημιουργήσει οποιαδήποτε σύνθετη οντότητα και να την προσθέσει στην εφαρμογή. Η σχεδίαση ενός σύνθετου αντικειμένου ουσιαστικά συνίσταται στη σχεδίαση των επιμέρους τμημάτων του. Για το λόγο αυτό, είναι επιθυμητή η ενιαία αντιμετώπιση όλων των αντικειμένων. Το πρότυπο Composite, επιτρέπει τον αναδρομικό ορισμό περιεκτικότητας, ώστε οι πελάτες να μην αντιλαμβάνονται τη διαφορά μεταξύ πρωταρχικών και σύνθετων αντικειμένων. Το σημείο-κλειδί στο πρότυπο σχεδίασης Composite είναι η ύπαρξη μιας αφηρημένης κλάσης που αναπαριστά τόσο πρωταρχικές όσο και περικλείουσες κλάσεις. 1.3.3.2 Γενική Δομή Εφαρμογή Το πρότυπο Σύνθετο χρησιμοποιείται όταν είναι επιθυμητό τα προγράμματα πελάτες να μπορούν να χειρίζονται μεμονωμένα αντικείμενα και σύνθετα αντικείμενα με τον ίδιο τρόπο. 18
Εικόνα 4: Διάγραμμα Κλάσεων προτύπου Σύνθετο 1.3.4 Διακοσμητής - Decorator Το πρότυπο σχεδίασης Decorator προσδίδει νέες αρμοδιότητες σε ένα αντικείμενο δυναμικά. Ο Decorator παρέχει έναν ευέλικτο εναλλακτικό τρόπο για δημιουργία υποκλάσεων έτσι ώστε να προστίθεται λειτουργικότητα. 1.3.4.1 Γενικά Πιο συγκεκριμένα, επιτρέπει την εύκολη και δυναμική επέκταση της λειτουργικότητας κάποιων υπαρχόντων κλάσεων Α, Β κλπ, οι οποίες υλοποιούν την ίδια διασύνδεση ή κληρονομούν την ίδια αφηρημένη κλάση (έστω Interface), σε χρόνο εκτέλεσης. Αυτό γίνεται μέσω του Decorator, μίας νέας κλάσης η οποία επίσης υλοποιεί την Interface αλλά περιέχει ως ιδιωτικό πεδίο και μία αναφορά σε ένα στιγμιότυπο του γενικού τύπου Interface (έστω το instance), η οποία τυπικά μεταβιβάζεται ως ό- ρισμα στον κατασκευαστή της Decorator r. Έτσι οι μέθοδοι της τελευταίας υλοποιούν εσωτερικά την καινούργια λειτουργικότητα αλλά για τις κοινές εργασίες καλούν τις αντίστοιχες μεθόδους του instance. Κατά τον χρόνο εκτέλεσης θα μπορούσε το αντικείμενο Decorator να κατασκευάζεται με όρισμα οποιοδήποτε στιγμιότυπο τύπου Interface (ακόμα και του ίδιου του Decorator, αν και αυτό δε θα είχε ιδιαίτερο νόημα) ώστε κατά περίπτωση το αντικείμενο να παρέχει τη λειτουργικότητα οποιασδήποτε κλάσης τύπου Interface, είτε της Α είτε κάποιας άλλης, εκτεταμένης με ένα συγκεκριμένο σύνολο δυνατοτήτων. Με αυτόν τον τρόπο γίνεται εφικτός ένας δυναμικός 19
συνδυασμός λειτουργιών από στοιχειώδεις δομικούς λίθους κατά τον χρόνο εκτέλεσης. Η εναλλακτική λύση, χωρίς χρήση κάποιου σχεδιαστικού προτύπου, θα ήταν η απλή κληρονομικότητα, με τον ορισμό κλάσεων οι οποίες επεκτείνουν τις Α, Β κλπ. και προσθέτουν τη νέα λειτουργικότητα. Ωστόσο η λύση αυτή δεν είναι εφικτή σε περίπτωση που οι Α, Β κλπ. δεν μπορούν να επεκταθούν με κληρονομικότητα, ενώ σε άλλες περιπτώσεις δεν είναι καθόλου πρακτική, π.χ. αν έχουμε πολλαπλά διαφορετικά σύνολα νέων δυνατοτήτων τα οποία πρέπει να συνδυαστούν με τις Α, Β κλπ. Το πρόβλημα έγκειται στο ότι με την κληρονομικότητα όλοι οι πιθανοί συνδυασμοί δυνατοτήτων πρέπει να προβλεφθούν και να ληφθούν υπ όψιν κατά τη συγγραφή του προγράμματος. Αντιθέτως με την κλάση Decorator, η οποία δρα ως περίβλημα (Wrapper) άλλων αντικειμένων τύπου Interface προς τα οποία περιέχει αναφορές / δείκτες, η σύνθεση νέων αντικειμένων ουσιαστικά γίνεται δυναμικά ενώ το πρόγραμμα εκτελείται. 1.3.4.2 Γενική Δομή - Εφαρμογή Το πρότυπο σχεδίασης Decorator χρησιμοποιείται όταν πρέπει να αυξηθούν οι υπευθυνότητες ξεχωριστών αντικειμένων, δυναμικά και με διαφανή τρόπο, χωρίς να επηρεαστούν άλλα αντικείμενα. Επιπλέον χρησιμοποιείται, όταν κάποιες υπευθυνότητες αντικειμένων μπορούν να παραμεριστούν και όταν η επέκταση με χρήση υποκλάσεων είναι μη πρακτική. Εικόνα 5: Διάγραμμα κλάσεων προτύπου Decorator 20
1.3.5 Μοναδιαίο - Singleton Το πρότυπο σχεδίασης Μοναδιαίο (Singleton) εξασφαλίζει ότι μια κλάση θα έχει μόνο ένα στιγμιότυπο και παρέχει ένα καθολικό σημείο πρόσβασης σε αυτό. 1.3.5.1 Γενικά Συνήθως μεταξύ κλάσεων και στιγμιοτύπων τους υπάρχει μία σχέση ένα-προςπολλά. Κατά τη διαδικασία ανάλυσης, η ύπαρξη πολλών στιγμιοτύπων της ίδιας έννοιας στο σύστημα υποδηλώνει την αναγκαιότητα μιας κλάσης. Τα αντικείμενα δημιουργούνται δεσμεύοντας χώρο στη μνήμη όποτε κρίνεται σκόπιμο και διαγράφονται από τη μνήμη όταν τερματιστεί η χρήση τους. Ορισμένες φορές όμως, απαιτείται η ύπαρξη κλάσεων από τις οποίες παράγεται ένα μόνο αντικείμενο. Πολύ συχνά, το α- ντικείμενο αυτό συνήθως δημιουργείται κατά την έναρξη της εφαρμογής και διαγράφεται με το πέρας της. Ο ρόλος του μοναδικού αυτού αντικειμένου είναι η διαχείριση των υπολοίπων αντικειμένων της εφαρμογής και για το λόγο αυτό, αποτελεί λογικό σφάλμα να δημιουργηθούν περισσότερα του ενός τέτοια αντικείμενα διαχειριστές (managers ή controllers). Σε μια τέτοια περίπτωση, η εφαρμογή θα έχει περισσότερα του ενός σημεία εκκίνησης, και ο υποθετικός χρήστης, ανάλογα με το σημείο εκκίνησης, μπορεί να καταλήξει να χρησιμοποιεί ένα υποσύνολο των αντικειμένων του συστήματος. Επιπρόσθετα, αν υπάρχουν περισσότεροι του ενός διαχειριστές, ενώ ο επιθυμητός στόχος είναι η ακολουθιακή εκτέλεση δραστηριοτήτων, πολλές δραστηριότητες θα εκτελούνται παράλληλα. Το πρότυπο σχεδίασης Singleton, εξασφαλίζει τη δημιουργία ενός και μόνο α- ντικειμένου, περιλαμβάνοντας μια ειδικά μέθοδο κατασκευής στιγμιοτύπων: Όταν καλείται αυτή η μέθοδος, ελέγχει αν κάποιο αντικείμενο έχει ήδη δημιουργηθεί. Αν έχει δημιουργηθεί, η μέθοδος επιστρέφει απλώς ένα δείκτη προς το υπάρχον αντικείμενο, σε αντίθετη περίπτωση, η μέθοδος δημιουργεί ένα νέο αντικείμενο και επιστρέφει δείκτη προς αυτό. Για να εξασφαλιστεί ότι αυτός είναι ο μοναδικός τρόπος δημιουργίας αντικειμένων από αυτή την κλάση, ο κατασκευαστής της κλάσης δηλώνεται ως προστατευμένος (protected) ή ιδιωτικός (private). Με τον τρόπο αυτό, δεν είναι δυνατόν να δημιουργηθεί ένα αντικείμενο παρακάμπτοντας την παραπάνω ειδική μέθοδο. 21
Παρακάτω αναφέρονται παραδείγματα τα οποία δείχνουν τη πιθανή χρήση του προτύπου σχεδίασης Singleton. Ενώ σε ένα σύστημα μπορεί να υπάρχουν πολλοί ε- κτυπωτές συνδεδεμένοι, υπάρχει μόνο μια εφαρμογή διαχείρισης των αρχείων που πρόκειται να εκτυπωθούν. Επιπλέον, υπάρχει μόνο ένα σύστημα αρχείων και ένας διαχειριστής παραθύρων. Ένα λογιστικό πρόγραμμα σχεδιάζεται για τη διαχείριση των οικονομικών μιας μόνο εταιρίας. Τέλος, σε μια κεντρική μονάδα επεξεργασίας ενός υπολογιστή, παρόλο που είναι δυνατόν να υπάρχουν πολλοί καταχωρητές γενικής χρήσης, πρέπει να υπάρχει μόνο ένας καταχωρητής (συσσωρευτής Accumulator) στον οποίο καταλήγουν τα αποτελέσματα των αριθμητικών και λογικών πράξεων. 1.3.5.2 Γενική Δομή - Εφαρμογή Το πρότυπο σχεδίασης Singleton χρησιμοποιείται όταν σε κάποιο σύστημα λογισμικού υπάρχει η απαίτηση από μια κλάση να δημιουργείται ένα και μόνο ένα αντικείμενο. Εικόνα 6: Δομή του προτύπου Μοναδιαίο 1.3.6 Επισκέπτης - Visitor Το πρότυπο σχεδίασης Επισκέπτης (Visitor) έχει ως στόχο την αναπαράσταση μιας λειτουργίας που πρόκειται να πραγματοποιηθεί στα στοιχεία μιας δομής α- ντικειμένων. Ειδικότερα, το πρότυπο επιτρέπει τον ορισμό μιας νέας λειτουργίας χωρίς την τροποποίηση των κλάσεων των στοιχείων στα οποία επιδρά. 1.3.6.1 Γενικά Πολύ συχνά απαιτείται η προσθήκη μιας νέας μεθόδου σε μια υπάρχουσα ιεραρχία κλάσεων αλλά είναι εξαιρετικά δύσκολο να τροποποιηθούν οι ίδιες κλάσεις της ιεραρχίας. Η προσέγγιση αυτή ενθαρρύνει τη σχεδίαση ιεραρχιών από στοιχεία 22