Στην πληροφορική αντικειμενοστρεφή προγραμματισμό (object-oriented programming)[1][2] ή ΑΠ, ονομάζουμε ένα προγραμματιστικό υπόδειγμα το οποίο εμφανίστηκε στα τέλη της δεκαετίας του 1970 και καθιερώθηκε κατά τη δεκαετία του 1990, αντικαθιστώντας σε μεγάλο βαθμό το παραδοσιακό υπόδειγμα του δομημένου προγραμματισμού. Πρόκειται για μία μεθοδολογία ανάπτυξης προγραμμάτων, υποστηριζόμενη από κατάλληλες γλώσσες προγραμματισμού, όπου ο χειρισμός σχετιζόμενων δεδομένων και των διαδικασιών που επενεργούν σε αυτά γίνεται από κοινού, μέσω μίας δομής δεδομένων που τα περιβάλλει ως αυτόνομη οντότητα με ταυτότητα και δικά της χαρακτηριστικά. Αυτή η δομή δεδομένων καλείται αντικείμενο και αποτελεί πραγματικό στιγμιότυπο στη μνήμη ενός σύνθετου, και πιθανώς οριζόμενου από τον χρήστη, τύπου δεδομένων που ονομάζεται κλάση (class). Η κλάση προδιαγράφει τόσο δεδομένα όσο και τις διαδικασίες οι οποίες επιδρουζν επάνω τους· αυτή υπήρξε η πρωταρχική καινοτομία του ΑΠ.
Έτσι μπορεί να οριστεί μία προδιαγραφή δομής αποθήκευσης (π.χ. μία κλάση «τηλεόραση») η οποία να περιέχει τόσο ιδιότητες (π.χ. μία μεταβλητή (variable) «τρέχον κανάλι») όσο και πράξεις ή χειρισμούς επί αυτών των ιδιοτήτων (π.χ. μία διαδικασία (method/function) «άνοιγμα της τηλεόρασης»). Στο εν λόγω παράδειγμα κάθε υλική τηλεόραση (κάθε αντικείμενο αποθηκευμένο πραγματικά στη μνήμη) αναπαρίσταται ως ξεχωριστό, «φυσικό» στιγμιότυπο αυτής της πρότυπης, ιδεατής κλάσης. Επομένως μόνο τα αντικείμενα καταλαμβάνουν χώρο στη μνήμη του υπολογιστή ενώ οι κλάσεις αποτελούν απλώς «καλούπια». Οι αιτίες που ώθησαν στην ανάπτυξη του ΑΠ ήταν οι ίδιες με αυτές που οδήγησαν στην ανάπτυξη του δομημένου προγραμματισμού (ευκολία συντήρησης, οργάνωσης, χειρισμού και επαναχρησιμοποίησης κώδικα μεγάλων και πολύπλοκων εφαρμογών), όμως τελικώς η αντικειμενοστρέφεια επικράτησε καθώς μπορούσε να ανταπεξέλθει σε προγράμματα πολύ μεγαλύτερου όγκου και πολυπλοκότητας.
Οι περισσότερες αντικειμενοστρεφείς έννοιες εμφανίστηκαν αρχικά στη γλώσσα προγραμματισμού Simula 67, η οποία ήταν προσανατολισμένη στην εκτέλεση προσομοιώσεων του πραγματικού κόσμου. Οι ιδέες της Simula 67 επηρέασαν κατά τη δεκαετία του '70 την ανάπτυξη της Smalltalk, της γλώσσας που εισήγαγε τον όρο αντικειμενοστρεφής προγραμματισμός. Η Smalltalk αναπτύχθηκε από τον Άλαν Κέι (Alan Key) της εταιρείας Xerox στο πλαίσιο μίας εργασίας με στόχο τη δημιουργία ενός χρήσιμου, αλλά και εύχρηστου, προσωπικού υπολογιστή. Όταν η τελική έκδοση της Smalltalk έγινε διαθέσιμη το 1980 η έρευνα για την αντικατάσταση του δομημένου προγραμματισμού με ένα πιο σύγχρονο υπόδειγμα ήταν ήδη εν εξελίξει. Στη γλώσσα αυτή όλοι οι τύποι δεδομένων ήταν κλάσεις (δεν υπήρχαν δηλαδή πια παραδοσιακές δομές δεδομένων παρά μόνο αντικείμενα).
Την ίδια περίπου εποχή, και επίσης με επιρροές από τη Simula, ολοκληρωνόταν η ανάπτυξη της C++ ως μίας ισχυρής επέκτασης της δημοφιλούς γλώσσας προγραμματισμού C στην οποία είχαν "μεταμοσχευθεί" αντικειμενοστρεφή χαρακτηριστικά. Η επιρροή της C++ καθ' όλη της δεκαετία του '80 ήταν καταλυτική με αποτέλεσμα τη σταδιακή κυκλοφορία αντικειμενοστρεφών εκδόσεων πολλών γνωστών διαδικαστικών γλωσσών προγραμματισμού. Κατά το πρώτο ήμισυ της δεκαετίας του '90 η βαθμιαία καθιέρωση στους μικροϋπολογιστές των γραφικών διασυνδέσεων χρήστη (GUI), για την ανάπτυξη των οποίων ο ΑΠ φαινόταν ιδιαιτέρως κατάλληλος, και η επίδραση της C++ οδήγησαν στην επικράτηση της αντικειμενοστρέφειας ως βασικού προγραμματιστικού υποδείγματος.
Το 1995 η εμφάνιση της Java, μίας ιδιαίτερα επιτυχημένης, πλήρως αντικειμενοστρεφούς γλώσσας που έμοιαζε συντακτικώς με τη C/C++ και προσέφερε πρωτοποριακές για την εποχή δυνατότητες, έδωσε νέα ώθηση στον ΑΠ. Παράλληλα εμφανίστηκαν ποικίλες άτυπες βελτιώσεις στο βασικό προγραμματιστικό υπόδειγμα, όπως οι αντικειμενοστρεφείς γλώσσες μοντελοποίησης λογισμικού, τα σχεδιαστικά πρότυπα κ.λπ. Το 2001 η Microsoft εστίασε την προσοχή της στην πλατφόρμα .NET, μία ανταγωνιστική της Java πλατφόρμα ανάπτυξης και εκτέλεσης λογισμικού, η οποία ήταν εξ ολοκλήρου προσανατολισμένη στην αντικειμενοστρέφεια.
Κεντρική ιδέα στον αντικειμενοστρεφή προγραμματισμό είναι η κλάση (class), μία αυτοτελής και αφαιρετική αναπαράσταση κάποιας κατηγορίας αντικειμένων, είτε φυσικών αντικειμένων του πραγματικού κόσμου είτε νοητών, εννοιολογικών αντικειμένων, σε ένα περιβάλλον προγραμματισμού. Πρακτικώς είναι ένας τύπος δεδομένων, ή αλλιώς το προσχέδιο μιας δομής δεδομένων με δικά της περιεχόμενα, τόσο μεταβλητές όσο και διαδικασίες. Τα περιεχόμενα αυτά δηλώνονται είτε ως δημόσια (public) είτε ως ιδιωτικά (private), με τα ιδιωτικά να μην είναι προσπελάσιμα από κώδικα εκτός της κλάσης. Οι διαδικασίες των κλάσεων συνήθως καλούνται μέθοδοι (methods) και οι μεταβλητές τους γνωρίσματα (attributes) ή πεδία (fields). Μία κλάση πρέπει ιδανικά να είναι εννοιολογικά αυτοτελής, να περιέχει δηλαδή μόνο πεδία τα οποία περιγράφουν μία κατηγορία αντικειμένων και δημόσιες μεθόδους οι οποίες επενεργούν σε αυτά όταν καλούνται από το εξωτερικό πρόγραμμα, χωρίς να εξαρτώνται από άλλα δεδομένα ή κώδικα εκτός της κλάσης, και επαναχρησιμοποιήσιμη, να αποτελεί δηλαδή μαύρο κουτί δυνάμενο να λειτουργήσει χωρίς τροποποιήσεις ως τμήμα διαφορετικών προγραμμάτων.
Αντικείμενο (object) είναι το στιγμιότυπο μίας κλάσης, δηλαδή αυτή καθαυτή η δομή δεδομένων (με αποκλειστικά δεσμευμένο χώρο στη μνήμη) βασισμένη στο «καλούπι» που προσφέρει η κλάση. Παραδείγματος χάριν, σε μία αντικειμενοστρεφή γλώσσα προγραμματισμού θα μπορούσαμε να ορίσουμε κάποια κλάση ονόματι BankAccount, η οποία αναπαριστά έναν τραπεζικό λογαριασμό, και να δηλώσουμε ένα αντικείμενο της με όνομα MyAccount. Το αντικείμενο αυτό θα έχει δεσμεύσει χώρο στη μνήμη με βάση τις μεταβλητές και τις μεθόδους που περιγράψαμε όταν δηλώσαμε την κλάση. Έτσι, στο αντικείμενο θα μπορούσε να περιέχεται ένα γνώρισμα Balance (=υπόλοιπο) και μία μέθοδος GetBalance (=επίστρεψε το υπόλοιπο). Ακολούθως, θα μπορούσαμε να δημιουργήσουμε ακόμα ένα ή περισσότερα αντικείμενα της ίδιας κλάσης, τα οποία θα είναι διαφορετικές δομές δεδομένων (διαφορετικοί τραπεζικοί λογαριασμοί στο παράδειγμα). Τα αντικείμενα μιας κλάσης μπορούν να προσπελάσουν τα ιδιωτικά περιεχόμενα άλλων αντικειμένων της ίδιας κλάσης.
Ενθυλάκωση δεδομένων (data encapsulation) καλείται η ιδιότητα που προσφέρουν οι κλάσεις να «κρύβουν» τα ιδιωτικά δεδομένα τους από το υπόλοιπο πρόγραμμα και να εξασφαλίζουν πως μόνο μέσω των δημόσιων μεθόδων τους θα μπορούν αυτά να προσπελαστούν. Αυτή η τακτική παρουσιάζει μόνο οφέλη καθώς εξαναγκάζει κάθε εξωτερικό πρόγραμμα να φιλτράρει το χειρισμό που επιθυμεί να κάνει στα πεδία μίας κλάσης μέσω των ελέγχων που μπορούν να περιέχονται στις δημόσιες μεθόδους της κλάσης.
Αφαίρεση δεδομένων καλείται η ιδιότητα των κλάσεων να αναπαριστούν αφαιρετικά πολύπλοκες οντότητες στο προγραμματιστικό περιβάλλον. Μία κλάση αποτελεί ένα αφαιρετικό μοντέλο κάποιας κατηγορίας αντικειμένων. Επίσης, οι κλάσεις προσφέρουν και αφαίρεση ως προς τον υπολογιστή, εφόσον η καθεμία μπορεί να θεωρηθεί ένας μικρός και αυτάρκης υπολογιστής (με δική του κατάσταση, μεθόδους και μεταβλητές).
Κληρονομικότητα ονομάζεται η ιδιότητα των κλάσεων να επεκτείνονται σε νέες κλάσεις, ρητά δηλωμένες ως κληρονόμους (υποκλάσεις ή 'θυγατρικές κλάσεις'), οι οποίες μπορούν να επαναχρησιμοποιήσουν τις μεταβιβάσιμες μεθόδους και ιδιότητες της γονικής τους κλάσης αλλά και να προσθέσουν δικές τους. Στιγμιότυπα των θυγατρικών κλάσεων μπορούν να χρησιμοποιηθούν όπου απαιτούνται στιγμιότυπα των γονικών (εφόσον η θυγατρική είναι κατά κάποιον τρόπο μία πιο εξειδικευμένη εκδοχή της γονικής), αλλά το αντίστροφο δεν ισχύει. Παράδειγμα κληρονομικότητας είναι μία γονική κλάση Vehicle (=Όχημα) και οι δύο πιο εξειδικευμένες υποκλάσεις της Car (=Αυτοκίνητο) και Bicycle (=Ποδήλατο), οι οποίες λέμε ότι "κληρονομούν" από αυτήν. Πολλαπλή κληρονομικότητα είναι η δυνατότητα που προσφέρουν ορισμένες γλώσσες προγραμματισμού μία κλάση να κληρονομεί ταυτόχρονα από περισσότερες από μία γονικές. Από μία υποκλάση μπορούν να προκύψουν νέες υποκλάσεις που κληρονομούν από αυτήν, με αποτέλεσμα μία ιεραρχία κλάσεων που συνδέονται μεταξύ τους "ανά γενιά" με σχέσεις κληρονομικότητας.
Υπερφόρτωση μεθόδου (method overloading) είναι η κατάσταση κατά την οποία υπάρχουν, στην ίδια ή σε διαφορετικές κλάσεις, μέθοδοι με το ίδιο όνομα και πιθανώς διαφορετικά ορίσματα. Αν πρόκειται για μεθόδους της ίδιας κλάσης διαφοροποιούνται μόνο από τις διαφορές τους στα ορίσματα και στον τύπο επιστροφής.
Υποσκέλιση μεθόδου (method overriding) είναι η κατάσταση κατά την οποία μία θυγατρική κλάση και η γονική της έχουν μία μέθοδο ομώνυμη και με τα ίδια ορίσματα. Χάρη στη δυνατότητα του πολυμορφισμού ο μεταγλωττιστής «ξέρει» πότε να καλέσει ποια μέθοδο, βασισμένος στον τύπο του τρέχοντος αντικειμένου. Δηλαδή πολυμορφισμός είναι η δυνατότητα των αντικειμενοστρεφών μεταγλωττιστών να αποφασίζουν δυναμικά ποια είναι η κατάλληλη να κληθεί μέθοδος σε συνθήκες υποσκέλισης.
Αφηρημένη κλάση (abstract class) είναι μία κλάση που ορίζεται μόνο για να κληρονομηθεί σε θυγατρικές υποκλάσεις και δεν υπάρχουν δικά της στιγμιότυπα (αντικείμενα). Η αφηρημένη κλάση ορίζει απλώς ένα "συμβόλαιο" το οποίο θα πρέπει να ακολουθούν οι υποκλάσεις της όσον αφορά τις υπογραφές των μεθόδων τους (όπου ως υπογραφή ορίζεται το όνομα, τα ορίσματα και η τιμή επιστροφής μίας διαδικασίας). Μία αφηρημένη κλάση μπορεί να έχει και μη αφηρημένες μεθόδους οι οποίες υλοποιούνται στην ίδια την κλάση (αν και φυσικά μπορούν να υποσκελίζονται σε υποκλάσεις). Αντιθέτως, οι αφηρημένες μέθοδοί της είναι απλώς ένας ορισμός της υπογραφής τους και εναπόκειται στις υποκλάσεις να τις υλοποιήσουν. Μία αφηρημένη κλάση που δεν έχει γνωρίσματα και όλες οι μέθοδοί της είναι αφηρημένες και δημόσιες καλείται διασύνδεση (interface). Οι κλάσεις που κληρονομούν από μία διασύνδεση λέγεται ότι την "υλοποιούν".
Ακολουθεί ένα απλό παράδειγμα σε γλώσσα προγραμματισμού Java:
interface Logger {
public void log(String msg);
}
class ConsoleLogger implements Logger {
public void log(String msg) {
System.err.println("\nConsole logging..." + msg + "\n");
}
}
class FileLogger implements Logger {
public void log(String msg) {
System.out.println("\nFile logging..." + msg + "\n");
}
}
public class LogTest {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("\nError. Exiting...");
return;
}
Logger logg;
String choice = args[0];
if (choice.equals("FileLogger")) {
logg = new FileLogger();
} else if (choice.equals("ConsoleLogger")) {
logg = new ConsoleLogger();
} else {
System.err.println("\nError. Exiting...");
return;
}
logg.log("Log This!");
}
}
Στο παραπάνω παράδειγμα ορίζουμε μία διασύνδεση Logger η οποία παρέχει την υπογραφή μίας μεθόδου log, που υποθέτουμε πως πρέπει να καταγράφει κάπου πληροφορίες για τα σφάλματα που συναντά η εφαρμογή όταν εκτελείται (οι πληροφορίες αυτές τής μεταβιβάζονται με το αλφαριθμητικό όρισμα msg). Η κλάση ConsoleLogger και η κλάση FileLogger είναι δύο διαφορετικές κλάσεις που υλοποιούν τη διασύνδεση Logger και υποσκελίζουν, η καθεμία με διαφορετικό τρόπο, τη μέθοδο log ώστε η μία να καταγράφει πληροφορίες στην οθόνη και η άλλη σε κάποιο αρχείο.
Το πρόγραμμα LogTest είναι ένα μικρό δοκιμαστικό πρόγραμμα το οποίο δέχεται ως όρισμα γραμμής εντολών το πού επιθυμεί ο χρήστης να γίνεται η καταγραφή και δημιουργεί ένα στιγμιότυπο της αντίστοιχης κλάσης: της ConsoleLogger ή της FileLogger. Το στιγμιότυπο αυτό δηλώνεται με τον γενικότερο τύπο Logger, τον τύπο δηλαδή της διασύνδεσης που υλοποιούν και οι δύο κλάσεις, αλλά χάρη στον πολυμορφισμό καλείται αυτομάτως η κατάλληλη εκδοχή της μεθόδου log.
Με το πέρασμα του χρόνου κωδικοποιήθηκαν κάποιες ανεπίσημες αρχές για την ορθή σχεδίαση αντικειμενοστρεφών συστημάτων λογισμικού. Οι αρχές αυτές παρουσιάστηκαν κατά καιρούς σε βιβλία και άρθρα ακαδημαϊκών και αναγνωρισμένων μηχανικών λογισμικού. Οι σπουδαιότερες αρχές είναι οι παρακάτω:
- Αρχή ανοιχτότητας-κλειστότητας (open-closed principle), του δημιουργού της γλώσσας προγραμματισμού Eiffel Μπέρτραντ Μέιερ. Η αρχή αυτή δηλώνει πως τα συστατικά ενός προγράμματος πρέπει να είναι "ανοιχτά" ως προς την επέκταση των δυνατοτήτων του συστήματος αλλά "κλειστά" ως προς αλλαγές στην υλοποίηση του. Πρακτικώς αυτό σημαίνει οι διάφορες κλάσεις και τα υπόλοιπα τμήματα λογισμικού να μη χρειάζεται να τροποποιηθούν σε περίπτωση που προστεθεί νέα λειτουργικότητα στο σύστημα (π.χ. μία νέα κλάση) προκειμένου να την αξιοποιήσουν. Βεβαίως είναι αδύνατο να μη χρειάζεται να τροποποιηθεί τίποτα, οπότε αυτό που επιτάσσει στην πραγματικότητα η εν λόγω αρχή είναι η ελαχιστοποίηση και η συγκέντρωση, κατά προτίμηση σε ένα μικρό τμήμα του κώδικα, των γραμμών που θα πρέπει να αλλάξουν. Αυτό συνήθως επιτυγχάνεται μέσω αφαίρεσης (με αφηρημένες κλάσεις ή διασυνδέσεις και πραγματικές κλάσεις που κληρονομούν από αυτές) και με χρήση του πολυμορφισμού. Έτσι, στο παράδειγμα της προηγούμενης ενότητας αν προσθέσουμε και μία τρίτη υλοποίηση της διασύνδεσης Logger, την PrinterLogger η οποία "καταγράφει" σφάλματα αποστέλλοντας τα προς εκτύπωση, ο κώδικας του προγράμματος γίνεται:
public class LogTest {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("\nError. Exiting...");
return;
}
Logger logg;
String choice = args[0];
if (choice.equals("FileLogger")) {
logg = new FileLogger();
} else if(choice.equals("ConsoleLogger")) {
logg = new ConsoleLogger();
} else if(choice.equals("PrinterLogger")) {
logg = new PrinterLogger();
} else {
System.err.println("\nError. Exiting...");
return;
}
logg.log("Log This!");
}
}
Όπως φαίνεται μόνη αλλαγή είναι η προσθήκη μίας ακόμα δήλωσης else if. Η εντολή
μπορεί να λειτουργήσει και με αντικείμενα του νέου τύπου χωρίς καμία τροποποίηση. Μία συνηθισμένη τακτική για διασφάλιση της κλειστότητας του ολικού προγράμματος ως προς την υλοποίηση μίας κλάσης, είναι η συνειδητή προσπάθεια για δήλωση όλων των γνωρισμάτων της ως ιδιωτικών. Έτσι η προσπέλαση των πεδίων της κλάσης μπορεί να ελεγχθεί εξ ολοκλήρου μέσω ειδικών δημόσιων μεθόδων της, γεγονός που διευκολύνει κατά πολύ την αποσφαλμάτωση: στις μεθόδους αυτές συγκεντρώνονται οι έλεγχοι επιτρεπτών τιμών για τα πεδία, έλεγχοι κατάλληλων συνθηκών κλπ.
- Αρχή υποκατάστασης Λίσκοφ (Liskov substitution principle), της επιστήμονα υπολογιστών Μπάρμπαρα Λίσκοφ. Η αρχή αυτή συμπυκνώνεται στον παρακάτω κανόνα για σχηματισμό μίας ορθής ιεραρχίας κλάσεων: μία κλάση Κ1 μπορεί να υλοποιηθεί ως υποκλάση μίας κλάσης Κ2 αν κάθε πρόγραμμα Π το οποίο λειτουργεί με αντικείμενα Κ2 συμπεριφέρεται με τον ίδιο τρόπο και με αντίστοιχα αντικείμενα Κ1. Έτσι με την αρχή υποκατάστασης Λίσκοφ φαίνεται πως για να οριστεί μία κλάση ως θυγατρική μίας άλλης δεν αρκεί να έχουν διαισθητικά μία ανάλογη εννοιολογική σχέση (π.χ. μία κλάση που αναπαριστά όχημα και μία που αναπαριστά αυτοκίνητο) αλλά, στο πλαίσιο του υπό εξέταση προγράμματος, τα αντικείμενα της υποκλάσης να έχουν πάντα την ίδια προγραμματιστική συμπεριφορά με τα αντικείμενα της υπερκλάσης υπό τις ίδιες συνθήκες.
- Αρχή αντιστροφής εξαρτήσεων (dependency inversion principle), του γνωστού μηχανικού λογισμικού Ρόμπερτ Σέσιλ Μάρτιν. Η αρχή αυτή πρακτικά αποτελεί εκλέπτυνση της αρχής ανοιχτότητας-κλειστότητας, προϋποθέτοντας όμως χρήση και της αρχής υποκατάστασης Λίσκοφ. Αφορά ιεραρχίες κληρονομικότητας κλάσεων και τη χρήση αντικειμένων αυτών των ιεραρχιών από εξωτερικά προγράμματα. Στα πλαίσια της αρχής αντιστροφής εξαρτήσεων ένα τμήμα λογισμικού Α (π.χ. μία κλάση) το οποίο χρησιμοποιεί τις υπηρεσίες που παρέχει ένα άλλο τμήμα λογισμικού Β, καλώντας για παράδειγμα μία μέθοδό του, θεωρείται στοιχείο "υψηλότερου επιπέδου" σε σχέση με το Β. Η αρχή λέει πως τα υψηλού επιπέδου στοιχεία δεν πρέπει να εξαρτώνται από την υλοποίηση χαμηλότερου επιπέδου στοιχείων, αλλά πως και τα δύο πρέπει να βασίζονται σε ενδιάμεσα επίπεδα αφαίρεσης. Στην πράξη αυτή η αφαίρεση είναι μία διασύνδεση (ή αφηρημένη κλάση) την οποία γνωρίζει το υψηλού επιπέδου στοιχείο Α και υλοποιεί το χαμηλού επιπέδου στοιχείο Β. Ακόμα και αν το Β αλλαχθεί με μία κλάση Γ η οποία επίσης υλοποιεί την ίδια διασύνδεση, το Α θα πρέπει να συνεχίσει να λειτουργεί χωρίς καμία τροποποίηση. Η αρχή αντιστροφής εξαρτήσεων δεν είναι παρά ένα απτό παράδειγμα χρήσης ιεραρχικών επιπέδων με τη βοήθεια ενδιάμεσων αφαιρέσεων, μίας πρακτικής που εφαρμόζεται κατά κόρον στην επιστήμη υπολογιστών (για ένα άλλο παράδειγμα βλέπε δίκτυα υπολογιστών).
- Αρχή διαχωρισμού διασυνδέσεων (interface segregation principle), του μηχανικού λογισμικού Ρόμπερτ Σέσιλ Μάρτιν. Η εν λόγω αρχή σημαίνει ότι σε περιπτώσεις όπου διαφορετικά υποσύνολα μεθόδων μίας κλάσης αφορούν διαφορετικές περιπτώσεις χρήσης της κλάσης, σκόπιμο είναι να ορίζουμε επιμέρους διασυνδέσεις τις οποίες η κλάση θα υλοποιεί. Κάθε τέτοια διασύνδεση θα ορίζει μόνο το αντίστοιχο υποσύνολο των μεθόδων.
- Αρχή μοναδικής αρμοδιότητας (single responsibility principle), των Τομ Ντε Μάρκο και Μέιρ Πέιτζ Τζόουνς. Σύμφωνα με την αρχή αυτή κάθε κλάση θα πρέπει να έχει μόνο μία, καλά ορισμένη και διαχωρισμένη από το υπόλοιπο πρόγραμμα αρμοδιότητα, η ύπαρξη της οποίας να εξυπηρετεί έναν συγκεκριμένο σκοπό. Αν μπορούμε να εντοπίσουμε σε μία κλάση Α δύο διαφορετικές αρμοδιότητες, τότε η καλύτερη λύση είναι η διάσπαση της σε δύο κλάσεις Β' και Γ', καθεμία από τις οποίες θα λάβει ένα υποσύνολο των πεδίων και των μεθόδων της Α. Τα υποσύνολα αυτά θα είναι ξένα μεταξύ τους, οπότε με το ανάποδο σκεπτικό αν μπορούμε να διασπάσουμε μία κλάση Α σε δύο άλλες κλάσεις (π.χ. σε περίπτωση που κάποιες μέθοδοι δε χρησιμοποιούν κάποια γνωρίσματα, οπότε οι μεν μπορούν να καταλήξουν στη μία κλάση Β' και τα πεδία στην άλλη κλάση Γ') τότε πιθανώς η κλάση να παραβιάζει την αρχή μοναδικής αρμοδιότητας. Έτσι έχουν προταθεί κάποιες μετρικές οι οποίες επιχειρούν να προσδιορίσουν την έλλειψη συνοχής (cohesion) σε μία κλάση, δηλαδή το κατά πόσον οι μέθοδοι της δε σχετίζονται με τα γνωρίσματα της. Συνήθως η συνοχή αντιπαραβάλλεται με τη σύζευξη (coupling), δηλαδή το βαθμό στον οποίον μία κλάση εξαρτάται από κάποια/ες άλλη/ες, και τα δύο αυτά μεγέθη είναι αντιστρόφως ανάλογα.
Κατά καιρούς έχουν οριστεί κάποιες μετρικές για την εκτίμηση της περιπλοκότητας και της ποιότητας μίας αντικειμενοστρεφούς σχεδίασης λογισμικού. Σε αυτές συμπεριλαμβάνονται και οι μετρικές έλλειψης συνοχής (LCOM). Ακολουθεί μία σύνοψη των σπουδαιότερων:
Το ακέραιο πλήθος των μεθόδων που ορίζονται σε μία κλάση. Δεν υπάρχει ένα καθιερωμένο εύρος βέλτιστου WMC αλλά είναι γενικώς αποδεκτό ότι υπερβολικά μεγάλο WMC οδηγεί σε μεγαλύτερη πιθανότητα σφαλμάτων και προβλημάτων συντήρησης.
Το μήκος του μέγιστου μονοπατιού κληρονομικότητας από την τρέχουσα κλάση έως τη ρίζα μιας ιεραρχίας κλάσεων. Μεγάλες τιμές DIT αυξάνουν την πολυπλοκότητα της σχεδίασης, την πιθανότητα σφαλμάτων αλλά και την επαναχρησιμοποίηση κώδικα λόγω της κληροδότησης μεθόδων. Μία βέλτιστη τιμή DIT θεωρείται το 5.
Το πλήθος των κλάσεων που κληρονομούν άμεσα από την τρέχουσα. Υψηλό NOC σημαίνει υψηλή επαναχρησιμοποίηση κώδικα αλλά πιθανόν να υποδεικνύει εννοιολογικά εσφαλμένη χρήση της κληρονομικότητας. Δεν υπάρχει κάποιο γενικώς αποδεκτό βέλτιστο NOC, αφού αυτό εξαρτάται από την εκάστοτε κλάση, αλλά εν γένει οι κλάσεις οι ευρισκόμενες υψηλότερα στην ιεραρχία κληρονομικότητας είναι θεμιτό να έχουν υψηλότερο NOC από όσες τοποθετούνται χαμηλότερα αφού είναι λιγότερο εξειδικευμένες.
Το πλήθος των κλάσεων από τις οποίες εξαρτάται η τρέχουσα κλάση. Κάθε τέτοια εξάρτηση μπορεί να είναι αμφίδρομη ή μονόδρομη οποιασδήποτε κατεύθυνσης. Επομένως υψηλό CBO σημαίνει υψηλή σύζευξη (coupling) και δεν είναι επιθυμητό, καθώς αποτρέπει την επαναχρησιμοποίηση κώδικα και ζημιώνει τον αρθρωτό σχεδιασμό του προγράμματος. Όσο χαμηλότερο είναι το CBO μίας κλάσης τόσο πιθανότερο είναι να μπορεί η τελευταία να επαναχρησιμοποιηθεί ως μαύρο κουτί. Υπερβολικά υψηλές θεωρούνται οι τιμές CBO > 14.
Response for a Class (RFC και RFC’)
Επεξεργασία
Η μετρική αυτή ισούται με το πλήθος των μεθόδων που μπορεί να εκτελεστούν ως απάντηση στη λήψη ενός μηνύματος / συμβάντος από την τρέχουσα κλάση (έστω Α), τόσο τοπικών της μεθόδων όσο και «απομακρυσμένων» μεθόδων άλλων κλάσεων οι οποίες καλούνται άμεσα από την Α. Η μετρική RFC’ συμπεριλαμβάνει επιπλέον και τις απομακρυσμένες μεθόδους που καλούνται εμμέσως (π. χ. από άμεσα καλούμενες απομακρυσμένες μεθόδους ή, αναδρομικά, από άλλες έμμεσα καλούμενες απομακρυσμένες μεθόδους). Κάθε μέθοδος μετράται μόνο μία φορά στον υπολογισμό του RFC, ανεξαρτήτως του πόσες φορές καλείται. Υψηλή τιμή RFC υποδεικνύει υψηλές πιθανότητες σφάλματος και δυσκολία συντήρησης.
Μετρική έλλειψης συνοχής. Ισχύει LCOM1 = P - Q αν P > Q, διαφορετικά LCOM1 = 0. Τα P, Q υπολογίζονται με τον ακόλουθο αλγόριθμο (σε ψευδοκώδικα):
P = 0; Q = 0;
Για κάθε ζεύγος μεθόδων της κλάσης
do
{
Αν τα σύνολα των πεδίων που χρησιμοποιούν οι δύο τρέχουσες μέθοδοι είναι ξένα μεταξύ τους
P = P + 1;
Διαφορετικά
Q = Q + 1;
}
Μία τιμή LCOM1 = 0 υποδεικνύει συνεκτική κλάση, ενώ αν LCOM1 > 0 η κλάση καλό είναι να διασπαστεί. Η μετρική LCOM1 παρουσιάζει κάποια προβλήματα, όπως π.χ. ότι δίνει τιμή 0 για κλάσεις πολύ διαφορετικές μεταξύ τους.
Βελτιωμένη μετρική έλλειψης συνοχής. Ισχύει LCOM2 = 1 - sum(mA)/(m*a), όπου m το πλήθος των μεθόδων της κλάσης, a το πλήθος των πεδίων της, mA το πλήθος των μεθόδων που προσπελαύνουν ένα γνώρισμα και sum(mA) το άθροισμα των mA για όλα τα πεδία μίας κλάσης. Η μετρική LCOM2 αποτελεί τον μέσο όρο των ποσοστών των μεθόδων που δε χρησιμοποιούν κάθε γνώρισμα. LCOM2 = 0 (υψηλή συνοχή) σημαίνει πως όλες οι μέθοδοι της κλάσης χρησιμοποιούν όλα τα πεδία της, ενώ LCOM2 = 1 (καμία συνοχή) σημαίνει πως καμία μέθοδος δεν προσπελαύνει κανένα πεδίο.
Εναλλακτική μετρική έλλειψης συνοχής. Ισχύει LCOM3 = (m - sum(mA)/a) / (m-1). Η μετρική LCOM3 λαμβάνει τιμές από 0 (υψηλή συνοχή) έως 2 (καμία συνοχή). Τιμές μεγαλύτερες του 1 σημαίνουν πως με βεβαιότητα υπάρχει τουλάχιστον ένα "νεκρό γνώρισμα", δηλαδή γνώρισμα το οποίο δεν προσπελαύνεται από καμία μέθοδο της κλάσης.
Μετά την ευρεία διάδοση του ΑΠ κατά τη δεκαετία του '90, το αντικειμενοστρεφές μοντέλο σχεδίασης (με κλάσεις, κληρονομικότητα, αντικείμενα και τυποποιημένες αλληλεπιδράσεις μεταξύ τους) επικράτησε ακόμη και για μοντελοποίηση που δεν περιελάμβανε καν προγραμματισμό (π. χ. σχήματα βάσεων δεδομένων). Έτσι αναπτύχθηκαν διάφορες πρότυπες γλώσσες μοντελοποίησης λογισμικού οι οποίες τυποποιούσαν οπτικά σύμβολα και συμπεριφορές με στόχο την αφαιρετική περιγραφή της λειτουργίας και της δομής ενός υπολογιστικού συστήματος. Οι γλώσσες αυτές είχαν εξαρχής έναν εμφανή αντικειμενοστρεφή προσανατολισμό. Τελικά οι πιο δημοφιλείς από αυτές ενοποιήθηκαν στο κοινό πρότυπο UML που η πρώτη του έκδοση οριστικοποιήθηκε το 1997.
Η UML πλέον είναι η πρότυπη γλώσσα μοντελοποίησης στη μηχανική λογισμικού. Χρησιμοποιείται για τη γραφική απεικόνιση, προσδιορισμό, κατασκευή και τεκμηρίωση των στοιχείων ενός συστήματος λογισμικού. Μπορεί να χρησιμοποιηθεί σε διάφορες φάσεις ανάπτυξης, από την ανάλυση απαιτήσεων ως τον έλεγχο ενός ολοκληρωμένου συστήματος, και αποτελείται από ένα σύνολο προσυμφωνημένων όρων, συμβόλων και διαγραμμάτων.
Κατά τα τέλη της δεκαετίας του '70 ένας αρχιτέκτονας ονόματι Κρίστοφερ Αλεξάντερ επιχείρησε να βρει και να καταγράψει αποδεδειγμένα ποιοτικούς σχεδιασμούς στον τομέα των κατασκευών. Έτσι μελέτησε πολλές διαφορετικές κατασκευές που εξυπηρετούσαν τον ίδιο σκοπό και προσπάθησε να ανακαλύψει κοινά στοιχεία, τα οποία κατηγοριοποίησε σε σχεδιαστικά πρότυπα (design patterns). Το 1987 η ιδέα της εύρεσης σχεδιαστικών προτύπων εφαρμόστηκε για πρώτη φορά στη μηχανική λογισμικού και μέχρι τα μέσα της δεκαετίας του '90 η εν λόγω έννοια είχε καθιερωθεί και εξαπλωθεί, στραμμένη πλέον στον κόσμο της αντικειμενοστρέφειας.
Ένα πρότυπο σχεδίασης ορίζεται ως μία αποδεδειγμένα καλή λύση που έχει εφαρμοστεί με επιτυχία στην επίλυση ενός επαναλαμβανόμενου προβλήματος σχεδίασης συστημάτων λογισμικού. Τα πρότυπα σχεδίασης ορίζονται τόσο σε επίπεδο μακροσκοπικής σχεδίασης όσο και σε επίπεδο υλοποίησης, ενώ με τη χρήση τους ένας προγραμματιστής αντικαθιστά πρακτικώς μεγάλα τμήματα του κώδικα του με μαύρα κουτιά. Πρόκειται για αφαιρέσεις υψηλού επιπέδου που αποτελούν πλήρη υποσυστήματα, κατάλληλα ρυθμισμένα για την επίλυση συγκεκριμένων προβλημάτων και έτοιμα για χρήση. Έχουν οριστεί διάφορες κατηγορίες προτύπων, για διαφορετικά προβλήματα, και κάθε κατηγορία περιλαμβάνει πολλαπλά στοιχεία. Έτσι υπάρχουν κατασκευαστικά πρότυπα, δομικά πρότυπα, συμπεριφορικά πρότυπα κλπ.