Παράδειγμα Σύνθεσης: Class Name class Name{ private String first; private String middle; private String last; // Object represent names of people //e.g., "William" //e.g., "Jefferson" //e.g., "Clinton" Name(){ //default constructor Name(String first, String last){ this.first = first; this.last = last; Name(String first, String middle, String last){ this(first,last); this.middle = middle; String first(){ return first; String middle(){ return middle; String last(){ return last; void setfirst(string first){ this.first = first; void setmiddle(string middle){ this.middle =middle; void setlast(string last){ this.last = last; public String tostring(){ String s=new String(); if (first!= null) s += first + " "; if (middle!= null) s += middle + " "; if (last!= null) s += last + " "; return s.trim(); class TestName{ //Test driver for Name class public static void main(string[] args){ Name tr=new Name("Theodore ","Roosevelt"); Name fc=new Name("Francis", "Harry Compton", "Crick"); System.out.println(fc + "won the 1962 Nobel in Physiology. "); System.out.println("His first name was " + fc.first()); System.out.println(tr + " won the 1906 Nobel Peace Prize."); System.out.println("His middle name was " + tr.middle()); ΕΞΟ ΟΣ Francis Harry Compton Crick won the 1962 Nobel in Physiology. His first name was Francis Theodore Roosevelt won the 1906 Nobel Peace Prize. His middle name was null ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 1
Παράδειγμα Σύνθεσης - 2: Class Person H κλάση αυτή χρησιμοποιεί την κλάση Name του προηγούμενου παραδείγματος class Person{ //objects represent people protected Name name; protected char sex; // M or F protected String id; // e.g. Identity number Person(Name name, char sex){ this.name = name; this.sex = sex; Person(Name name, char sex, String id){ this.name = name; this.sex = sex; this.id = id; Name name(){ return name; char sex(){ return sex; String id(){ return id; void setid(string id){ this.id = id; public String tostring(){ String s = new String(name + " (sex: " + sex); if (id!= null) s += "; id: " + id; s += ")"; return s; class TestPerson{ // Test driver for the Person class: public static void main(string[] args){ Name bobsname = new Name("Robert", "Lee"); Person bob = new Person(bobsName, M ); System.out.println("bob: " + bob); bob.name.setmiddle("edward"); System.out.println("bob: " + bob); Person ann= new Person(new Name("Ann", "Baker"), F ); System.out.println("ann: " + ann); ann.setid("α101010"); System.out.println("ann: " + ann); ΕΞΟ ΟΣ bob: Robert Lee (sex: M) bob: Robert Edward Lee (sex: M) ann: Ann Baker (sex: F); ann: Ann Baker (sex: F; id: Α101010) ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 2
ΑΝΑ ΡΟΜΙΚΕΣ ΚΛΑΣΕΙΣ (RECURSIVE CLASSES) Αναδρομική μέθοδος είναι αυτή που καλεί τον εαυτό της. Αναδρομική κλάση είναι αυτή που συντίθεται από τον εαυτό της, δηλ. έχει τουλάχιστον ένα μέλος το οποίο είναι αναφορά σε αντικείμενα της κλάσης που ανήκει. Οι αναδρομικές κλάσεις παρέχουν μία ισχυρή τεχνική δημιουργίας συνδεμένων δομών οι οποίες μπορούν να αναπαραστήσουν αποτελεσματικά πολύπλοκες δομές. Το παρακάτω παράδειγμα χρησιμοποιεί την κλάση Person του προηγούμενου παραδείγματος και προσθέτει τα χαρακτηριστικά mother, father, καθώς και τα twoblanks, και tab για την εμφάνιση, τα οποία χρησιμοποιεί στην υπέρβαση (αλλαγή λειτουργίας) της μεθόδου tostring(). Παράδειγμα: Οικογενειακά δένδρα class Person{ // Objects represent people protected Name name; protected char sex; // 'M' or 'F' protected String id; //e.g., Social Security number protected Person mother; protected Person father; private static final String twoblanks = " "; private static String tab = "" ; // Constructors Person(){ Person(Name name, char sex){ this.name = name; this.sex= sex; Person(Name name, char sex, String id){ this.name =name; // or this(name, sex); this.sex=sex; this.id = id; // Access methods Mutators eg.: Name name(){ return name; char sex(){ return sex; String id() { return id; void setid(string id){ this.id = id; void setmother(person mother){ this.mother = mother; void setfather(person father){ this.father = father; ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 3
public String tostring(){ String s = new String(name + " (" + sex + ")"); if (id!= null) s += "; id: " + id; s += "\n"; if (mother!= null ){ tab += twoblanks; // adds two banks s += tab + "mother: " + mother; tab = tab.substring(2); // removes two blanks if(father!= null){ tab += twoblanks; // adds two blanks s += tab + "father: " + father; tab = tab.substring(2); // removes two blanks return s; class RecTestPerson{ // Test driver for the Person class: public static void main(string[] args){ Person ww = new Person(new Name("William", "Windsor"), 'M'); Person cw = new Person(new Name("Charles", "Windsor"), 'M'); Person ds = new Person(new Name("Diana", "Spencer"), 'F'); Person es = new Person(new Name("Edward", "Spencer"), 'M'); Person ew = new Person(new Name("Elizabeth", "Windsor"), 'F'); Person pm = new Person(new Name("Phillip", "Mountbatten"), 'M'); Person eb = new Person(new Name("Elizabeth","Bowes-Lyon"), 'F'); Person gw = new Person(new Name("George", "Windsor"), 'M'); ww.setfather(cw); ww.setmother(ds); ds.setfather(es); cw.setmother(ew); cw.setfather(pm); ew.setmother(eb); ew.setfather(gw); System.out.println(ww); ΕΞΟ ΟΣ William Windsor (M) mother: Diana Spencer (F) father: Edward Spencer (M) father: Charles Windsor (M) mother: Elizabeth Windsor (F) mother: Elizabeth Bowes-Lyon (F) father: George Windsor (M) father: Philip Mountbatten (M) ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 4
Συνδυασμός Σύνθεσης και Κληρονομικότητας Είναι συνηθισμένο να χρησιμοποιούνται σύνθεση και κληρονομικότητα μαζί. Το παρακάτω παράδειγμα δείχνει την δημιουργία μιας πιο πολύπλοκης κλάσης χρησιμοποιώντας μαζί κληρονομικότητα και σύνθεση, μαζί με την κατάλληλη αρχικοποίηση των δομητών. Παράδειγμα class Plate { Plate(int i) { System.out.println("Plate constructor: "+i); class DinnerPlate extends Plate { DinnerPlate(int i) { super(i); System.out.println("DinnerPlate constructor: "+i); class Utensil { Utensil(int i) { System.out.println("Utensil constructor: "+i); class Spoon extends Utensil { Spoon(int i) { super(i); System.out.println("Spoon constructor: "+i); class Fork extends Utensil { Fork(int i) { super(i); System.out.println("Fork constructor: "+i); class Knife extends Utensil { Knife(int i) { super(i); System.out.println("Knife constructor: "+i); ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 5
class Custom { Custom(int i) { System.out.println("Custom constructor: "+i); public class PlaceSetting extends Custom { Spoon sp; Fork frk; Knife kn; DinnerPlate pl; PlaceSetting(int i) { super(i + 1); sp = new Spoon(i + 2); frk = new Fork(i + 3); kn = new Knife(i + 4); pl = new DinnerPlate(i + 5); System.out.println("PlaceSetting constructor"); public static void main(string[] args) { PlaceSetting x = new PlaceSetting(9); ΕΞΟ ΟΣ Custom constructor: 10 Utensil constructor: 11 Spoon constructor: 11 Utensil constructor: 12 Fork constructor: 12 Utensil constructor: 13 Knife constructor: 13 Plate constructor: 14 DinnerPlate constructor: 14 PlaceSetting constructor ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 6
Απόκρυψη ονόματος Εάν μία υπερκλάση έχει μία μέθοδο η οποία υπερφορτώνεται αρκετές φορές, τότε εάν σε μία υποκλάση ορίσουμε μία μέθοδο με το ίδιο όνομα, αυτή δεν αποκρύπτει τις παραλλαγές της υπερκλάσης. Έτσι η υπερφόρτωση λειτουργεί ανεξάρτητα από το εάν η μέθοδος ορίστηκε σε αυτό το επίπεδο ή στο προηγούμενο (υπερκλάση). class Homer { char doh(char c) { System.out.println("doh(char)"); return 'd'; float doh(float f) { System.out.println("doh(float)"); return 1.0f; class Milhouse { class Bart extends Homer { void doh(milhouse m) { class Hide { public static void main(string[] args) { Bart b = new Bart(); b.doh(1); b.doh('x'); b.doh(1.0f); b.doh(new Milhouse()); ΕΞΟ ΟΣ: doh(float) doh(char) doh(float) ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 7
Σύνθεση ή Κληρονομικότητα; Τόσο η σύνθεση όσο και η κληρονομικότητα μας επιτρέπουν να ορίσουμε νέες κλάσεις με την επαναχρησιμοποίηση άλλως κλάσεων τις οποίες έχουμε έτοιμες και είμαστε σίγουροι για την εύρυθμη λειτουργία τους. Η σύνθεση γενικά χρησιμοποιείται όταν θέλουμε τα χαρακτηριστικά μιας υπάρχουσας κλάσης μέσα σε μία νέα κλάση, αλλά δεν θέλουμε την διασύνδεσή της. Δηλαδή ενσωματώνουμε ένα μέλος-αντικείμενο το οποίο μπορούμε να χρησιμοποιούμε για να υλοποιήσουμε την λειτουργία της νέας μας κλάσης, αλλά ο χρήστης της νέας κλάσης βλέπει τη διασύνδεση που ορίζεται για τη νέα κλάση παρά τη διασύνδεση του ενσωματωμένου αντικειμένου. Για να έχουμε αυτό το αποτέλεσμα ενσωματώνουμε private μέλη της υπάρχουσας κλάσης μέσα στη νέα κλάση. Μερικές φορές χρειάζεται να υπάρχει άμεση πρόσβαση του χρήστη στη σύνθεση της νέας κλάσης δηλ. να κάνουμε τα μέλη public, έτσι ώστε να δίνεται άμεση πρόσβαση στα αντικείμενα που θα προκύψουν. Για αυτό το λόγο, σημαντικό είναι τα ίδια τα αντικείμενα-μέλη να φροντίζουν για την απόκρυψη της υλοποίησής τους, έτσι ώστε να είναι ασφαλής η δημόσια δήλωσή τους. Η διασύνδεση είναι περισσότερο κατανοητή όταν ο χρήστης γνωρίζει ότι η νέα κλάση συναρμολογείται ένα σύνολο συστατικών μερών. Κληρονομικότητα σημαίνει εξειδίκευση. Μία παραγόμενη κλάση (subclass/derived class) εξειδικεύεται κληρονομώντας όλα τα μέλη (χαρακτηριστικά και μεθόδους) της βασικής κλάσης (superclass/base class/parent class). Τα επιπλέον μέλη κάνουν την παραγόμενη κλάση πιο περιορισμένη, πιο ειδική. Το σύνολο των αντικειμένων μιας παραγόμενης κλάσης είναι ένα υποσύνολο των αντικειμένων της βασικής κλάσης. Για παράδειγμα, το σύνολο όλων των φοιτητών είναι ένα υποσύνολο του συνόλου των ανθρώπων. Σημειώνεται ότι το πιο σημαντικό κριτήριο για να κληρονομεί μία κλάση Β (υποκλάση/ παραγόμενη) μία άλλη κλάση Α, είναι, η κλάση Β να περιλαμβάνει όλα τα μέλη της κλάσης Α. Αυτό σημαίνει ότι το σύνολο των μελών της παραγόμενης κλάσης είναι υποσύνολο του συνόλου των μελών της βασικής κλάσης! Αν αυτό σας φαίνεται αντιφατικό, σκεφτείτε ότι η εξειδίκευση σε ένα μικρότερο σύνολο είναι αποτέλεσμα περισσότερων κριτηρίων, αφού τα κριτήρια είναι περιορισμοί και οι περισσότεροι περιορισμοί οδηγούν σε μικρότερα σύνολα. Κληρονομικότητα σημαίνει εξειδίκευση, ενώ σύνθεση σημαίνει ενοποίηση/ενσωμάτωση. Ακολουθώντας ένα από τα προηγούμενα παραδείγματα, μία κλάση "Student" που περιγράφει ένα φοιτητή θα είναι εξειδίκευση μίας κλάσης "Person" η οποία περιγράφει ένα άτομο, ενώ ενσωματώνει μία άλλη κλάση "Name" η οποία περιγράφει το όνομα ενός ατόμου. Συχνά χρησιμοποιούνται οι φράσεις "είναι ένα" (is a) και "έχει ένα" (has a) για να διακρίνουμε μεταξύ κληρονομικότητας και σύνθεσης αντιστοίχως. Ένας φοιτητής "είναι ένα" άτομο, ενώ ένας φοιτητής "έχει ένα" όνομα. Η κατανόηση της διάκρισης μεταξύ των σχέσεων "είναι ένα" και "έχει ένα" μπορεί να μας βοηθήσει να αποφύγουμε το λάθος ορισμού νέας παραγόμενης κλάσης (υποκλάσης) όταν δεν χρειάζεται. Αυτό το λάθος μπορεί να συμβεί όταν νομίσουμε ότι κληρονομικότητα σημαίνει μόνο πρόσθεση νέων μελών στην κλάση. ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 8
Η σύνθεση είναι αρκετά ευέλικτη. Τα αντικείμενα μέλη της νέας κλάσης είναι συνήθως ιδιωτικά και έτσι δεν έχουν σε αυτά πρόσβαση οι προγραμματιστές που χρησιμοποιούν αυτή την κλάση. Αυτό μας επιτρέπει να μεταβάλουμε αυτά τα μέλη χωρίς να επηρεάσουμε τον υπόλοιπο κώδικα του προγράμματος. Μπορούμε επίσης να αλλάξουμε τα μέλη-αντικείμενα κατά την διάρκεια της εκτέλεσης του προγράμματος, δηλαδή να μεταβάλουμε δυναμικά την συμπεριφορά του προγράμματος. Η κληρονομικότητα δεν έχει αυτή την ευελιξία αφού ο compiler πρέπει να θέσει κάποιους περιορισμούς, κατά το στάδιο της μεταγλώττισης, στις κλάσεις που δημιουργήθηκαν με κληρονομικότητα. Η κληρονομικότητα είναι μεν πολύ σημαντική αλλά συχνά, για εκπαιδευτικούς λόγους, της δίνεται περισσότερη έμφαση από όση πρέπει, και οι νέοι προγραμματιστές δημιουργούν την λανθασμένη αντίληψη ότι πρέπει να την χρησιμοποιούν παντού. Αντιθέτως όταν δημιουργούμε νέες κλάσεις, θα πρέπει πρώτα να δούμε την δυνατότητα χρήσης σύνθεσης, η οποία είναι απλούστερη και πιο ευέλικτη. Αφού αποκτήσουμε κάποια εμπειρία θα είναι σχετικά εύκολο να κρίνουμε εάν χρειάζεται κληρονομικότητα ή σύνθεση. ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 9
Παράδειγμα: Σύνθεση με public μέλη-αντικείμενα class Engine { public void start() { public void rev() { public void stop() { class Wheel { public void inflate(int psi) { class Window { public void rollup() { public void rolldown() { class Door { public Window window = new Window(); public void open() { public void close() { public class Car { public Engine engine = new Engine(); public Wheel[] wheel = new Wheel[4]; public Door left = new Door(), right = new Door(); // 2-door public Car() { for(int i = 0; i < 4; i++) wheel[i] = new Wheel(); public static void main(string[] args) { Car car = new Car(); car.left.window.rollup(); car.wheel[0].inflate(72); Επειδή η σύνθεση ενός αυτοκινήτου είναι μέρος της ανάλυσης του προβλήματος (και όχι απλά μέρος της σχεδίασης) κάνοντας τα μέλη public βοηθάει τον προγραμματιστή να κατανοήσει πώς να χρησιμοποιήσει την κλάση και απαιτεί μικρότερη πολυπλοκότητα του κώδικα της κλάσης. Να θυμάστε όμως ότι αυτή είναι μια ειδική περίπτωση και ότι γενικά θα πρέπει να δηλώνετε τα πεδία/χαρακτηριστικά ως private. Όταν κληρονομείτε, παίρνετε μία υπάρχουσα κλάση και δημιουργείτε μία ειδική έκδοσή της. Γενικά, αυτό σημαίνει ότι παίρνετε μία κλάση γενικού σκοπού και την εξειδικεύετε σε μία συγκεκριμένη περίπτωση. Για παράδειγμα δεν έχει νόημα να συνθέσετε ένα αυτοκίνητο χρησιμοποιώντας ένα αντικείμενο τύπου όχημα. Το αυτοκίνητο δεν περιέχει ένα όχημα αλλά είναι ένα όχημα. ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 10
Υπέρβαση μεθόδων (Method overriding) Όταν η παραγόμενη κλάση ορίζει μία μέθοδο με το ίδιο όνομα και υπογραφή όπως της υπερκλάσης, τότε λέμε ότι η νέα μέθοδος που ορίζεται στην παραγόμενη κλάση (υποκλάση) υπερβαίνει (overrides) την έκδοση της υπερκλάσης. Το παρακάτω παράδειγμα ορίζει μία κλάση με όνομα Thought η οποία κληρονομείται από την κλάση Αdvice. Και οι δύο έχουν την μέθοδο με όνομα message. Παράδειγμα class Thought { public void message() { System.out.println("Living in a parallel world with Java"); class Advice extends Thought { public void message() { System.out.println("What about changing your field of studies"); class Messages { public static void main (String[] args) { Thought parked = new Thought(); Advice dates = new Advice(); parked.message(); dates.message(); ΕΞΟ ΟΣ: Living in a parallel world with Java What about changing your field of studies ΑΤΕΙ Θεσσαλονίκης Τμήμα Πληροφορικής Σελίδα 11