Αφαιρετικές Μέθοδοι Istrumet Strig what() ΔΙΑΠΡΟΣΩΠΕΙΕΣ Wid Strig what() Percussio Strig what() Striged Strig what() WoodWid Strig what() Brass Strig what() Στα προηγούμενα παραδείγματα, οι μέθοδοι της κλάσεως-κληροδότη Istrumet είναι "πλασματικές", με την έννοια ότι ο ορισμός τους γίνεται για τον καθορισμό μιας διαπροσωπείας κοινής σε όλες τις κλάσεις-κληρονόμους της (commo iterface). 1 2 Αφαιρετικές Μέθοδοι Αφαιρετικές Κλάσεις H Java μας επιτρέπει να δηλώνουμε ρητά ορισμένες μεθόδους μιας κλάσης σαν αφαιρετικές ώστε: Να έχουμε τη δυνατότητα να προσδιορίζουμε την διαπροσωπεία της κλάσης που τις περιέχει. Να αποτρέπεται η εκ παραδρομής κλήση τους από άλλες μεθόδους. Ο προσδιορισμός μιας μεθόδου ως αφαιρετικής γίνεται ως εξής: abstract void X(); Μια κλάση η οποία περιλαμβάνει έστω και μια «αφαιρετική» μέθοδο, καθίσταται επίσης αφαιρετική και πρέπει να δηλωθεί ως αφαιρετική. Τι συμβαίνει αν προσπαθήσουμε να δημιουργήσουμε ένα αντικείμενο από κάποια αφαιρετική κλάση; Εφόσον μια κλάση κληρονομεί από μια αφαιρετική κλάση, και θέλουμε να δημιουργήσουμε αντικείμενα γι' αυτήν, θα πρέπει η κλάση μας να περιέχει τους ορισμούς για όλες τις αφαιρετικές μεθόδους της κλάσεως-βάσης. Διαφορετικά ο μεταφραστής επιβάλλει να προσδιορίσουμε την κλάση μας σαν αφαιρετική. Μπορούμε τέλος να δηλώσουμε μια κλάση σαν αφαιρετική, χωρίς ωστόσο η κλάση αυτή να περικλείει αφαιρετικές μεθόδους. Γιατί να θέλουµε κάτι τέτοιο; Για να αποκλείσουμε τη δημιουργία αντικειμένων αυτής της κλάσης. 3 4
Διαπροσωπείες-Iterfaces Παράδειγμα H Java επεκτείνει τις ιδέες της αφαιρετικότητας μεθόδων και κλάσεων που καθορίζονται με το abstract, μέσω του iterface. Με το iterface εισάγεται η έννοια μιας "καθαρής" αφηρημένης κλάσης. Επιτρέπεται έτσι στον προγραμματιστή να καθορίσει τη µορφή μιας κλάσης: ονόματα μεθόδων, καταλόγους παραμέτρων, τύπους επιστροφής, αλλά όχι σώματα μεθόδων. Επίσης, επιτρέπεται ο καθορισμός πεδίων δεδομένων, τα οποία ωστόσο προκαθορίζονται υπορρήτως ως στατικά (static) και τελικά (fial). Για την δήλωση διαπροσωπειών, χρησιμοποιούμε τη λέξη κλειδί iterface στη θέση της λέξης-κλειδί class. 5 6 Παράδειγμα Παράδειγμα (συνέχεια) iterface Istrumet { it i = 5; // static & fial ; Strig what(); ; class Wid implemets Istrumet { public { System.out.pritl("Wid.play()"); public Strig what() { retur "Wid"; public { class Percussio implemets Istrumet { public { System.out.pritl("Percussio.play()"); public Strig what() { retur "Percussio"; public { class Striged implemets Istrumet { public { System.out.pritl("Striged.play()"); public Strig what() { retur "Striged"; public { class Brass exteds Wid { public { System.out.pritl("Brass.play()"); public { System.out.pritl("Brass.adjust()"); class Woodwid exteds Wid { public void play({system.out.pritl("woodwid.play()"); public Strig what() { retur "Woodwid"; 7 8
Παράδειγμα (συνέχεια) Does't care about type, so ew types added to the system still work right public class Music5 { static void tue(istrumet i) { //... i.play(); static void tueall(istrumet[] e) { for(it i = 0; i < e.legth; i++) tue(e[i]); public static void mai(strig[] args) { Istrumet[] orchestra = ew Istrumet[5]; it i = 0; orchestra[i++] = ew Wid(); orchestra[i++] = ew Percussio(); orchestra[i++] = ew Striged(); orchestra[i++] = ew Brass(); orchestra[i++] = ew Woodwid(); tueall(orchestra); Upcastig durig additio to the array Επεξηγήσεις Παρατηρείστε ότι: Δεν έχει σημασία αν γίνεται upcastig σε κανονική κλάση, σε αφηρημένη κλάση ή σε διαπροσωπεία. Η δημοσιότητα της Διαπροσωπείας: Οι διαπροσωπείες μπορούν να προσδιοριστούν ως δηµόσιες (public) ή φιλικές, ακολουθώντας τους ίδιους κανόνες που ακολουθούν οι κανονικές κλάσεις. Οι μέθοδοι μιας διαπροσωπείας εξυπακούονται σαν δημόσιες (αν δεν προσδιοριστούν ρητώς έτσι). Συνεπώς, κατά την υλοποίηση της διαπροσωπείας με κάποια κλάση, οι μέθοδοι της κλάσης πρέπει να δηλωθούν ως δημόσιες. Διαφορετικά προκύπτει περιορισμός της πρόσβασης σε κάποια μέθοδο κατά την κληρονομικότητα, πράγμα που απαγορεύει ο μεταφραστής της Java. 9 10 Σχεδιαστικό πρότυπο «στρατηγικής» (strategy desig patter) Παράδειγμα υλοποίησης προτύπου στρατηγικής Strategy desig patter: αφορά στη δημιουργία μεθόδου, η οποία συμπεριφέρεται με διαφορετικό τρόπο ανάλογα με τα αντικείμενα που περνιούνται στη μέθοδο αυτή σαν παράμετροι. Η μέθοδος περιλαμβάνει το «σταθερό» τμήμα του αλγορίθμου που πρέπει να εκτελεσθεί. Η «στρατηγική» αφορά στη διαφοροποίηση της συμπεριφοράς της μεθόδου και αντιστοιχεί σε ένα αντικείμενο που περνιέται σαν παράμετρος στην μέθοδο, και το οποίο περιέχει κώδικα προς εκτέλεση. Υλοποιεί έναν «επεξεργαστή», ο οποίος λαμβάνει κάποια είσοδο, την επεξεργάζεται και επιστρέφει το αποτέλεσμα της επεξεργασίας. Διαθέτει: Μέθοδο ame(): επιστρέφει το όνομα της κλάσης public Strig ame(); Μέθοδο process(): δέχεται ένα αντικείμενο, το επεξεργάζεται και επιστρέφει το αποτέλεσμα της επεξεργασίας Object process(object); import java.util.*; class Processor { public Strig ame() { retur getclass().getsimplename(); Object process(object iput) { retur iput; 11 12
Υλοποίηση προτύπου στρατηγικής import java.util.*; class Upcase exteds Processor { Strig process(object iput) { // Covariat retur retur ((Strig)iput).toUpperCase(); class Dowcase exteds Processor { Strig process(object iput) { retur ((Strig)iput).toLowerCase(); class Splitter exteds Processor { Strig process(object iput) { retur Arrays.toStrig(((Strig)iput).split(" ")); 13 14 public class Apply { public static void process(processor p, Object s) { prit("usig Processor " + p.ame()); prit(p.process(s)); Υλοποίηση προτύπου στρατηγικής public static Strig s = "Disagreemet with beliefs is by defiitio icorrect"; public static void mai(strig[] args) { process(ew Upcase(), s); process(ew Dowcase(), s); process(ew Splitter(), s); Mέθοδος Apply.process() δέχεται σαν παραμέτρους ένα αντικείμενο «Processor» και ένα αντικείμενο «Object». O «Processor» υλοποιεί την «στρατηγική», η οποία διαφοροποιεί την συμπεριφορά της κλάσης Apply. 15 Παράδειγμα (συνέχεια) Η παραπάνω μέθοδος (Apply.process) δεν μπορεί ωστόσο να εφαρμοστεί σε κλάσεις που δεν κληρονομούν από την Processor, ακόμα κι αν έχουν την ίδια διαπροσωπεία. Π.χ.: public class Filter { public Strig ame() { retur getclass().getsimplename(); public Waveform process(waveform iput) { retur iput; Η μή δυνατότητα χρήσης της Filter σε συνδυασμό με την Apply.process ώστε να γίνει επαναχρησιμοποίηση του κώδικα της Apply.process για επεξεργασία που παρέχει η Filter, οφείλεται στην ισχυρή συνδετικότητα ανάμεσα στην Apply.process και στην Processor. 16
Παράδειγμα (συνέχεια) public iterface Processor { Strig ame(); Object process(object iput); public class Apply { public static void process(processor p, Object s) { prit("usig Processor " + p.ame()); prit(p.process(s)); l Η επαναχρησιμοποίηση του κώδικα της Apply μπορεί να γίνει εφόσον οι προγραμματιστές γράφουν τις κλάσεις τους λαμβάνοντας υπόψη τη διαπροσωπεία Processor. Επαναχρησιμοποίηση Iterface public abstract class StrigProcessor implemets Processor{ public Strig ame() { retur getclass().getsimplename(); public abstract Strig process(object iput); public static Strig s = "If she weighs the same as a duck, she's made of wood"; public static void mai(strig[] args) { Apply.process(ew Upcase(), s); Apply.process(ew Dowcase(), s); Apply.process(ew Splitter(), s); 17 18 Επαναχρησιμοποίηση Iterface Άλλοι τρόποι επαναχρησιμοποίησης κώδικα class Upcase exteds StrigProcessor { public Strig process(object iput) { // Covariat retur retur ((Strig)iput).toUpperCase(); class Dowcase exteds StrigProcessor { public Strig process(object iput) { retur ((Strig)iput).toLowerCase(); class Splitter exteds StrigProcessor { public Strig process(object iput) { retur Arrays.toStrig(((Strig)iput).split(" ")); Συχνά δεν υπάρχει δυνατότητα να τροποποιήσουμε κάποιες κλάσεις, τις οποίες θέλουμε να χρησιμοποιήσουμε: Αν π.χ. οι κλάσεις αυτές ανήκουν σε βιβλιοθήκες τις οποίες δεν έχουμε υλοποιήσει εμείς οι ίδιοι. Στις περιπτώσεις αυτές μπορούμε να «προσαρμόσουμε» τις κλάσεις που μας ενδιαφέρουν στη Διαπροσωπεία που χρειαζόμαστε, χρησιμοποιώντας το σχεδιαστικό πρότυπο Προσαρμογέα (Adapter desig patter). Στο ακόλουθο παράδειγμα, θα εφαρμόσουμε το Adapter στις κλάσεις Filter. 19 20
Filter.java: η κλάση που μας ενδιαφέρει Filter.java public class Filter { public Strig ame() { retur getclass().getsimplename(); public Waveform process(waveform iput) { retur iput; public class Waveform { private static log couter; private fial log id = couter++; public Strig tostrig() { retur "Waveform " + id; public class LowPass exteds Filter { double cutoff; public LowPass(double cutoff) { this.cutoff = cutoff; public Waveform process(waveform iput) { retur iput; // Dummy processig public class HighPass exteds Filter { double cutoff; public HighPass(double cutoff) { this.cutoff = cutoff; public Waveform process(waveform iput) { retur iput; 21 22 Filter.java FilterAdapter public class BadPass exteds Filter { double lowcutoff, highcutoff; public BadPass(double lowcut, double highcut) { lowcutoff = lowcut; highcutoff = highcut; public Waveform process(waveform iput) { retur iput; class FilterAdapter implemets Processor { Filter filter; public FilterAdapter(Filter filter) { this.filter = filter; public Strig ame() { retur filter.ame(); public Waveform process(object iput) { retur filter.process((waveform)iput); public class FilterProcessor { public static void mai(strig[] args) { Waveform w = ew Waveform(); Apply.process(ew FilterAdapter(ew LowPass(1.0)), w); Apply.process(ew FilterAdapter(ew HighPass(2.0)), w); Apply.process( ew FilterAdapter(ew BadPass(3.0, 4.0)), w); 23 24
«Πολλαπλή» Κληρονομικότητα Παράδειγμα Πολλαπλής Κληρον. Μια Διαπροσωπεία (iterface), δεν έχει καθόλου υλοποίηση και δεν δεσμεύει μνήμη. Συνεπώς, μπορούμε να συνδυάσουμε πολλές διαπροσωπείες μαζί στην κληροδότηση κάποιας κλάσης. Στην C++, μια κλάση μπορεί να κληρονομεί από περισσότερες της μιας κλάσης. Αυτό δεν είναι δυνατόν στην JAVA. Μπορούμε ωστόσο να συνδυάσουμε πολλές διαπροσωπείες μαζί, τις οποίες να υλοποιεί από κοινού μια κλάση της JAVA, δίνοντάς μας μια μορφή πολλαπλής κληρονοµικότητας (multiple iheritace). import java.util.*; iterface CaFight {void fight(); iterface CaSwim {void swim(); iterface CaFly {void fly(); class ActioCharacter { public void fight() { class Hero exteds ActioCharacter implemets CaFight,CaSwim,CaFly { public void swim() { public void fly() { 25 26 Παράδειγμα Πολλαπλής Κληρον. Η χρησιμότητα των Διαπροσωπειών public class Adveture { static void t(cafight x) { x.fight(); static void u(caswim x) { x.swim(); static void v(cafly x) { x.fly(); static void w(actiocharacter x) { x.fight(); public static void mai(strig[] args) { Hero i = ew Hero(); t(i); // Treat it as a CaFight u(i); // Treat it as a CaSwim v(i); // Treat it as a CaFly w(i); // Treat it as a ActioCharacter Μας παρέχουν τη δυνατότητα να κάνουμε upcastig σε περισσότερους από έναν τύπους-βάσης, όπως στο προηγούμενο παράδειγμα. Αποτρέπουν τη δημιουργία αντικειμένων για κάποια συγκεκριμένη κλάση (όπως και όταν μια κλάση είναι αφηρημένη). Προτιμάτε τις διαπροσωπείες από τις αφηρημένες κλάσεις, στις περιπτώσεις που θέλετε να κατασκευάσετε βασικές κλάσεις κληροδότες, χωρίς οποιουσδήποτε ορισμούς μεθόδων και πεδία δεδομένων. Οι Διαπροσωπείες μπορούν να επεκταθούν με τη χρήση κληρονομικότητας. 27 28
Κληρονομικότητα στις Διαπροσωπείες iterface Moster { void meace(); iterface DagerousMoster exteds Moster { void destroy(); iterface Lethal { void kill(); class DragoZilla implemets DagerousMoster { public void meace() { public void destroy() { iterface Vampire exteds DagerousMoster,Lethal{ void drikblood(); class VeryBadVampire implemets Vampire { public void meace() { public void destroy() { public void kill() { public void drikblood() { Κληρονομικότητα στις Διαπροσωπείες public class HorrorShow { static void u(moster b) { b.meace(); static void v(dagerousmoster d) { d.meace(); d.destroy(); static void w(lethal l) { l.kill(); public static void mai(strig[] args) { DagerousMoster barey = ew DragoZilla(); u(barey); v(barey); Vampire vlad = ew VeryBadVampire(); u(vlad); v(vlad); w(vlad); 29 30 Συγκρούσεις ονομάτων σε συνδυασμό διαπροσωπειών iterface I1 { void f(); iterface I2 { it f(it i); iterface I3 { it f(); class C { public it f() { retur 1; class C2 implemets I1, I2 { public void f() { public it f(it i) { retur 1; class C3 exteds C implemets I2 { public it f(it i) { retur 1; class C4 exteds C implemets I3 { public it f() { retur 1; class C5 exteds C implemets I1 { iterface I4 exteds I1, I3 { Αρχικοποιήσεις μελών Διαπροσωπείας Τα πεδία των διαπροσωπειών έχουν προκαθοριστεί να είναι στατικά και τελικά. Ωστόσο, μπορούμε να τα αρχικοποιούμε με μη-σταθερές εκφράσεις. Η αρχικοποίηση γίνεται την στιγμή που φορτώνεται η διαπροσωπεία, όταν δηλ. γίνει αναφορά στα πεδία της. import java.util.*; public iterface RadVals { Radom rad = ew Radom(); it radomit = rad.extit(10); log radomlog = rad.extlog() * 10; float radomfloat = rad.extlog() * 10; double radomdouble = rad.extdouble() * 10; 31 32