ΕΥΡΕΤΙΚΟΙ ΚΑΝΟΝΕΣ. 7.1 Εισαγωγή



Σχετικά έγγραφα
Ευρετικοί Κανόνες Αντικειμενοστρεφούς Σχεδίασης. Object-Oriented Design Heuristics

ΠΟΛΥΜΟΡΦΙΣΜΟΣ. 4.1 Κληρονομικότητα και Αρχή της Υποκατάστασης

public void printstatement() { System.out.println("Employee: " + name + " with salary: " + salary);

Αρχιτεκτονική Λογισμικού

Αντικειμενοστρεφής Προγραμματισμός

Διαγράμματα UML στην Ανάλυση. Μέρος Γ Διαγράμματα Επικοινωνίας Διαγράμματα Ακολουθίας Διαγράμματα Μηχανής Καταστάσεων

ΛΟΓΙΣΜΟΣ ΜΙΑΣ ΜΕΤΑΒΛΗΤΗΣ, ΕΣΠΙ 1

Διαδικασίες παραγωγής λογισμικού. Βασικές αρχές Τεχνολογίας Λογισμικού, 8η αγγ. έκδοση

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ Η/Υ Ακαδημαϊκό έτος ΤΕΤΡΑΔΙΟ ΕΡΓΑΣΤΗΡΙΟΥ #4

ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΑΝΑΛΥΣΗ Επιχειρηματική Μοντελοποίηση. Ιωάννης Σταμέλος Βάιος Κολοφωτιάς Πληροφορική

Διαγράμματα Κλάσεων στη Σχεδίαση

Εισαγωγή στον Αντικειμενοστρεφή Προγραμματισμό Διάλεξη #2

Η κατασκευή αντικειμένων της κλάσης Student μπορεί να πραγματοποιηθεί είτε στη main είτε σε οποιαδήποτε μέθοδο κλάσης:

Δομημένος Προγραμματισμός (ΤΛ1006)

Περιεχόμενο του μαθήματος

Πίνακας Περιεχομένων. μέρος A 1 Εισαγωγή στην Τεχνολογία Λογισμικού

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

Ανάπτυξη & Σχεδίαση Λογισμικού (ΗΥ420)

Προγραμματισμός Ι. Κλάσεις και Αντικείμενα. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Η πρώτη παράμετρος είναι ένα αλφαριθμητικό μορφοποίησης

Από τη UML στον Κώδικα. Μέρος Α

Αναφορές, είκτες και Αλφαριθμητικά

Εργαστήριο Τεχνολογίας Λογισμικού και Ανάλυσης Συστημάτων - 4 ο Εργαστήριο -

Εισαγωγή στη Σχεδίαση Λογισμικού

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αντικείμενα με πίνακες. Constructors. Υλοποίηση Στοίβας

Εισαγωγή στον Προγραµµατισµό, Αντώνιος Συµβώνης, ΣΕΜΦΕ, ΕΜΠ,, Slide 6

Κλάσεις και αντικείμενα #include <iostream.h<

Προβλήματα, αλγόριθμοι, ψευδοκώδικας

Κλήση Συναρτήσεων ΚΛΗΣΗ ΣΥΝΑΡΤΗΣΕΩΝ. Γεώργιος Παπαϊωάννου ( )

Προγραμματισμός Η/Υ (ΤΛ2007 )

Διάγραμμα Κλάσεων. Class Diagram

ΠΑΝΕΠΙΣΤΗΜΙΟ ΑΙΓΑΙΟΥ

2.1 Αντικειµενοστρεφής προγραµµατισµός

2.1. Εντολές Σχόλια Τύποι Δεδομένων

ΕΠΛ131 Αρχές Προγραμματισμού

3 Αλληλεπίδραση Αντικειμένων

ΠΛΗΡΟΦΟΡΙΚΗ Ι JAVA Τμήμα θεωρίας με Α.Μ. σε 3, 7, 8 & 9 6/12/07

Εισαγωγή σε αντικειμενοστραφή concepts. Και λίγη C#

Συναρτήσεις. Εισαγωγή

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Δημιουργώντας δικές μας Κλάσεις και Αντικείμενα

Τεχνολογία Λογισμικού. Ενότητα 1: Εισαγωγή στην UML Καθηγητής Εφαρμογών Ηλίας Γουνόπουλος Τμήμα Διοίκησης Επιχειρήσεων (Γρεβενά)

Σχεδιασµός βασισµένος σε συνιστώσες

Προγραμματισμός Υπολογιστών & Εφαρμογές Python. Κ.Π. Γιαλούρης

Εργαστήριο «Τεχνολογία Πολιτισμικού Λογισμικού» Ενότητα. Επεξεργασία πινάκων

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Πίνακες Κλάσεις και Αντικείμενα

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Αντικείμενα ως ορίσματα

Προγραμματισμός Ι (ΗΥ120)

ΠΕΡΙΕΧΟΜΕΝΑ. Μονοδιάστατοι πίνακες Πότε πρέπει να χρησιμοποιούνται πίνακες Πολυδιάστατοι πίνακες Τυπικές επεξεργασίες πινάκων

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

Αντικειμενοστρεφής Προγραμματισμός

ΠΑΝΕΠΙΣΤΗΜΙΟ ΑΙΓΑΙΟΥ

ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΑΝΑΛΥΣΗ Διαγράμματα Αλληλεπίδρασης. Ιωάννης Σταμέλος Βάιος Κολοφωτιάς Πληροφορική

1 Συστήματα Αυτοματισμού Βιβλιοθηκών

6. ΠΙΝΑΚΕΣ & ΑΛΦΑΡΙΘΜΗΤΙΚΑ

ΑΝΑΠΤΥΞΗ ΕΦΑΡΜΟΓΩΝ ΣΕ ΠΡΟΓΡΑΜΜΑΤΙΣΤΙΚΟ ΠΕΡΙΒΑΛΛΟΝ ΜΑΡΙΑ Σ. ΖΙΩΓΑ ΚΑΘΗΓΗΤΡΙΑ ΠΛΗΡΟΦΟΡΙΚΗΣ ΕΙΣΑΓΩΓΗ ΣΤΟΝ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟ

Αντικειμενοστραφής Προγραμματισμός I (5 ο εξ) Εργαστήριο #2 ο : Ανατομία προγραμμάτων εφαρμογών, η

Browsers. Λειτουργικότητα και Παραμετροποίηση

J-GANNO. Σύντοµη αναφορά στους κύριους στόχους σχεδίασης και τα βασικά χαρακτηριστικά του πακέτου (προέκδοση 0.9Β, Φεβ.1998) Χάρης Γεωργίου

POINTERS, AGGREGATION, COMPOSITION

Εφαρμογή Μεθοδολογίας ICONIX

I (JAVA) Ονοματεπώνυμο: Α. Μ.: Δώστε τις απαντήσεις σας ΕΔΩ: Απαντήσεις στις σελίδες των ερωτήσεων ΔΕΝ θα ληφθούν υπ όψην.

Διαδικασιακός Προγραμματισμός

2 Ορισμός Κλάσεων. Παράδειγμα: Μηχανή για Εισιτήρια. Δομή μιας Κλάσης. Ο Σκελετός της Κλάσης για τη Μηχανή. Ορισμός Πεδίων 4/3/2008

Δομημένος Προγραμματισμός (ΤΛ1006)

Προγραμματισμός Ι. Δυναμική Διαχείριση Μνήμης. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Αντικειμενοστραφής Προγραμματισμός I(5 ο εξ) Εργαστήριο #2 ο : Ανατομία προγραμμάτων εφαρμογών, η

Προγραμματισμός Η/Υ (ΤΛ2007 )

Εισαγωγή στον Προγραμματισμό

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Σύνθεση αντικειμένων

ΣΧΕ ΙΑΣΗ ΑΝΤΙΚΕΙΜΕΝΩΝ ΜΕ ΑΡΜΟ ΙΟΤΗΤΕΣ. Ορισµός σχεδιαστικών προτύπων Εφαρµογή των 9 GRASP προτύπων

Σχεδιασμός Οικολογικού Διαμεσολαβητή για την εποπτεία και διαχείριση δικτύου διανομής ηλεκτρικής ενέργειας

ΕΠΛ131 Αρχές Προγραμματισμού

Προγραμματισμός Η/Υ Ι (Χρήση της C) 6 η Θεωρία ΜΟΝΟΔΙΑΣΤΑΤΟΙ ΠΙΝΑΚΕΣ

ΚΕΦΑΛΑΙΟ 5. Κύκλος Ζωής Εφαρμογών ΕΝΟΤΗΤΑ 2. Εφαρμογές Πληροφορικής. Διδακτικές ενότητες 5.1 Πρόβλημα και υπολογιστής 5.2 Ανάπτυξη εφαρμογών

ΚΕΦΑΛΑΙΟ 8 Η ΓΛΩΣΣΑ PASCAL

Αντικειμενοστρεφής Προγραμματισμός

Υπολογισμός - Εντολές Ελέγχου

Πίνακες (Arrays) Εισαγωγή στη C++

Διαδικασιακός Προγραμματισμός

Εντολές εισόδου - εξόδου. Εισαγωγή στη C++

Περιεχόµενα. 1 Εισαγωγή στις οµές εδοµένων 3. 2 Στοίβα (Stack) 5

«ΕΙΔΙΚΑ ΘΕΜΑΣΑ ΣΟΝ ΠΡΟΓΡΑΜΜΑΣΙΜΟ ΤΠΟΛΟΓΙΣΩΝ» Κεφάλαιο 4: Αντικειμενοςτρεφήσ Προγραμματιςμόσ

I (JAVA) Ονοματεπώνυμο: Α. Μ.: Δώστε τις απαντήσεις σας ΕΔΩ: Απαντήσεις στις σελίδες των ερωτήσεων ΔΕΝ θα ληφθούν υπ όψην.

Υπολογισμός - Εντολές Επανάληψης

ΒΑΣΙΚΟΙ ΤΥΠΟΙ ΚΑΙ ΠΙΝΑΚΕΣ

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα Αναφορές

Περιεχόμενο του μαθήματος

ΑΤΕΙ ΘΕΣΣΑΛΟΝΙΚΗΣ ΤΜΗΜΑ ΜΗΧΑΝΙΚΩΝ ΠΛΗΡΟΦΟΡΙΚΗΣ Αλγοριθμική και Προγραμματισμός. Παναγιώτης Σφέτσος

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Δημιουργία Κλάσεων και Αντικειμένων

Δοκιμή και Αποσφαλμάτωση Testing and Debugging

3. Προσομοίωση ενός Συστήματος Αναμονής.

Αντικειμενοστρεφής Προγραμματισμός Διάλεξη 2 : ΜΕΤΑΒΛΗΤΕΣ ΤΕΛΕΣΤΕΣ & ΕΚΦΡΑΣΕΙΣ ΕΛΕΓΧΟΣ ΡΟΗΣ

ΥΠΟΛΟΓΙΣΤΕΣ ΙΙ. Τι περιλαμβάνει μια μεταβλητή; ΔΕΙΚΤΕΣ. Διεύθυνση μεταβλητής. Δείκτης

ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΗΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ

Αντικειμενοστρέφεια. Henri Matisse, Harmony in Red, Κωστής Σαγώνας Νίκος Παπασπύρου

Διάλεξη 07: Λίστες Ι Υλοποίηση & Εφαρμογές

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Κλάσεις και Αντικείμενα Constructors, equals, tostring

ΤΕΧΝΙΚΕΣ ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΑΦΟΥΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ. Πίνακες Κλάσεις και Αντικείμενα

Ανάπτυξη Plugins για το AgentSheets

SNMP ΔΙΑΧΕΙΡΙΣΗ ΔΙΚΤΥΟΥ ΒΑΣΙΚΕΣ ΕΝΝΟΙΕΣ

Transcript:

ΕΥΡΕΤΙΚΟΙ ΚΑΝΟΝΕΣ 7.1 Εισαγωγή Ένα τυπικό πρόβλημα κατά την ανάπτυξη οποιουδήποτε συστήματος και συνεπώς και του αντικειμενοστρεφούς λογισμικού, είναι το ερώτημα που τίθεται μετά το πέρας της σχεδίασης: "Πρόκειται για μια καλή σχεδίαση ή όχι;" Συνήθως το ερώτημα απαντάται από τους πλέον έμπειρους σχεδιαστές της ομάδας ανάπτυξης ("γκουρού"), για τους οποίους μια σχεδίαση είναι καλή όταν "αισθάνονται" ότι όσα έχουν ελέγξει είναι εντάξει. Αυτό που στην πραγματικότητα συμβαίνει, είναι να ελέγξει ο κάθε ειδικός υποσυνείδητα ή συστηματικά μια λίστα ευρετικών κανόνων (heuristics) που καθόρισε ο ίδιος με βάση την εμπειρία του. Αν η σχεδίαση περνά τα τεστ των κανόνων, το σύστημα δίνει την αίσθηση "καλής" σχεδίασης. Οι ευρετικοί κανόνες συνιστούν επομένως οδηγίες για την εφαρμογή καλών πρακτικών. Διάφοροι συγγραφείς, σε μια προσπάθεια να εξαλείψουν την αναγκαιότητα τέτοιων εμπειρογνωμόνων, πρότειναν ή κατέγραψαν τους ευρετικούς κανόνες που χρησιμοποιούνται συνηθέστερα στην πράξη της αντικειμενοστρεφούς σχεδίασης. Η συστηματικότερη ταξινόμηση πραγματοποιήθηκε από τον Arthur J. Riel, ο οποίος κατέγραψε 68 εμπειρικούς κανόνες που χρησιμοποιούνται ευρέως στη βιομηχανία λογισμικού. Οι κανόνες αυτοί δεν είναι αυστηρές προδιαγραφές που πρέπει να τηρούνται υποχρεωτικά. Μάλιστα, πολλοί κανόνες έρχονται σε αντίθεση μεταξύ τους και είναι στην αρμοδιότητα του σχεδιαστή να αποφασίσει ποιο δρόμο θα ακολουθήσει. Θα πρέπει να εκλαμβάνονται ως προειδοποιητικά σήματα τα οποία ενεργοποιούνται όταν ο αντίστοιχος κανόνας παραβιαστεί. Καθώς κάθε δραστηριότητα μηχανικής υπόκειται σε συμβιβασμούς θα πρέπει να εξετάζονται κάθε φορά τα πλεονεκτήματα και τα μειονεκτήματα που προκύπτουν από την εφαρμογή ενός κανόνα: Για παράδειγμα, υπάρχουν αλλαγές που μειώνουν την πολυπλοκότητα αλλά ταυτοχρόνως μειώνουν και την ευελιξία του λογισμικού. Το γεγονός δε, ότι πρόκειται για εμπειρικούς κανόνες, δυσκολεύει την αυτοματοποίηση των περισσοτέρων από αυτούς. Οι ευρετικοί κανόνες έχουν διττή χρήση: Αφενός, μια λίστα από βασικούς κανόνες πρέπει να λαμβάνεται διαρκώς υπόψη κατά τη διάρκεια σχεδίασης και υλοποίησης ενός 211

212 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ συστήματος λογισμικού. Ένας περιορισμένος αριθμός τέτοιων κανόνων, όπως "Διατηρείτε σχετιζόμενα δεδομένα και συμπεριφορά σε ένα σημείο", είναι σχετικά εύκολο να διατηρείται στη μνήμη κάθε σχεδιαστή ως αποτέλεσμα προηγούμενης εμπειρίας και να εφαρμόζεται κατά τον εντοπισμό κλάσεων και των μεταξύ τους σχέσεων. Είναι αυτονόητο ότι ένας σχεδιαστής δεν μπορεί να διατρέχει νοερά την πλήρη λίστα των ευρετικών κανόνων: οι υπόλοιποι χρησιμοποιούνται μετά το πέρας της σχεδίασης για τον εντοπισμό σχεδιαστικών σφαλμάτων ή αδυναμιών. Προς την κατεύθυνση αυτή συμβάλλουν και τα διάφορα περιβάλλοντα ανάπτυξης που μπορούν με αυτόματο τρόπο να εντοπίσουν παραβιάσεις ορισμένων ευρετικών κανόνων καθώς και το βαθμό υπέρβασης κάποιων από αυτούς με τη χρήση μετρικών. Στη συνέχεια, απαιτείται αναδόμηση (refactoring) των σχετικών τμημάτων του λογισμικού με στόχο τη συμμόρφωση με τους υπό εξέταση ευρετικούς κανόνες. Λόγω του πλήθους των ευρετικών κανόνων αλλά και του γεγονότος ότι πολλοί από αυτούς ενυπάρχουν στα πρότυπα και στις αρχές σχεδίασης που ήδη συζητήθηκαν, στη συνέχεια θα αναφερθούν ενδεικτικά ορισμένοι. Σε ορισμένους κανόνες η τεκμηρίωση είναι πληρέστερη αλλά στις περισσότερες περιπτώσεις η χρησιμότητά τους θα παρουσιάζεται στα πλαίσια κάποιου παραδείγματος. 7.2 Σύγκριση δομών διαδικασιακών και αντικειμενοστρεφών συστημάτων Όπως έχει ήδη αναφερθεί, ο αντικειμενοστρεφής τρόπος επίλυσης προβλημάτων διαφέρει ριζικά από αυτόν που ακολουθείται στις διαδικασιακές γλώσσες προγραμματισμού. Η κυριότερη διαφορά έγκειται στο ότι η περιγραφή αλλά και η λύση του προβλήματος επιδιώκεται να πραγματοποιηθεί ως αποτέλεσμα της συνεργασίας μεταξύ αντικειμένων. Η διαφορά αυτή περιγράφεται παραστατικά στο παράδειγμα των οκτώ βασιλισσών. Στο σκάκι, μία βασίλισσα μπορεί να επιτεθεί σε οποιοδήποτε πιόνι που βρίσκεται στην ίδια γραμμή, στην ίδια στήλη ή κατά μήκος μιας διαγωνίου. Οι οκτώ βασίλισσες (eight queens) είναι ένα κλασσικό παιχνίδι λογικής. Ο στόχος είναι να τοποθετηθούν οκτώ βασίλισσες σε μία σκακιέρα κατά τέτοιο τρόπο ώστε καμία βασίλισσα να μην μπορεί να επιτεθεί σε μια άλλη. Στο πρόβλημα αυτό υπάρχουν πολλαπλές λύσεις. Σε μια συμβατική επίλυση με κάποια διαδικασιακή γλώσσα προγραμματισμού, κάποια δομή δεδομένων θα χρησιμοποιούνταν για να αποθηκεύει τη θέση των πιονιών στη σκακιέρα. Στη συνέχεια, μία κεντρική συνάρτηση θα έλυνε το πρόβλημα τροποποιώντας με συστηματικό τρόπο τις τιμές στη δομή δεδομένων, ελέγχοντας για κάθε νέα θέση αν πληρείται η συνθήκη ότι καμία βασίλισσα δεν μπορεί να επιτεθεί σε κάποια άλλη. Μεταφορικά, η διαφορά μεταξύ της αντικειμενοστρεφούς και συμβατικής λύσης μπορεί να περιγραφεί ως εξής: Ένα συμβατικό πρόγραμμα μοιάζει με κάποιο άτομο που παρακολουθεί τη σκακιέρα και μετακινεί τις βασίλισσες, οι οποίες δεν προσομοιώνουν κάποια συμπεριφορά από μόνες τους. Σε μια αντικειμενοστρεφή επίλυση, παρέχεται η δυνατότητα στις βασίλισσες να επιλύσουν το πρόβλημα από μόνες τους. Με άλλα λόγια, αντί να υπάρχει μία μονολιθική οντότητα που ελέγχει το αποτέλεσμα, η ευθύνη για την εύρεση μιας λύσης κατανέμεται μεταξύ αλληλεπιδρώντων οντοτήτων. Είναι σαν να έχουν οι βα-

7 Ευρετικοί κανόνες 213 σίλισσες κάποια μορφή ζωής και να αλληλεπιδρούν μεταξύ τους με στόχο την ορθή τοποθέτησή τους στη σκακιέρα. Συνεπώς, η ουσία της αντικειμενοστρεφούς επίλυσης είναι η δημιουργία αντικειμένων που αντιπροσωπεύουν κάθε μία από τις βασίλισσες, και η προσθήκη σε αυτά τα αντικείμενα λειτουργιών για την εύρεση της λύσης. Η μεθοδολογία αυτή ονομάζεται συχνά υπολογισμός-ως-προσομοίωση (computing-as-simulation) όπου δημιουργείται ένα μοντέλο του περιβάλλοντος (σύμπαντος), καθορίζεται η συμπεριφορά των αντικειμένων σε αυτό το περιβάλλον και στη συνέχεια το περιβάλλον τίθεται σε κίνηση. Όταν η δραστηριότητα του περιβάλλοντος σταθεροποιηθεί, έχει βρεθεί μια λύση. Queen -column : int -row : int +checkneighbors() +advance() Σχήμα 7.2.1: Επίλυση του προβλήματος των οκτώ βασιλισσών με βάση το αντικειμενοστρεφές μοντέλο Σε μια συγκεκριμένη επίλυση, κάθε βασίλισσα μπορεί να μοντελοποιηθεί ως αντικείμενο μιας κλάσης, εφοδιασμένης με λειτουργίες ελέγχου απειλής (αν η συγκεκριμένη βασίλισσα απειλείται από κάποια άλλη) και προώθησης σε άλλη θέση, καθώς και ιδιότητες όπως η στήλη και η γραμμή της σκακιέρας (Σχήμα 7.2.1). (Η υλοποίηση αφήνεται ως ά- σκηση στον αναγνώστη). Στο μοντέλο αυτό, οι βασίλισσες απλώς αρχικοποιούνται (λαμβάνουν τυχαίες θέσεις στη σκακιέρα) και "αφήνονται" να επιλύσουν το πρόβλημα επικοινωνώντας (αποστέλλοντας μηνύματα) μεταξύ τους. Καθώς κάθε ξεχωριστή βασίλισσα είναι ένα διακριτό αντικείμενο της ίδιας κατηγορίας, ο γενικός ευρετικός κανόνας που διέπει την ανωτέρω φιλοσοφία είναι ο εξής:

214 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ Ευρετικός Κανόνας 1 Επιδιώκετε την μοντελοποίηση της πραγματικότητας οποτεδήποτε αυτό είναι δυνατό. Ωστόσο, η εφαρμογή του ανωτέρω γενικού κανόνα, αν και απλή κατά τη διάρκεια ανάπτυξης του εννοιολογικού μοντέλου ενός συστήματος, έρχεται συχνά σε αντίθεση με άλλες αρχές και ευρετικούς κανόνες, ειδικά κατά τη διάρκεια της υλοποίησης. Στη συνέχεια θα παρουσιαστούν οι διαφορές που προκύπτουν στη δομή του κώδικα, ανάλογα με το αν θα χρησιμοποιηθεί το διαδικασιακό ή το αντικειμενοστρεφές μοντέλο. Ωστόσο, τα πλεονεκτήματα που ισχύουν θεωρητικά, αναιρούνται συχνά στην πράξη αν δεν ληφθούν συγκεκριμένοι παράγοντες υπόψη. Θεωρούμε μια απλουστευμένη εφαρμογή καταχώρησης αριθμών φορολογικού μητρώου στη γλώσσα προγραμματισμού C. Η εφαρμογή αρχικά διαχωρίζεται σε τέσσερις συναρτήσεις: Μια συνάρτηση issuenew() που αναλαμβάνει να εισάγει τον αριθμό που λαμβάνει από το πληκτρολόγιο σε μια απλή δομή δεδομένων (για το παράδειγμα, έναν πίνακα ακεραίων). Για λόγους απλότητας, θεωρούμε ότι το πρώτο ψηφίο κάθε αριθμού δεν μπορεί να είναι μηδέν καθώς και ότι αρχικά ούτε το τελευταίο ψηφίο μπορεί να είναι μηδέν. Μια δεύτερη συνάρτηση checkvalidity() ελέγχει αν όλοι οι αριθμοί που έ- χουν καταχωρηθεί είναι έγκυροι (αν έχουν δηλαδή τα εννέα απαιτούμενα ψηφία ενός α- ριθμού φορολογικού μητρώου). Η συνάρτηση countbylastdigit() ταξινομεί τους αριθμούς με βάση το τελευταίο ψηφίο τους και αποθηκεύει το πλήθος κάθε ομάδας σε έναν δεύτερο πίνακα, ώστε να είναι γνωστό πόσοι φορολογούμενοι υπάρχουν σε κάθε ομάδα. Τέλος, μια συνάρτηση printnumbers() εκτυπώνει το πλήθος των αριθμών φορολογικού μητρώου κάθε ομάδας με βάση το τελευταίο ψηφίο. Ο κώδικας είναι σχετικά απλός και παρουσιάζεται στη συνέχεια (στη συνάρτηση main καλούνται και ελέγχονται οι υπόλοιπες συναρτήσεις): #include <stdio.h> //Πρωτότυπα συναρτήσεων void issuenew(int A[], int* size); void checkvalidity(int A[], int size); void countbylastdigit(int A[], int size, int B[]); void printnumbers(int A[]); int main() { int numbers[100]; //πίνακας αριθμών φορολογ. μητρώου int currentsize = 0; //τρέχον μέγεθος πίνακα int count[9]; //πίνακας πλήθους κάθε ομάδας αριθμών

7 Ευρετικοί κανόνες 215 issuenew(numbers, &currentsize); issuenew(numbers, &currentsize); checkvalidity(numbers, currentsize); countbylastdigit(numbers, currentsize, count); printnumbers(count); return 0; //έκδοση νέου αριθμού void issuenew(int A[], int* size) { int input; printf("enter new number: "); scanf("%d", &input); A[*size] = input; (*size)++; //έλεγχος ορθότητας αριθμών void checkvalidity(int A[], int size) { int i; int length; int temp; for(i=0; i<size; i++) //για όλα τα στοιχεία του πίνακα { length = 1; temp = A[i]; while (temp > 9) //πραγματοποίηση διαδοχικών { //διαιρέσεων με το 10 temp = temp / 10; //όσο το πηλίκο είναι μεγαλύτερο length++; //του 9, αύξηση του μετρητή κατά 1 if(length!= 9) //αν αριθμός ψηφίων διάφορος του 9 { printf("error with number at position %d \n", i); return; printf("all OK with number of digits!\n"); //μέτρηση πλήθους αριθμών κάθε ομάδας με βάση //το τελευταίο ψηφίο void countbylastdigit(int A[], int size, int B[]) { int i; int lastdigit;

216 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ //τελευταίο ψηφίο: από το 1 μέχρι και το 9 for(i=1; i<10; i++) B[i] = 0; for(i=0; i<size; i++) { lastdigit = A[i] % 10; B[lastDigit]++; //εκτύπωση πλήθους αριθμών κάθε ομάδας void printnumbers(int A[]) { int i; printf("count by last digit: \n"); //τελευταίο ψηφίο: από το 1 μέχρι και το 9 for(i=1; i<10; i++) printf("%d: %10d\n", i, A[i]); Στον διαδικασιακό προγραμματισμό, οι δομές δεδομένων δημιουργούνται ως δεύτερη σκέψη, κατά τη διάρκεια της υλοποίησης των συναρτήσεων. Τα μέλη της ομάδας ανάπτυξης αντιλαμβάνονται κατά την υλοποίηση ότι ορισμένες συναρτήσεις μπορούν να μοιράζονται τμήματα των υποκείμενων δεδομένων περνώντας τα αντίστοιχα δεδομένα ως παραμέτρους στις συναρτήσεις. Στο ανωτέρω πρόγραμμα, η συνάρτηση main διατηρεί όλα τα δεδομένα και κάθε συνάρτηση έχει πρόσβαση σε αυτά. Οι εξαρτήσεις των δεδομένων (δηλαδή τα δεδομένα από τα οποία εξαρτάται ένα τμήμα κώδικα) ανακαλύπτονται εξετάζοντας απλά την υλοποίηση των συναρτήσεων. Συγκεκριμένα, όλες οι τυπικές παράμετροι, τοπικές μεταβλητές και προσπελαζόμενες καθολικές μεταβλητές (αν επιτρέπονται στη γλώσσα) αποτελούν τα δεδομένα από τα οποία εξαρτάται μια συνάρτηση. Φαίνεται εύκολα, για παράδειγμα, ότι η συνάρτηση countbylastdigit() εξαρτάται άμεσα από τους δύο πίνακες που λαμβάνει ως παραμέτρους, πέραν των υπολοίπων παραμέτρων και τοπικών μεταβλητών. Ωστόσο, αυτό που είναι προβληματικό στον διαδικασιακό προγραμματισμό είναι το αντίθετο: Ο εντοπισμός των διαδικασιακών εξαρτήσεων ενός δεδομένου (δηλαδή οι συναρτήσεις οι οποίες εξαρτώνται από ένα δεδομένο). Ο λόγος είναι ότι δεν υπάρχει σαφής συσχέτιση μεταξύ δεδομένων και λειτουργικότητας. Έστω, για παράδειγμα, (κατόπιν αλλαγών στις απαιτήσεις), ότι ζητείται η προσθήκη ενός επιπλέον ψηφίου στους αριθμούς φορολογικού μητρώου. Εκτός από την καταχώρηση νέων αριθμών με 10 ψηφία, πρέπει για λόγους ομοιόμορφης αντιμετώπισης όλοι οι υπάρχοντες αριθμοί στις δομές δεδομένων να "αποκτήσουν" ένα επιπρόσθετο ψηφίο στο τέλος, συνήθως το μηδέν. Μια τέτοια διαδικασία μπορεί να μοντελοποιηθεί εύκολα με την προσθήκη μιας νέας συνάρτησης η οποία σαρώνοντας τη δομή δεδομένων που διατηρεί τους υπάρχοντες αριθμούς, θα προσθέτει ως επιπλέον ψηφίο στο τέλος το μηδέν (πολλαπλασιάζοντας κάθε α- ριθμό με το 10). Παρόλο που η προσθήκη της συνάρτησης είναι εύκολη (όπως φαίνεται

7 Ευρετικοί κανόνες 217 στον ακόλουθο κώδικα) δεν είναι εύκολο να εντοπιστούν (ειδικά σε προγράμματα εκατοντάδων μονάδων και χιλιάδων γραμμών κώδικα) ποιες συναρτήσεις επηρεάζονται από μια τέτοια αλλαγή. void addzeros(int A[], int size) { int i; for(i=0; i<size; i++) A[i] = A[i] * 10; Αν τα μέλη της ομάδας ανάπτυξης παραβλέψουν ότι απαιτείται η τροποποίηση και της συνάρτησης checkvalidity() το πρόγραμμα δεν θα λειτουργεί σωστά. Ακόμα όμως και αν γίνουν οι απαραίτητες διορθώσεις σε αυτή τη συνάρτηση (στο κάτω-κάτω αυτή ελέγχει το πλήθος των ψηφίων κάθε αριθμού), το πρόγραμμα θα συνεχίσει να παρουσιάζει προβλήματα. Η παράβλεψη των αλλαγών που απαιτούνται στη δομή δεδομένων count[9] (πλέον οι ομάδες των αριθμών με βάση το τελευταίο ψηφίο είναι 10) και στις συναρτήσεις countbylastdigit() και printnumbers() είναι πολύ εύκολο να συμβεί λόγω της μη τεκμηριωμένης εξάρτησης δεδομένων/συμπεριφοράς. Η απεικόνιση των συναρτήσεων και των μονόδρομων εξαρτήσεων τους από τα δεδομένα παρουσιάζεται στο Σχήμα 7.2.2. checkvalidity() countbylastdigit() numbers... issuenew() count checkvalidity() printnumbers() Σχήμα 7.2.2: Μονόδρομη εξάρτηση συναρτήσεων από δεδομένα σε διαδικασιακό σύστημα Στο αντικειμενοστρεφές μοντέλο, το ανωτέρω πρόβλημα θεωρείται ότι δεν μπορεί καν να δημιουργηθεί, καθώς η συμπεριφορά του συστήματος καθοδηγεί τη διαδικασία αποσύνθεσης τόσο των λειτουργιών όσο και των δεδομένων. Το αποτέλεσμα είναι η σχεδίαση του συστήματος ως μια συλλογή αποκεντρωμένων τμημάτων δεδομένων με σαφώς καθορισμένες δημόσιες διασυνδέσεις. Οι μόνες εξαρτήσεις της λειτουργικότητας από τα δεδο-

218 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ μένα είναι οι λειτουργίες της δημόσιας διασύνδεσης που εξαρτώνται από τα συσχετιζόμενα δεδομένα. Στο Σχήμα 7.2.3 κάθε κλάση περιλαμβάνει τα δικά της δεδομένα και επιτρέπει την πρόσβαση σε αυτά μέσω των δημόσια διαθέσιμων μεθόδων. Αλλαγές στην εσωτερική υλοποίηση κάθε κλάσης θα πρέπει να μην επηρεάζουν τη διασύνδεση της κλάσης και αν αυτό απαιτείται, είναι ευθύνη του σχεδιαστή της κλάσης να ενημερώσει τους σχεδιαστές των συσχετιζόμενων κλάσεων. Θεωρητικά, η εξάρτηση των μεθόδων μιας κλάσης από τις ιδιότητές της είναι δεδομένη και η εξάρτηση μεταξύ κλάσεων βασίζεται στη χρήση των διασυνδέσεων μέσω των συσχετίσεων που απεικονίζονται σε ένα διάγραμμα κλάσεων. Ταξινόμηση -count +countbylastdigit() +printnumbers() ΕισαγωγήΔεδομένων -numbers +issuenew() +checkvalidity() Σχήμα 7.2.3: Τεκμηριωμένη εξάρτηση μεταξύ λειτουργιών και ιδιοτήτων σε αντικειμενοστρεφές σύστημα Ωστόσο, όπως πολύ σωστά σημειώνει ο Riel, η εικόνα σε ένα διαδικασιακό σύστημα δεν είναι πάντοτε τόσο κακή ούτε βέβαια η εικόνα ενός αντικειμενοστρεφούς συστήματος τόσο ρόδινη. Ένας έμπειρος προγραμματιστής θα διαμαρτυρηθεί σχετικά με το προηγούμενο διαδικασιακό παράδειγμα λέγοντας ότι στην πράξη κάθε δομή δεδομένων τοποθετείται σε ξεχωριστό αρχείο όπου επίσης περιλαμβάνονται οι συναρτήσεις που εξαρτώνται από τη συγκεκριμένη δομή. Κατά συνέπεια, το σύστημα είναι ευκολότερα συντηρήσιμο. Το πρόβλημα όμως είναι ότι στην ουσία χρησιμοποιείται μια σύμβαση, σύμφωνα με την οποία τα δεδομένα και οι συναρτήσεις που λειτουργούν επί αυτών ομαδοποιούνται σε μία λογική οντότητα, ότι ακριβώς κάνει και μια κλάση στον αντικειμενοστρεφή προγραμματισμό. Εδώ δεν υπάρχουν κλάσεις, και για αυτό χρησιμοποιείται ένας μηχανισμός χαμηλοτέρου επιπέδου. Ωστόσο, ένας προγραμματιστής θα πρέπει αφενός να κατανοήσει την αναγκαιότητα αυτής της σύμβασης και αφετέρου να την τηρεί πιστά πάντοτε. Το τελευταίο είναι και το δυσκολότερο. Φαίνεται καθαρά ότι ένα καλό διαδικασιακό σύστημα υιοθετεί τις αρχές ενός αντικειμενοστρεφούς συστήματος. Τι μπορεί να "στραβώσει" σε ένα αντικειμενοστρεφές σχέδιο ώστε αυτό να μην εξελιχθεί καλά; Υπάρχουν δύο κύρια είδη τέτοιων προβλημάτων: Το πρώτο σχετίζεται με τη μη ομοιόμορφη κατανομή της λειτουργικότητας του συστήματος και αναφέρεται ως το πρόβλημα της "θεϊκής κλάσης" (God class). Το δεύτερο είναι η δημιουργία υπερβολικού αριθμού κλάσεων για το μέγεθος του υπό εξέταση προβλήματος και είναι γνωστό ως το πρόβλημα του "πολλαπλασιασμού των κλάσεων" (proliferation of classes). Το πρόβλημα των θεϊκών κλάσεων εμφανίζεται σε δύο μορφές, τη μορφή συμπεριφοράς και τη μορφή δεδομένων.

7 Ευρετικοί κανόνες 219 7.3 Πρόβλημα "θεϊκής" κλάσης Μορφή Συμπεριφοράς Στον διαδικασιακό τρόπο ανάπτυξης λογισμικού, πολύ συχνά προκύπτει η ανάγκη ύπαρξης ενός κεντρικού μηχανισμού ελέγχου, ο οποίος καλεί τις υπόλοιπες συναρτήσεις μονάδες του συστήματος. Ένα σύνηθες σφάλμα κατά τη μετάβαση στο αντικειμενοστρεφές μοντέλο είναι η προσπάθεια απεικόνισης της κεντρικής αυτής συνάρτησης ελέγχου σε μια αντίστοιχη κλάση. Το αποτέλεσμα είναι μια υπερβολικά ανεπτυγμένη κλάση που αναλαμβάνει τις περισσότερες αρμοδιότητες του συστήματος, αφήνοντας μικρό μέρος λεπτομερειακών εργασιών στις υπόλοιπες στοιχειώδεις κλάσεις. Στο κεφάλαιο για τις αρχές σχεδίασης (ενότητα 5.4) παρουσιάστηκε το παράδειγμα ενός φούρνου μικροκυμάτων όπου κάθε ξεχωριστή οντότητα αναπαραστάθηκε ως μία κλάση με αποτέλεσμα να υπάρχει πολύ υψηλή σύζευξη μεταξύ των κλάσεων. Ωστόσο, η πρώτη απόπειρα (των άπειρων συνήθως προγραμματιστών) για τη δημιουργία ενός αντικειμενοστρεφούς μοντέλου ενός φούρνου μικροκυμάτων καταλήγει συνήθως σε ένα σύστημα όπως αυτό που απεικονίζεται στο Σχήμα 7.3.1. ΠλήκτροΑκύρωσης ΣωλήναςΜικροκυμάτων Κουδούνι Φούρνος Χρονόμετρο Πόρτα ΠλήκτροΛειτουργίας Λαμπτήρας Σχήμα 7.3.1: Κεντρικός Μηχανισμός Ελέγχου σε Αντικειμενοστρεφές Σύστημα Είναι προφανές ότι η κλάση Φούρνος αναλαμβάνει το ρόλο ενός κεντρικού συντονιστή ο οποίος λαμβάνει και αποστέλλει μηνύματα σε όλες τις υπόλοιπες κλάσεις. Η υπερσυγκέτρωση αρμοδιοτήτων στην κλάση Φούρνος είναι εμφανής και στο διάγραμμα ακολουθίας που περιγράφει ένα συγκεκριμένο σενάριο χρήσης του συστήματος (Σχήμα 7.3.2).

220 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ :ΠλήκτροΛειτουργίας :Πόρτα :Φούρνος :Λαμπτήρας :ΣωλήναςΜικροκυμάτων :Χρονόμετρο :Κουδούνι Χρήστης Πάτημα() έναρξηλειτουργίας() είναικλειστή() προσθήκη30δευτερολέπτων() ενεργοποίηση() ενεργοποίηση() αντίστροφημέτρηση() τέλοςχρόνου() απενεργοποίηση() απενεργοποίηση() Ειδοποίηση() Σχήμα 7.3.2: Εντοπισμός θεϊκής κλάσης σε διάγραμμα ακολουθίας Παρόλο που η σύζευξη μεταξύ των κλάσεων είναι πλέον χαμηλή, το μεγαλύτερο μέρος της σημασιολογίας του συστήματος μοντελοποιείται σε μία κλάση. Η λύση αυτή δεν υπερέχει, όσον αφορά τη διαχείριση της πολυπλοκότητας, σε σχέση με τον διαδικασιακό προγραμματισμό. Ένας αριθμός ευρετικών κανόνων μπορούν να βοηθήσουν για την αποφυγή δημιουργίας τέτοιων κλάσεων (Riel 1996): Ευρετικός Κανόνας 2 Η λειτουργικότητα του συστήματος θα πρέπει να κατανέμεται οριζοντίως όσο το δυνατόν πιο ομοιόμορφα, δηλαδή, οι κλάσεις υψηλού επιπέδου σε ένα σχέδιο θα πρέπει να μοιράζονται ομοιόμορφα την εργασία που πρέπει να πραγματοποιηθεί. Ευρετικός Κανόνας 3 Μή δημιουργείτε θεϊκές κλάσεις/αντικείμενα στο σύστημά σας. Να είστε ιδιαίτερα καχύποπτοι για κλάσεις των οποίων το όνομα περιλαμβάνει τους όρους Driver, Manager, System, Subsystem ή παρόμοιες έννοιες. Ευρετικός Κανόνας 4 Να είστε ιδιαίτερα προσεκτικοί με τις κλάσεις οι οποίες έχουν πολλές μεθόδους πρόσβασης στη δημόσια διασύνδεσή τους. Η ύπαρξη πολλών μεθόδων πρόσβασης υποδηλώνει ότι συσχετιζόμενα δεδομένα και συμπεριφορά δεν διατηρούνται μαζί σε ένα σημείο.

7 Ευρετικοί κανόνες 221 Ευρετικός Κανόνας 5 Να είστε ιδιαίτερα προσεκτικοί με τις κλάσεις που εμφανίζουν υπερβολική συμπεριφορά η οποία δεν σχετίζεται με επικοινωνία με άλλα αντικείμενα (noncommunicating behavior), δηλαδή μεθόδους οι οποίες δρουν σε ένα συγκεκριμένο υποσύνολο των μελών δεδομένων της κλάσης. Οι θεϊκές κλάσεις παρουσιάζουν συχνά υπερβολική συμπεριφορά τέτοιου τύπου. Η ύπαρξη θεϊκών κλάσεων σε ένα σύστημα, συνεπάγεται την εμφάνιση πολλών μεθόδων get() και set() στις υπόλοιπες κλάσεις. Η αναγκαιότητα τέτοιων μεθόδων οφείλεται σε δύο λόγους: Είτε η κλάση που χρησιμοποιεί τις get() και set() υλοποιεί μια στρατηγική (policy) μεταξύ δύο ή περισσοτέρων κλάσεων, είτε βρίσκεται στα όρια μεταξύ ε- νός αντικειμενοστρεφούς μοντέλου και της γραφικής διασύνδεσης χρήστη. Θεωρούμε ένα απλουστευτικό μοντέλο ενός παιχνιδιού αγώνων αυτοκινήτου όπου κάθε παίκτης οδηγός (Driver) λαμβάνει μέρος σε ένα τουρνουά αγώνων (Tournament). Στα πλαίσια ενός τουρνουά ο κάθε οδηγός εγγράφεται σε διάφορους αγώνες (Races). Ω- στόσο, για την εγγραφή σε έναν αγώνα απαιτείται ο κατάλληλος τύπος άδειας οδήγησης. Η κάθε μία από τις ανωτέρω τρεις οντότητες μοντελοποιείται ως μία ξεχωριστή κλάση. Η κλάση Driver περιλαμβάνει στατική πληροφορία (το όνομα του οδηγού) και δυναμική πληροφορία (ο τύπος της άδειας που κατέχει), καθώς αυτή μπορεί να αλλάζει κατά τη διάρκεια του παιχνιδιού. Η κλάση Race περιλαμβάνει μία δομή δεδομένων (διάνυσμα) ό- που καταχωρούνται οι τύποι αδειών οδήγησης που επιτρέπεται να λαμβάνουν μέρος καθώς και μία αντίστοιχη μέθοδο ελέγχου αν ένας συγκεκριμένος τύπος άδειας περιλαμβάνεται στις επιτρεπτές. Τέλος, η κλάση Tournament διατηρεί ως ιδιότητες τον μέγιστο επιτρεπόμενο και τον τρέχοντα αριθμό χρηστών και παρέχει μία μέθοδο εγγραφής ενός οδηγού σε έναν αγώνα. Στο Σχήμα 7.3.3 παρουσιάζεται ένα διάγραμμα ακολουθίας όπου απεικονίζονται τα μηνύματα που ανταλλάσσονται μεταξύ των αντικειμένων για την εγγραφή ενός οδηγού σε έναν αγώνα. Σε ένα τουρνουά "δίνονται" (από το χρήστη ή από κάποια άλλη κλάση) ο οδηγός καθώς και ο συγκεκριμένος αγώνας στον οποίο επιθυμεί να εγγραφεί. Πριν από την εγγραφή ενός οδηγού, το αντίστοιχο αντικείμενο της κλάσης τουρνουά θα πρέπει να ελέγξει ότι ο συγκεκριμένος οδηγός έχει άδεια οδήγησης που του επιτρέπει την είσοδο στον συγκεκριμένο αγώνα. Καθώς οι δύο αυτές πληροφορίες (τύπος αδείας και άδειες που επιτρέπονται σε κάποιον αγώνα) βρίσκονται κατανεμημένες σε δύο διαφορετικές κλάσεις, κατ' ελάχιστον, απαιτείται η λήψη της πληροφορίας από τη μία κλάση και η μεταβίβασή της στην άλλη. Στην επίλυση που παρουσιάζεται στο διάγραμμα ακολουθίας, η πληροφορία λαμβάνεται από το αντικείμενο Driver και μεταβιβάζεται στο αντικείμενο Race ώστε να ληφθεί μια απόφαση. Η ύπαρξη μιας μεθόδου get() (getlicensetype()) για τη λήψη της πληροφορίας είναι προφανής.

222 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ Σχήμα 7.3.3: Διάγραμμα ακολουθίας για παιχνίδι αγώνων αυτοκινήτου Ο κώδικας C++ για ένα τέτοιο σύστημα δίνεται στη συνέχεια (ως ένα αρχείο για λόγους απλότητας): #include <iostream> #include <string> #include <vector> using namespace std; //Κλάση Οδηγός -------------------------------------------------- class Driver { public: Driver(string text); void setlicensetype(string text); string getlicensetype(); string getname() { return name; private: string name; string licensetype; ; Driver::Driver(string text) { name = text; void Driver::setLicenseType(string text) { licensetype = text; string Driver::getLicenseType() { return licensetype;

7 Ευρετικοί κανόνες 223 //Κλάση Αγώνας -------------------------------------------------- class Race { public: Race(string text); void addlicense(string text); bool checkifallowed(string text); string getname() { return name; private: string name; vector<string> allowedlicenses; //επιτρεπτές άδειες ; Race::Race(string text) { name = text; //προσθήκη τύπου άδειας στη λίστα των επιτρεπτών void Race::addLicense(string text) { allowedlicenses.push back(text); //έλεγχος αν ένας τύπος άδειας είναι επιτρεπτός bool Race::checkIfAllowed(string text) { vector<string>::const_iterator p; for(p= allowedlicenses.begin(); p!= allowedlicenses.end(); p++) if( (*p) == text) return true; return false; //Κλάση Τουρνουά ------------------------------------------------ class Tournament { public: Tournament(int maxno); void registeruser(race& arace, Driver& adriver); private: static int maxnoofdrivers; static int drivers; ; int Tournament::maxNoOfDrivers = 0; int Tournament::drivers = 0; Tournament::Tournament(int maxno) { maxnoofdrivers = maxno;

224 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ //εγγραφή οδηγού σε αγώνα void Tournament::registerUser(Race& arace, Driver& adriver) { if(drivers < maxnoofdrivers) { if(arace.checkifallowed(adriver.getlicensetype())) { cout << "Driver " << adriver.getname() << " added to race " << arace.getname() << endl; drivers++; else cout << "Driver is not allowed to enter this race" << endl; else cout << "Tournament is full!" << endl; //Πρόγραμμα οδήγησης -------------------------------------------- int main() { Driver D1("John"); D1.setLicenseType("Β1"); Race R1("Monte Carlo"); R1.addLicense("A1"); R1.addLicense("B1"); Tournament T1(2); T1.registerUser(R1, D1); return 0; Ωστόσο, σύμφωνα με μια ευρέως γνωστή μεθοδολογία αντικειμενοστρεφούς ανάλυσης και σχεδίασης (Jacobson's Objectory, πλέον γνωστή ως Rational Unified Process) στην ανωτέρω περίπτωση, παραβιάζεται ένας κανόνας ανάλυσης, σύμφωνα με τον οποίο η στρατηγική δεν θα πρέπει να τοποθετείται μέσα σε κλάσεις του προβλήματος οι οποίες εμπλέκονται στη λήψη της απόφασης. Υποστηρίζεται, ότι κατ' αυτόν τον τρόπο, οι κλάσεις του πεδίου αχρηστεύονται γιατί συνδέονται με το πεδίο του προβλήματος που θέτει τη στρατηγική. Για παράδειγμα, ο βαθμός επαναχρησιμοποίησης της κλάσης Race μειώνεται, καθώς συμπεριλαμβάνει λειτουργικότητα (τη μέθοδο ελέγχου) που αφορά τη στρατηγική της συγκεκριμένης μόνο εφαρμογής. Παρόλο που κάτι τέτοιο σε ένα μικρό παράδειγμα γίνεται δύσκολα κατανοητό, καθίσταται σημαντικό στην περίπτωση συστημάτων λογισμικού μεγάλης κλίμακας.

7 Ευρετικοί κανόνες 225 Σύμφωνα με τη μεθοδολογία, η λύση συνίσταται είτε στο να λάβει η κλάση Tournament και τον τύπο της άδειας του οδηγού και ολόκληρη τη λίστα των επιτρεπτών τύπων άδειας για τον συγκεκριμένο αγώνα και να αποφασίσει η ίδια, είτε να δημιουργηθεί μια ξεχωριστή κλάση ΈλεγκτηςΆδειας (LicenseController) για την εκτέλεση αυτής της λειτουργίας (Σχήμα 7.3.4). Γίνεται όμως εύκολα αντιληπτό ότι η κλάση Tournament στην πρώτη περίπτωση καθώς και η κλάση LicenseController στη δεύτερη, αποτελούν εν δυνάμει θεϊκές κλάσεις που αναλαμβάνουν όλες τις αρμοδιότητες στο σύστημα. Στις δύο αυτές εναλλακτικές σχεδιάσεις, καθώς και σε αυτές που θα προκύπτουν μετά από αλλαγές στις απαιτήσεις και την περαιτέρω επαύξηση των λειτουργιών των θεϊκών κλάσεων, οι υπόλοιπες κλάσεις περιλαμβάνουν μόνο συναρτήσεις get() και set(). Υπενθυμίζεται ότι η ύπαρξη πολλών μεθόδων get() και set() υποδηλώνει συχνά την ύπαρξη θεϊκών κλάσεων. Οι θεϊκές κλάσεις τείνουν να έχουν ολοένα και περισσότερη συμπεριφορά και να παίζουν το ρόλο του ελεγκτή συντονιστή, ενώ οι ελεγχόμενες κλάσεις περιορίζονται στο να διατηρούν τα δεδομένα. Σε μια τέτοια σχεδίαση καταργείται στην ουσία το αντικειμενοστρεφές μοντέλο, διαχωρίζοντας δεδομένα και συμπεριφορά. Το μοναδικό πλεονέκτημα που προκύπτει από τη χρήση τέτοιων κλάσεων ελεγκτών είναι το ότι καθιστούν τις υπόλοιπες κλάσεις περισσότερο επαναχρησιμοποιήσιμες μειώνοντας τη σύζευξη μεταξύ τους. Tournament Race -allowedlicenses +getallowedlicenses() Tournament LicenseController Race -allowedlicenses +getallowedlicenses() +registeruser() +checkifallowed() Driver -licensetype +getlicensetype() +registeruser() +checkifallowed() Driver -licensetype +getlicensetype() (α) (β) Σχήμα 7.3.4: Εναλλακτικές σχεδιάσεις για τον έλεγχο προαπαιτουμένων Υπάρχει μία περίπτωση όπου είναι αναγκαία η ύπαρξη μεθόδων πρόσβασης και αναφέρεται σε αρχιτεκτονικές όπου ένα αντικειμενοστρεφές μοντέλο αλληλεπιδρά με τη γραφική διασύνδεση χρήστη. Ο ευρετικός κανόνας εδώ είναι ότι το μοντέλο θα πρέπει να είναι α- νεξάρτητο από τη γραφική διασύνδεση, παρόλο που η τελευταία θα πρέπει να επιτρέπει την παρακολούθηση και τροποποίηση ορισμένων από τα δεδομένα του μοντέλου. Σε μια τέτοια αρχιτεκτονική, η γραφική διασύνδεση χρειάζεται την ύπαρξη μεθόδων get() και set() στις κλάσεις του μοντέλου. Σε τέτοια συστήματα οι κλάσεις του μοντέλου σπανίως έχουν ενδιαφέρουσα συμπεριφορά. Σε συστήματα διαχείρισης δεδομένων για παράδειγμα, οι κλάσεις επικοινωνούν με ένα σύστημα διαχείρισης βάσης δεδομένων, ανακτούν εγγραφές, τις προωθούν, τις τροποποιούν και επανεγγράφουν στη βάση.

226 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ Ευρετικός Κανόνας 6 Σε εφαρμογές που αποτελούνται από ένα αντικειμενοστρεφές μοντέλο που αλληλεπιδρά με διασύνδεση χρήστη, το μοντέλο θα πρέπει να είναι ανεξάρτητο από τη διασύνδεση. Η διασύνδεση θα πρέπει να εξαρτάται από το μοντέλο. Παρόλο που εδώ αναφέρεται ως ευρετικός κανόνας, η σχεδίαση που προκύπτει από την εφαρμογή του αποτελεί ένα από τα κλασσικότερα πρότυπα αρχιτεκτονικής λογισμικού, το πρότυπο Μοντέλου-Όψης-Ελεγκτή (Model-View-Controller, MVC). Στόχος του προτύπου είναι η μείωση της σύζευξης μεταξύ των κλάσεων ενός συστήματος και των κλάσεων που συνιστούν τη γραφική διασύνδεση χρήστη. Το πρότυπο διαχωρίζει το λειτουργικό τμήμα μιας εφαρμογής (το μοντέλο), από τις δύο απόψεις της διασύνδεσης χρήστη (της όψης και του ελεγκτή). Η όψη αφορά τα συστατικά που αναλαμβάνουν την παρουσίαση των δεδομένων στον χρήστη αλλά και των χειριστηρίων αλληλεπίδρασης με αυτά (πλήκτρα, tick boxes, radio buttons, ράβδους κύλισης κλπ). Ο ελεγκτής περιλαμβάνει τα αντικείμενα που χειρίζονται την αλληλεπίδραση του χρήστη (π.χ. την πληκτρολόγηση κειμένου σε κάποιο πεδίο ή την κίνηση του ποντικιού). Επιπλέον ορίζει τη λογική και χρονική ακολουθία των ενεργειών που μπορούν να γίνουν. Το πλεονέκτημα αυτής της αρχιτεκτονικής (Σχήμα 7.3.5) είναι ότι το μοντέλο παραμένει ανεξάρτητο από τη γραφική δια- παρουσίαση στον χρήση Όψη χειρισμός αλληλεπίδρασης Ελεγκτής ClassA ClassA διαχείριση ClassB ClassB ClassC ClassC κλάσεις συστήματος ενημέρωση ClassA τροποποίηση ClassB Μοντέλο ClassC Σχήμα 7.3.5: Πρότυπο Μοντέλου-Όψης-Ελεγκτή

7 Ευρετικοί κανόνες 227 σύνδεση χρήστη και οποιαδήποτε αλλαγή στη δομή των δεδομένων δεν επηρεάζει την εμφάνιση καθώς και το αντίστροφο. 7.4 Πρόβλημα "θεϊκής" κλάσης Μορφή Δεδομένων Το αντίστροφο σύμπτωμα από αυτό που αναφέρθηκε προηγουμένως, δηλαδή μία υπερβολικά ανεπτυγμένη κλάση που περιλαμβάνει τα περισσότερα δεδομένα του συστήματος και παρέχει μεθόδους πρόσβασης (συναρτήσεις get()) και μεθόδους τροποποίησης (συναρτήσεις set()), υποδηλώνει την ύπαρξη της μορφής δεδομένων μιας θεϊκής κλάσης. Σε αυτή την εκδοχή, οι υπόλοιπες κλάσεις του συστήματος εκτελούν υπολογισμούς επί των δεδομένων της θεϊκής κλάσης. Το σύμπτωμα που γίνεται εύκολα αντιληπτό είναι πάλι ο διαχωρισμός μεταξύ δεδομένων και συμπεριφοράς. Το πρόβλημα της περίπτωσης αυτής εμφανίζεται συχνά κατά τη μετατροπή ενός υπάρχοντος συστήματος λογισμικού (legacy system) σε μια νέα αντικειμενοστρεφή σχεδίαση. Θεωρούμε ένα υπάρχον σύστημα λογισμικού, ανεπτυγμένο σε διαδικασιακή γλώσσα, που διαπραγματεύεται την διαχείριση επιχειρησιακών πόρων (ERP). Στο σύστημα υπάρχει μία μεγάλη κοινή δομή δεδομένων που διατηρεί όλες τις πληροφορίες του συστήματος για τη διεκπεραίωση των απαραίτητων λειτουργιών. Η δομή αυτή πιθανόν να διατηρεί εγγραφές για τους διάφορους υπαλλήλους, το τμήμα στο οποίο ανήκουν, τις αρμοδιότητές τους, τους πελάτες που σχετίζονται με τον κάθε υπάλληλο κ.ο.κ. Στο σύστημα περιλαμβάνεται και ένας αριθμός συναρτήσεων επεξεργασίας των αντίστοιχων δεδομένων και παραγωγής των αντίστοιχων εκτυπωτικών αναφορών (Σχήμα 7.4.1). Υπολογισμός τζίρου ανά τμήμα() Υπολογισμός τζίρου ανά υπάλληλο() Υπολογισμός τζίρου ανά πελάτη() Στατιστικά πωλήσεων() Υπάλληλος1 όνομα τμήμα μισθός bonus αρμοδιότητες πελάτες πωλήσεις Υπάλληλος2 όνομα τμήμα μισθός bonus αρμοδιότητες πελάτες πωλήσεις Υπάλληλος3 όνομα τμήμα μισθός bonus αρμοδιότητες πελάτες πωλήσεις Υπάλληλος4 όνομα τμήμα μισθός bonus αρμοδιότητες πελάτες πωλήσεις... ΥπάλληλοςN όνομα τμήμα μισθός bonus αρμοδιότητες πελάτες πωλήσεις What if σενάρια() Εκτύπωση αρμοδιοτήτων ανά υπάλληλο() Εκτύπωση μισθοδοτικών καταστάσεων() Σχήμα 7.4.1: Υπάρχον σύστημα διαχείρισης επιχειρησιακών πόρων

228 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ Κατά τη μετατροπή του συστήματος χρησιμοποιώντας μια αντικειμενοστρεφή γλώσσα προγραμματισμού, η ομάδα ανάπτυξης αποφασίζει να ενσωματώσει τη δομή δεδομένων σε μια κλάση, προσθέτοντας σε αυτή μεθόδους get() και set() για όλα τα μέλη δεδομένων. Οι συναρτήσεις επεξεργασίας ομαδοποιούνται με βάση τη λειτουργία τους σε ξεχωριστές κλάσεις, οι οποίες ωστόσο δεν έχουν δεδομένα αλλά χρησιμοποιούν τις μεθόδους πρόσβασης και τροποποίησης της κλάσης των δεδομένων (Σχήμα 7.4.2). Παρόλο που ένα τέτοιο σύστημα υπερτερεί από το αρχικό καθώς υπάρχει κατανομή των λειτουργιών σε διάφορες κλάσεις, η σχεδίασή του δεν είναι καλής ποιότητας καθώς αντιβαίνει στην αρχή της ενσωμάτωσης όπου κάθε κλάση είναι υπεύθυνη για τα δεδομένα που σχετίζονται με τις λειτουργίες που παρέχει. Η κλάση Στοιχεία στο σχήμα, συγκεντρώνει όλα τα δεδομένα του συστήματος και κατά συνέπεια πρόκειται για μία θεϊκή κλάση. Σχήμα 7.4.2: Θεϊκή κλάση μορφή δεδομένων Η ορθή σχεδίαση του συστήματος θα είχε προκύψει εάν εξ' αρχής οι οντότητες του σχεδίου είχαν επιλεγεί αφενός με βάση ένα μοντέλο της πραγματικότητας και αφετέρου με βάση την αρχή της ενσωμάτωσης δεδομένων και συμπεριφοράς. Οι κλάσεις που θα προέκυπταν, θα "αντλούσαν" τα δεδομένα για τη διεκπεραίωση των λειτουργιών τους μέσω των συσχετίσεων διαύλων επικοινωνίας με τις υπόλοιπες κλάσεις. Οι συσχετίσεις αυτές θα αναπαριστούσαν επιπλέον και τις πραγματικές σχέσεις μεταξύ των αντίστοιχων εννοιών, ενώ δεν θα υπήρχε η αναγκαιότητα για συσσώρευση όλης της πληροφορίας του συστήματος σε ένα σημείο. Καθώς βέβαια η σχεδίαση δεν πραγματοποιείται πάντοτε εκ νέου, η ομαδοποίηση των συναρτήσεων και των αντίστοιχων δεδομένων σε κλάσεις θα πρέπει να γίνεται με βάση το υποσύνολο των δεδομένων της αρχικής δομής που χρησιμοποιείται κάθε φορά. Η ιδανική σχεδίαση ενός τέτοιου συστήματος παρουσιάζεται στο Σχήμα 7.4.3.

7 Ευρετικοί κανόνες 229 Πελάτης εξυπηρετεί Υπάλληλος εργάζεται Τμήμα * * 1 αναλαμβάνει * Αρμοδιότητα Σχήμα 7.4.3: Εξάλειψη θεϊκής κλάσης κατανομή αρμοδιοτήτων και δεδομένων σε κλάσεις Το πρόβλημα των θεϊκών κλάσεων είναι επίσης γνωστό ως το Blob αντι-πρότυπο (Blob AntiPattern), όπου Blobs είναι οι κλάσεις που περιλαμβάνουν εκατοντάδες μεθόδους και ιδιότητες ενσωματώνοντας όλη τη λογική του συστήματος. Δεν είναι σπάνιο να υπάρχουν συστήματα με τρεις κλάσεις υπερβολικών διαστάσεων, μία για παράδειγμα για τη γραφική διασύνδεση χρήστη, μία για την κλάση που ενσωματώνει τους κανόνες του συστήματος και μία για την προσπέλαση της βάσης δεδομένων, οι οποίες αθροιστικά να συνιστούν το 80% της συνολικής λειτουργικότητας του προγράμματος. Κλάσεις με υπερβολικό αριθμό αρμοδιοτήτων ή δεδομένων είναι δυνατόν να προκύψουν με δύο τρόπους: είτε εξαιτίας της πλήρους έλλειψης αρχιτεκτονικής σχεδίασης είτε λόγω της υλοποίησης των νέων α- παιτήσεων σε κάθε νέα έκδοση του λογισμικού στις ήδη υπάρχουσες κλάσεις αντί της προσθήκης στο σύστημα νέων κλάσεων. Σε κάθε περίπτωση, η συστηματική σχεδίαση του συστήματος (καθορισμός των συστατικών, των αρμοδιοτήτων τους και των μεταξύ τους σχέσεων) μετά από την καταγραφή των απαιτήσεων (και όχι παράλληλα με αυτήν), συμβάλλει στην αποφυγή θεϊκών κλάσεων. 7.5 Πρόβλημα πολλαπλασιασμού των κλάσεων Ένα άλλο πρόβλημα σχεδίασης, γνωστό και ως αντι-πρότυπο Poltergeists (=Πνεύματα, Φαντάσματα), είναι αυτό του υπερβολικού αριθμού κλάσεων. Το συγκεκριμένο πρόβλημα ανήκει στην κατηγορία σφαλμάτων σχεδίασης που προκαλούν τις μεγαλύτερες επιπτώσεις σε ένα σύστημα και είναι από τα δυσκολότερα να διορθωθούν σε περίπτωση που δεν ε- ντοπιστούν εγκαίρως. Ένα από τα θεμελιώδη προτερήματα του αντικειμενοστρεφούς μοντέλου, όπως αναφέρθηκε ήδη αρκετές φορές, είναι η ευκολία πραγματοποίησης αλλαγών: Για την προσθήκη μιας νέας δυνατότητας απαιτείται, σε ένα καλά σχεδιασμένο σύστημα, η τροποποίηση ενός μικρού αριθμού κλάσεων. Υποθέστε ότι έχετε αναπτύξει ένα σύστημα που περιέχει ένα πολύ μεγάλο αριθμό κλάσεων. Το πρόβλημα είναι σε ποια από τις δεκάδες ή εκατοντάδες κλάσεις του λογισμικού θα πρέπει να προστεθούν νέες ή να τροποποιηθούν υπάρχουσες μέθοδοι. Πολύ συχνά, ο σχεδιαστής καταφεύγει στη λύση της προσθήκης επιπλέον κλάσεων στο σύστημα και κατά συνέπεια επιπρόσθετων συσχετίσε-

230 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ ων, αυξάνοντας δραματικά την πολυπλοκότητα του συστήματος και εξαλείφοντας κάθε δυνατότητα κατανόησής του στο μέλλον. O Riel εφιστά την προσοχή σε τρεις ευρετικούς κανόνες καθώς η παραβίασή τους οδηγεί σε εκθετική αύξηση του αριθμού των κλάσεων (το πρόβλημα του χειρισμού των διακριτών ρόλων που αναλαμβάνει μια οντότητα και το οποίο συζητείται σε επόμενη ενότητα σχετίζεται επίσης με τον αριθμό των κλάσεων του συστήματος): Ευρετικός Κανόνας 7 Διαγράψτε τις άσχετες κλάσεις από τη σχεδίαση Μια άσχετη κλάση είναι αυτή της οποίας η συμπεριφορά δεν περιλαμβάνει τίποτε περισσότερο από μεθόδους πρόσβασης και τροποποίησης ιδιοτήτων και ενδεχομένως εκτύπωσης των τιμών τους. Τέτοιες κλάσεις προκύπτουν αν παραβιαστεί η αρχή της ενσωμάτωσης και διαχωριστούν τα δεδομένα από τη συμπεριφορά. Η λύση είναι η διαγραφή αυτών των κλάσεων και η ενσωμάτωση της πληροφορίας (μέλη δεδομένων) που διατηρούν, ως ιδιότητες σε άλλες κλάσεις. Αν στο παράδειγμα των αγώνων αυτοκινήτου που αναφέρθηκε, η κλάση Οδηγός (Driver) δεν έχει καμία άλλη λειτουργία (και δεν αναμένεται να αποκτήσει σε μελλοντικές εκδόσεις του προϊόντος) εκτός από την επιστροφή της τιμής της ιδιότητας "Άδεια Οδήγησης", τότε αυτή η πληροφορία μπορεί κάλλιστα να ενσωματωθεί σε κάποια άλλη κλάση ( π.χ. στην κλάση Tournament). Ευρετικός Κανόνας 8 Διαγράψτε κλάσεις που βρίσκονται εκτός συστήματος Τα όρια ενός αντικειμενοστρεφούς συστήματος καθορίζονται κατά τη διάρκεια της εξαγωγής των περιπτώσεων χρήσης. Ο τελικός πελάτης του λογισμικού είναι αυτός που διατηρεί το δικαίωμα να ορίσει τι θα περιλαμβάνει το σύστημα. Αν για παράδειγμα, αναπτύσσεται ένα σύστημα εγγραφής φοιτητών στα μαθήματα, για ένα πανεπιστήμιο, τότε η έννοια Πανεπιστήμιο βρίσκεται εκτός των ορίων του συστήματος, αν ο πελάτης είναι η Γραμματεία ενός συγκεκριμένου πανεπιστημίου. Αν όμως, ο τελικός πελάτης (π.χ. το υ- πουργείο Παιδείας) ζητήσει ένα σύστημα εγγραφής φοιτητών για όλα τα πανεπιστήμια της χώρας, έτσι ώστε να μπορεί να γίνεται ανταλλαγή διδακτικών μονάδων μεταξύ πανεπιστημίων, τότε η έννοια Πανεπιστήμιο βρίσκεται εντός των ορίων και πιθανότατα να πρέπει να μοντελοποιηθεί ως κλάση. Έννοιες που βρίσκονται εκτός των ορίων του συστήματος δεν πρέπει να μοντελοποιούνται ως κλάσεις. Ο κανόνας αυτός αποτελεί ειδικότερη περίπτωση του προηγούμενου, καθώς μια κλάση εκτός συστήματος δεν έχει συμπεριφορά που να έχει νόημα για το πεδίο του προβλήματος. Μια κλάση Πανεπιστήμιο στην πρώτη εκδοχή του συστήματος δεν θα έχει ουσιαστική συμπεριφορά για τις υπόλοιπες κλάσεις του λογισμικού. Ένα άλλο παράδειγμα κλάσης που βρίσκεται εκτός των ορίων του συστήματος είναι μια αφαίρεση που μοντελοποιεί κάποια πραγματική οντότητα και η οποία αποστέλλει μηνύματα προς το σύστημα αλλά δεν λαμβάνει κανένα μήνυμα από οποιαδήποτε κλάση του

7 Ευρετικοί κανόνες 231 προβλήματος. Το συνηθέστερο σφάλμα που πραγματοποιείται από νεοεισερχόμενους στον αντικειμενοστρεφή τρόπο ανάλυσης και σχεδίασης είναι η μοντελοποίηση των χρηστών του συστήματος ως κλάσεις. Ο χρήστης φαίνεται να ενεργοποιεί κάποια συμπεριφορά αποστέλλοντας μηνύματα σε κλάσεις του συστήματος (π.χ. μέσω μιας γραφικής διασύνδεσης χρήστη). Ωστόσο, μόνο η αποστολή μηνυμάτων δεν συνιστά συμπεριφορά στο πεδίο του προβλήματος. Ο χρήστης δεν λαμβάνει κανένα μήνυμα το οποίο να ενεργοποιεί λειτουργίες, ώστε να είναι χρήσιμη οντότητα και κατά συνέπεια δεν μοντελοποιείται. Η πρώτη εξαίρεση σε αυτή την περίπτωση, είναι εκείνη όπου με την είσοδο κάποιου χρήστη, απαιτείται να γνωρίζει το σύστημα ένα πλήθος στοιχείων για αυτόν. Αν για παράδειγμα κάποιος χρήστης εισέλθει σε ένα σύστημα παραγγελίας προϊόντων μέσω κωδικού, τότε το λογισμικό πιθανόν να πρέπει να μπορεί να προσπελάσει πληροφορίες που αφορούν το συγκεκριμένο χρήστη, όπως το διαθέσιμο υπόλοιπό του, τη διεύθυνσή του, τυχόν προηγούμενες παραγγελίες κ.ο.κ. Στην περίπτωση αυτή, με την είσοδο του χρήστη δημιουργείται ένα αντικείμενο τύπου "Χρήστης" που αποτελεί το "αντίγραφο" του χρήστη (alter-ego) στο σύστημα. Ένα τέτοιο αντικείμενο μπορεί να δημιουργείται ανακτώντας τις σχετικές πληροφορίες από μια βάση δεδομένων ή ένα αρχείο. Το σενάριο αυτό απεικονίζεται σε ένα διάγραμμα ακολουθίας ως εξής: Οντότητα εντός ορίων του συστήματος. (Alter-ego του χρήστη) Μοντελοποιείται ως κλάση. Οντότητα εκτός ορίων του συστήματος. Δεν μοντελοποιείται ως κλάση. Νίκος:Χρήστης Νίκος:Χρήστης είσοδος/ανάκτηση αντικειμένου από βάση Σχήμα 7.5.1: Μοντελοποίηση alter-ego εξωτερικής οντότητας ως κλάση Η δεύτερη εξαίρεση αφορά την περίπτωση όπου κάποιος από τους χρήστες του συστήματος (π.χ. ο χρήστης Β) απαιτεί να γνωρίζει στοιχεία για κάποιον άλλο χρήστη (π.χ. τον χρήστη Α). Τότε ο χρήστης Α θα πρέπει να μοντελοποιηθεί ως κλάση. Αν για παράδειγμα στο σύστημα εγγραφής στα μαθήματα ενός πανεπιστημίου (με υποθετικούς χρήστες του φοιτητές, τους καθηγητές και τη γραμματεία), ένα φοιτητής επιθυμεί να μπορεί να "δεί" τους τίτλους σπουδών ενός καθηγητή, η έννοια Καθηγητής θα πρέπει να αποτελέσει κλάση του συστήματος. Για το λόγο αυτό, στο στάδιο της ανάλυσης ενός συστήματος, οι χρήστες (χαρακτήρες) όλων των περιπτώσεων χρήσης εξετάζονται ως υποψήφιες κλάσεις.

232 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ Ευρετικός Κανόνας 9 Μην μετατρέπετε λειτουργίες σε κλάσεις. Να είστε καχύποπτοι για κλάσεις των οποίων το όνομα είναι ένα ρήμα, ή παράγωγο ρήματος, ειδικά αυτές που παρουσιάζουν μόνο μία συμπεριφορά με νόημα (εξαιρούνται μέθοδοι set, get και print). Κλάσεις που εμφανίζονται σε μια σχεδίαση να έχουν μόνο μία ουσιαστική λειτουργία θα πρέπει να εξεταστούν με προσοχή. Οι λειτουργίες αυτές (μαζί με τυχόν δεδομένα που χρησιμοποιούν) ενδεχομένως να μπορούν να ενσωματωθούν σε κάποια κλάση που απομένει να εντοπιστεί. Εδώ βέβαια εμφανίζεται μια περίπτωση αντίφασης μεταξύ αρχών αντικειμενοστρεφούς σχεδίασης. Για λόγους αύξησης της συνεκτικότητας, έχει ήδη αναφερθεί ότι πρέπει να επιχειρείται η δημιουργία μονάδων λογισμικού όπου όλα τα τμήματα συνεργάζονται για τον ίδιο σκοπό. Μια κλάση με μία μόνο λειτουργία, η οποία εξυπηρετεί προφανώς μόνο έναν σκοπό, φαίνεται να έχει την μέγιστη δυνατή συνεκτικότητα, στοιχείο που είναι επιθυμητό. Ωστόσο, στην αντικειμενοστρεφή σχεδίαση επιχειρείται ο εντοπισμός αφαιρέσεων: εννοιών που ομαδοποιούν κοινή συμπεριφορά και αποτελούν ένα πρότυπο από το οποίο μπορούν να δημιουργηθούν στιγμιότυπα με διαφορετικές ιδιότητες. Στο παράδειγμα του συστήματος εγγραφής σε μαθήματα ενός πανεπιστημίου, αν οι λειτουργίες εγγραφή_σε_μάθημα() και διαγραφή_από_μάθημα() μοντελοποιηθούν (εσφαλμένα) ως κλάσεις, τότε τα αντικείμενα των κλάσεων αυτών θα έχουν κοινές ή καθόλου ιδιότητες. Αν σε κάθε μία από αυτές τις κλάσεις περιλαμβάνονται τα στοιχεία του φοιτητή, τότε θα υπάρχει περιττή πολυπλοκότητα, καθώς θα επαναλαμβάνεται η ίδια πληροφορία στο σύστημα. Η έννοια Φοιτητής είναι μια αφαίρεση που πρέπει να μοντελοποιηθεί ως μια κλάση, η οποία περιλαμβάνει τις κοινές λειτουργίες (εγγραφή_σε_μάθημα() και διαγραφή_από_μάθημα()), ενώ κάθε φοιτητής έχει διακριτές ιδιότητες. Η κλάση παραμένει συνεκτική καθώς όλες οι λειτουργίες αφορούν την ίδια αφαίρεση. 7.6 Ο Νόμος της Δήμητρας O Νόμος της Δήμητρας (Law of Demeter LoD) που προτάθηκε από τους Karl Lieberherr και Ian Holland το 1987, αποτελεί ένα γενικό κανόνα για την ανάπτυξη λογισμικού και καθορίζει τον τρόπο με τον οποίο πρέπει να αναπτύσσονται οι μέθοδοι των κλάσεων ενός αντικειμενοστρεφούς συστήματος. Το όνομα προέρχεται από ένα έργο λογισμικού με τίτλο "The Demeter Project" στη διάρκεια του οποίου ανακαλύφθηκε ο συγκεκριμένος ευρετικός κανόνας. H γενική διατύπωσή του είναι: Ευρετικός Κανόνας 10 Κάθε μονάδα (λογισμικού) θα πρέπει να έχει περιορισμένη γνώση των υπολοίπων μονάδων. Θα πρέπει να γνωρίζει μόνο για τις μονάδες που σχετίζονται "στενά" με αυτήν. Στα πλαίσια του συγκεκριμένου ευρετικού κανόνα, ως μονάδα λογισμικού θεωρείται κάθε μέθοδος f μιας κλάσης C. Η μέθοδος θα πρέπει να καλεί μόνο: άλλες μεθόδους της κλάσης C

7 Ευρετικοί κανόνες 233 μεθόδους κλάσεων που αποτελούν παραμέτρους της f μεθόδους κλάσεων των οποίων αντικείμενα δημιουργούνται στην f μεθόδους κλάσεων που αποτελούν επιστρεφόμενους τύπους μεθόδων της C μεθόδους κλάσεων που είναι μέλη δεδομένων της C Στην πράξη ο νόμος της Δήμητρας έμεινε γνωστός ως "Να μιλάτε μόνο σε φίλους σας. Μη μιλάτε σε ξένους" και αποτελεί ειδική περίπτωση μιας αρχής που ήδη αναφέρθηκε, σύμφωνα με την οποία πρέπει πάντοτε να επιδιώκεται η μικρότερη δυνατή σύζευξη μεταξύ μονάδων λογισμικού. Αυτό που επιτυγχάνεται με την τήρηση του νόμου της Δήμητρας είναι η μείωση των εξαρτήσεων μεταξύ κλάσεων και κατά συνέπεια η ευκολότερη συντήρηση και κατανόηση ενός συστήματος. Για την καλύτερη κατανόηση του ευρετικού αυτού κανόνα θα παρουσιαστεί στη συνέχεια ένα διάσημο πλέον παράδειγμα, του David Bock (Bock 2005), με τίτλο "Το παιδί για τις εφημερίδες, το πορτοφόλι και ο Νόμος της Δήμητρας" (The Paperboy, the Wallet and the Law of Demeter). Το παράδειγμα αν και είναι αρκετά απλουστευτικό, αποκαλύπτει τα προβλήματα που μπορούν να προκύψουν από την παραβίαση του νόμου. Θεωρούμε ένα υποθετικό παιδί που κάνει διανομή εφημερίδων και το οποίο πληρώνεται από κάθε πελάτη στον οποίο παραδίδει μια εφημερίδα. Οι κλάσεις του συστήματος μας είναι οι εξής: Μία κλάση Πελάτης (Customer) με ιδιότητες το επώνυμο και το όνομα του καθώς και μία ιδιότητα Πορτοφόλι (Wallet), που αποτελεί αντικείμενο μιας ξεχωριστής κλάσης στο σύστημα. Ο κατασκευαστής της κλάσης θέτει τιμές στις ιδιότητες και δημιουργεί δυναμικά ένα αντικείμενο τύπου Πορτοφόλι προσθέτοντας ένα αρχικό ποσό. Ο κώδικας της κλάσης Customer σε Java είναι ο εξής: public class Customer { private String firstname; private String lastname; private Wallet mywallet; public Customer(String first, String last) { firstname = first; lastname = last; mywallet = new Wallet(); mywallet.addmoney(100); public String getfirstname() { return firstname; public String getlastname() { return lastname;

234 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ public Wallet getwallet() { return mywallet; Η κλάση Πορτοφόλι έχει ως ιδιότητα το ποσό που υπάρχει και κατάλληλες μεθόδους για την προσθήκη ή αφαίρεση ενός ποσού: public class Wallet { private float value; public Wallet() { value = 0; public float gettotalmoney() { return value; public void addmoney(float amount) { value += amount; public void subtractmoney(float amount) { value -= amount; Ένα πιο σύνθετο πορτοφόλι θα μπορούσε να περιέχει μια δομή δεδομένων με αναφορές προς πιστωτικές κάρτες, δίπλωμα οδήγησης, κάρτες μέλους σε συλλόγους κλπ. Τέλος, η κλάση "Παιδί Εφημερίδων" περιλαμβάνει μία μέθοδο πληρωμής με παράμετρο τον πελάτη στον οποίο παραδίδεται η εφημερίδα. public class PaperBoy { private String name; private float payment = 2; public PaperBoy(String text) { name = text; public void sellpaper(customer thecustomer) { Wallet customerswallet = thecustomer.getwallet(); if (customerswallet.gettotalmoney() > payment) customerswallet.subtractmoney(payment); else System.out.println("You cannot buy the paper!");

7 Ευρετικοί κανόνες 235 public static void main(string[] args) { Customer C1 = new Customer( "Robert", "Jones"); PaperBoy P1 = new PaperBoy("Bob"); P1.sellPaper(C1); System.out.println("One paper sold. Paperboy now has 2 more Euros"); Η υλοποίηση της μεθόδου sellpaper() στην κλάση PaperBoy ισοδυναμεί με το να πάρει το παιδί για τις εφημερίδες το πορτοφόλι του πελάτη (!!), να ελέγξει για τη διαθεσιμότητα του απαραίτητου ποσού πληρωμής, και να εισπράξει μόνο του το ποσό χρεώνοντας το πορτοφόλι του πελάτη. Ποιο είναι το πρόβλημα στην ανωτέρω σχεδίαση; Προφανώς, στην πραγματικότητα, ένας συνετός άνθρωπος δεν θα έδινε το πορτοφόλι του, εμπιστευόμενος το παιδί ότι θα λάβει μόνο αυτά που πρέπει να εισπράξει. Στην περίπτωση ενός πιο σύνθετου πορτοφολιού, το παιδί για τις εφημερίδες θα είχε πρόσβαση και στις πιστωτικές κάρτες, διπλώματα κλπ. Με άλλα λόγια, στο αντικείμενο PaperBoy αποκαλύπτεται πολύ περισσότερη πληροφορία από ότι χρειάζεται. Υπό όρους αντικειμενοστρεφούς σχεδίασης, αυτό σημαίνει ότι η κλάση PaperBoy "γνωρίζει" ότι ένας πελάτης έχει ένα πορτοφόλι και μπορεί να το διαχειριστεί. Όταν μεταγλωττίζεται η κλάση PaperBoy απαιτείται η ύπαρξη της κλάσης Customer αλλά και της κλάσης Wallet. Οι τρεις αυτές κλάσεις έχουν πλέον ισχυρή σύζευξη. Αν τροποποιηθεί η κλάση Wallet (για παράδειγμα στην περίπτωση ενός πιο σύνθετου πορτοφολιού στο μέλλον, η μέθοδος subtractmoney() μπορεί να λαμβάνει μία παράμετρο που θα καθορίζει αν η πληρωμή θα γίνει από τα μετρητά ή την πιστωτική κάρτα), τότε θα πρέπει να τροποποιηθεί και η κλάση Customer (που είναι φυσικό) αλλά και η κλάση PaperBoy. Υπάρχει ένα ακόμη κλασσικό πρόβλημα που μπορεί να δημιουργηθεί. Έστω ότι ο πελάτης υπέστη μια απώλεια του πορτοφολιού του (λόγω κλοπής!). Κάποιος σε μια ομάδα ανάπτυξης λογισμικού αποφάσισε ότι ένας καλός τρόπος μοντελοποίησης αυτής της κατάστασης είναι να θέσει την τιμή της αναφοράς mywallet σε null, προσθέτοντας μία μέθοδο της μορφής: public void setwallet(wallet thewallet) { mywallet = thewallet; στην κλάση Customer, και καλώντας την από τη main ως εξής: C1.setWallet(null);

236 ΑΝΤΙΚΕΙΜΕΝΟΣΤΡΕΦΗΣ ΣΧΕΔΙΑΣΗ Παρόλο που μια τέτοια σκέψη μπορεί να φαίνεται λογική αντιμετώπιση (καθώς ο κώδικας δεν υπαγορεύει μία συγκεκριμένη τιμή για την αναφορά mywallet), θα προκληθεί σφάλμα (εξαίρεση) κατά τη διάρκεια της εκτέλεσης, καθώς στη μέθοδο sell- Paper() καλείται μια μέθοδος μέσω μηδενικής αναφοράς (NullPointerException). Η αποφυγή ενός τέτοιου προβλήματος ελέγχοντας για την ύπαρξη μηδενικής αναφοράς στην κλάση PaperBoy θα ήταν αφύσικη και θα περιέπλεκε τον κώδικα. Η διατύπωση ενός τέτοιου ελέγχου σε φυσική γλώσσα θα ήταν: "Αν ο πελάτης έχει πορτοφόλι, έλεγξε το ποσό που έχει μέσα, αν επαρκεί, πάρε τα χρήματα" (!!). Η ορθή σχεδίαση στο συγκεκριμένο παράδειγμα επιτυγχάνεται με την εφαρμογή του Νόμου της Δήμητρας, καθώς ένα παιδί για εφημερίδες δεν χρειάζεται να γνωρίζει ότι ένας πελάτης "έχει" ένα πορτοφόλι. Η κλάση Customer δεν θα έχει πλέον μια μέθοδο getwallet() αλλά στη θέση της θα υπάρχει μία μέθοδος επιστροφής του ποσού που ζητείται: public class Customer {... public float getpayment(float amount) { if(mywallet!= null) { if(mywallet.gettotalmoney() > amount) { mywallet.subtractmoney(amount); return amount; return 0; Ο κώδικας της μεθόδου sellpaper() στην κλάση PaperBoy δεν γνωρίζει την ύπαρξη του πορτοφολιού αλλά συναλλάσσεται με τον πελάτη καλώντας τη νέα μέθοδο getpayment(): public class PaperBoy {... public void sellpaper(customer thecustomer) { float moneyreceived = thecustomer.getpayment(payment); if (moneyreceived >= payment) System.out.println("Thank you very much!"); else System.out.println("You cannot buy the paper!");