User Tools

Site Tools


java:interfaces

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
java:interfaces [2017/02/03 09:17] – [Εισαγωγικά] gthanosjava:interfaces [2017/02/03 09:51] (current) – removed gthanos
Line 1: Line 1:
-====== Διεπαφές (Interfaces) ====== 
- 
-===== Εισαγωγικά ===== 
- 
-Κατά την ανάπτυξη προγραμμάτων είναι σημαντικό να είναι τυποποιημένος ο τρόπος αλληλεπίδρασης ανάμεσα σε διαφορετικά συστήματα. Η ύπαρξη ενός "συμβολαίου" το οποίο καθορίζει ακριβώς το πώς μπορεί μία κλάση να αλληλεπιδράσει με μια άλλη διευκολύνει την ανεξάρτητη ανάπτυξη κώδικα από διαφορετικούς προγραμματιστές. 
- 
-Υπακούοντας στην παραπάνω αρχή, η Java εισάγει την έννοια της διεπαφής (**interface**). Τα //interfaces// στη Java λειτουργούν ως προ-συμφωνημένες διεπαφές μεταξύ προγραμματιστών. Κάθε interface τυπικά σχετίζεται με: 
-  - **τους προγραμματιστές που το χρησιμοποιούν:** οι προγραμματιστές γράφουν την εφαρμογή τους ώστε να χρησιμοποιεί τις μεθόδους του //interface//, οι οποίες παρέχουν τη λειτουργικότητα που υποδεικνύει το //interface// αυτό. 
-  - **τους προγραμματιστές που το υλοποιούν:** εφόσον θέλουν να ενσωματώσουν τη λειτουργικότητα που υποδεικνύει το //interface// σε συγκεκριμένες κλάσεις θα πρέπει να υλοποιήσουν τις μεθόδους του στις  κλάσεις αυτές. 
- 
-Από τα παραπάνω εξάγεται ότι το //interface// είναι μία προσυμφωνημένη διεπαφή η οποία δεσμεύει και τις δύο πλευρές, τόσο τους προγραμματιστές που το υλοποιούν, όσο και εκείνους που το χρησιμοποιούν. Παρακάτω θα δούμε την υποστήριξη των interfaces από τον compiler της Java και τους κανόνες που τα διέπουν. 
- 
-Ως παράδειγμα, θεωρήστε ένα //interface// το οποίο ορίζει τη χρήση ενός χρονόμετρου (//Timer//). Όσοι επιθυμούν να υλοποιήσουν το χρονόμετρο θα πρέπει κατ' ελάχιστο να υλοποιήσουν τις παρακάτω μεθόδους: 
-  * **setTimer:** αρχικοποίηση του timer  
-  * **startTimer:** έναρξη του timer και  
-  * **endTimer:** τερματισμός του timer 
- 
-Ας υποθέσουμε τώρα ότι ένας κατασκευαστής ηλεκτρικών συσκευών (κλιμαστιστικά, ψυγεία κ.α.) χρησιμοποιεί Java για τον αυτοματισμό τους και θέλει να χρησιμοποιεί μία κλάση τύπου //Timer// για τις συσκευές που κατασκευάζει προκειμένου να ενσωματώσει τη λειτουργία του χρονόμετρου. Για να το κάνει αυτό, μπορεί να χρησιμοποιήσει __οποιαδήποτε κλάση είναι σύμφωνη με τον παραπάνω //interface//__. Ας υποθέσουμε επίσης ότι υπάρχουν τουλάχιστον δύο διαφορετικές κλάσεις που υλοποιούν το συγκεκριμένο interface //Timer// 
- 
-Γνωρίζοντας ότι όλες οι εναλλακτικές κλάσεις που θα χρησιμοποιήσει θα υλοποιούν υποχρεωτικά το συγκεκριμένο //interface//, αρκεί να σχεδιάσει και να υλοποιήσει το λογισμικό του του ώστε να χρησιμοποιεί το //interface// αυτό. Σε αυτή την περίπτωση, σχεδιάζει και γράφει το λογισμικό με γνώμονα τον interface και όχι τη συγκεκριμένη κλάση που θα επιλέξει να χρησιμοποιήσει τελικά. 
- 
-Υιοθετώντας την έννοια του //interface// και γράφοντας κώδικα που χρησιμοποιεί //interfaces//, γράφουμε κώδικα που είναι ανεξάρτητος από την οποιαδήποτε υλοποίηση των //interfaces// που χρησιμοποιεί. Γενικότερα, ισχύει η αρχή ότι οι υλοποιήσεις ενός interface μπορεί να αλλάξουν, τα interfaces όμως συνιστάται να παραμένουν σταθερά. 
- 
-Οποιοσδήποτε προγραμματιστής χρησιμοποιεί το παραπάνω //interface// δεν χρειάζεται να γνωρίζει το παραμικρό για την εσωτερική υλοποίηση των κλάσεων που το υλοποιούν. Η επιλογή της κλάσης που το υλοποιεί μπορεί να μεταβάλλεται χωρίς να επηρεάζεται ο κώδικας που το χρησιμοποιεί. 
- 
-Αντιπαραθέστε τώρα την εναλλακτική μη ύπαρξης προσυμφωνημένων διεπαφών. Σε αυτή την περίπτωση κάθε πρόγραμμα ή ο κάθε κατασκευαστής θα έπρεπε να υλοποιεί την δική του κλάση για το χρονόμετρο, χωρίς να υπάρχει η δυνατότητα εύκολης χρήσης λογισμικού που προέρχεται από τρίτους.  
- 
-Η προτυποποίηση που παρέχει ο μηχανισμός του //interface// είναι αυτή που επιτρέπει: 
-  - την εύκολη και χωρίς σφάλματα επαναχρησιμοποίηση κώδικα σε διαφορετικά προγράμματα και  
-  - την αλλαγή της υφιστάμενης υλοποίησης μιας κλάσης που υλοποιεί ένα //interface//, δίχως οι χρήστες του //interface// να έχουν γνώση για τις αλλαγές αυτές. 
- 
-===== Ορίζοντας ένα Interface ===== 
- 
-Παρακάτω δίνεται το παράδειγμα ορισμού ενός Interface ως εξής: 
- 
-<code java> 
-public interface MyInterface extends Interface1, Interface2, Interface3 { 
- 
-    // constant declarations     
-    // base of natural logarithms 
-    double E = 2.718282; 
-    double PI = 3.14159; 
-  
-    // method signatures 
-    public void interfaceMethod(int i, double x); 
-    public int interfaceMethod2(String s); 
-} 
-</code> 
- 
-  * **public ή κανένας προσδιοριστής πρόσβασης:** Αν οριστεί //public// τότε το interface είναι ορατό από όλες τις κλάσεις και όλα τα πακέτα στην Java. Αν δεν οριστεί κάτι (package private) τότε το interface είναι ορατό μόνο μέσα στο πακέτο στο οποίο δηλώνεται. 
-  * **interface:** δεσμευμένη λέξη 
-  * **το όνομα του interface:** στο παράδειγμα μας ''MyInterface''. 
-  * **extends:** ένα ή περισσότερα interfaces τα οποία επεκτείνει το συγκεκριμένο interface (comma seperated).  
- 
-Τόσο στις κλάσεις όσο και στα interfaces ορίζεται η ιδιότητα της κληρονομικότητας. __Η διαφορά είναι ότι ενώ στην κλάση μπορούμε να έχουμε μόνο μία γονική κλάση, στα interfaces μπορούμε να έχουμε περισσότερα του ενός γονικά interfaces__. 
- 
-<WRAP tip 80% center round> 
-Ακριβώς όπως και οι κλάσεις κάθε interface πρέπει να βρίσκεται σε ξεχωριστό αρχείο, όπου το όνομα του αρχείου ταυτίζεται με το όνομα του interface και έχει κατάληξη **.java**. Για παράδειγμα το interface με όνομα **MyInterface** θα βρίσκεται σε ένα αρχείο με όνομα **MyInterface.java**. 
-</WRAP> 
- 
-==== Το σώμα του Interface ==== 
- 
-Ένα //interface// μπορεί να περιέχει πεδία όπως στο παραπάνω παράδειγμα. 
- 
-<code java> 
-double E = 2.718282; 
-double PI = 3.14159; 
-</code> 
- 
-Τα πεδία αυτά εξ ορισμού (by default) ''public, static, final'', δηλ είναι **__σταθερές__** που ανήκουν στις κλάσεις που θα υλοποιήσουν το interface στο οποίο δηλώνονται. 
- 
-Οι μέθοδοι σε ένα interface κατά κανόνα είναι **abstract**, δηλαδή περιέχεται μόνο η δήλωση της μεθόδου χωρίς σώμα (δηλ. υλοποίηση). Επίσης, όλες οι μέθοδοι σε ένα interface είναι εξ ορισμού //public//, κατά συνέπεια ο προσδιορισστής //public// μπορεί να παραληφθεί δηλ 
- 
-<code java> 
-public interface MyInterface extends Interface1, Interface2, Interface3 { 
- 
-    // constant declarations     
-    // base of natural logarithms 
-    double E = 2.718282; 
-    double PI = 3.14159; 
-  
-    // method signatures 
-    public void interfaceMethod1(int i, double x); 
-    public int interfaceMethod2(String s); 
-} 
-</code> 
- 
-===== Παράδειγμα interface - H Στοίβα (Stack) ===== 
- 
-Ας υποθέσουμε ότι θέλουμε να δηλώσουμε μέσω ενός interface της μεθόδους που πρέπει να υποστηρίζει μία στοίβα. Η στοίβα είναι μία δομή αποθήκευσης δεδομένων τύπου **Last In First Out (LIFO)**, όπου το αντικείμενο αποθηκεύεται στη στοίβα τελευταίο εξάγεται από αυτή πρώτο. Κάθε στοίβα θα πρέπει να υποστηρίζει κατ' ελάχιστο τα εξής: 
-  * μέθοδο επιστροφής του μεγέθους της στοίβας. 
-  * μέθοδο ένθεσης στην κορυφή της στοίβας. 
-  * μέθοδο απόσβεσης του κορυφαίου στοιχείου. 
-  * μέθοδο επιστροφής του κορυφαίου στοιχείου της στοίβας χωρίς διαγραφή από τη στοίβα. 
- 
-Παρακάτω δηλώνεται το //interface Stack// που υποστηρίζει αυτές τις λειτουργίες 
-<code java Stack.java> 
-public interface Stack { 
-  public int size();           // επιστροφή του μεγέθους της στοίβας. 
-  public void push(Object o);  // ένθεση στην κορυφή της στοίβας. 
-  public Object pop();         // απόσβεση από την κορυφή της στοίβας. 
-  public Object top();         // επιστροφή του κορυφαίου στοιχείου της στοίβας χωρίς διαγραφή του. 
-} 
-</code> 
- 
-===== Υλοποιώντας το Interface ===== 
- 
-Μία κλάση υλοποιεί ένα interface μόνο εάν υλοποιεί ΟΛΕΣ τις μεθόδους του interface. Έάν έστω και μία μέθοδος δεν υλοποιείται τότε η κλάση δεν υλοποιεί το interface.  
- 
-Σε συνέχεια του προηγούμενου παραδείγματος θα επιχειρήσουμε να δημιουργήσουμε δύο διαφορετικές κλάσεις που υλοποιούν το συγκεκριμένο interface. Οι κλάσεις αυτές είναι η ArrayStack που υλοποιεί τη στοίβα μέσω ενός πίνακα και η LinkedStack που υλοποιεί τη στοίβα διασυνδέοντας επιμέρους αντικείμενα μεταξύ τους σε αναλογία με μία διασυνδεδεμένη λίστα. 
- 
-<code java ArrayStack.java> 
-public class ArrayStack implements Stack { 
-  public int capacity; 
-  public Object []array; 
-  public int size; 
-   
-  public ArrayStack() { 
-    this(256); 
-  } 
-   
-  public ArrayStack(int capacity) { 
-    array = new Object[capacity]; 
-    this.capacity = capacity; 
-    size = 0; 
-  } 
-   
-  public int size() { 
-    return size; 
-  } 
-   
-  public void push(Object o) { 
-   
-    if( size >= capacity-1 ) { 
-      capacity *= 2; 
-      Object []newArray = new Object[capacity]; 
-      for(int i=0; i<array.length; i++) 
-        newArray[i] = array[i]; 
-      array = newArray; 
-      newArray = null; 
-    } 
-    array[size++] = o; 
-  } 
-   
-  public Object pop() { 
-    return array[--size]; 
-  } 
-   
-  public Object top() { 
-    return array[size-1]; 
-  } 
-   
-  public String toString() { 
-    String str = "@@@@@@@@ - Stack - @@@@@@@@\n"; 
-    for(int i=size-1; i>=0; i--) 
-      str += array[i].toString()+"\n"; 
-    return str + "@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; 
-  }   
-} 
-</code> 
- 
-<code java LinkedStack.java> 
- 
-class LinkedNode { 
-  private Object o; 
-  private LinkedNode next; 
-   
-  public LinkedNode(LinkedNode nxt, Object e) { 
-    next = nxt; 
-    o = e; 
-  } 
-   
-  public LinkedNode(Object e) { 
-    this(null, e); 
-  } 
-   
-  public Object getElement() { return o; } 
-  public LinkedNode getNext() { return next; } 
-   
-  public void setElement(Object e) { o = e; } 
-  public void setNext(LinkedNode node) { next = node; } 
-} 
- 
-public class LinkedStack implements Stack { 
-  int size; 
-  LinkedNode head; 
- 
-  public LinkedStack() { 
-    size=0; 
-    head = null; 
-  } 
-   
-  public int size() { 
-    return size; 
-  } 
-   
-  public void push(Object o) { 
-    // the following is OK even if head == null. 
-    head = new LinkedNode(head, o); 
-    size++; 
-  } 
-   
-  public Object pop() { 
-    LinkedNode pN = head; 
-    head = head.getNext(); 
-    size--; 
-    return pN.getElement(); 
-  } 
-   
-  public Object top() { 
-    return head; 
-  } 
-   
-  public String toString() { 
-    String str = "@@@@@@@@ - Stack - @@@@@@@@\n"; 
-    LinkedNode curr = head; 
-    while(curr != null) 
-      str += curr.getElement().toString(); 
-    return str + "@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; 
-  } 
-} 
-</code> 
- 
-Παρατηρούμε δηλαδή ότι έχουμε υλοποιήσει το συγκεκριμένο interface μέσα από δύο διαφορετικές κλάσεις. Αν και η υλοποίηση των κλάσεων διαφέρει σημαντικά, η λειτουργία τους είναι κοινή, δηλαδή μπορείτε να χρησιμοποιήσετε οποιαδήποτε από της δύο κλάσεις για να εξυπηρετήσετε την λειτουργία της στοίβας σε ένα πρόγραμμα. 
- 
- 
-===== Χρησιμοποιώντας ένα interface ως τύπο δεδομένων ===== 
- 
-Ας υποθέσουμε τώρα ότι θέλουμε να χρησιμοποιήσουμε μία στοίβα για να αντιμεταθέσουμε τα στοιχεία ενός πίνακα χαρακτήρων. Ο πίνακας αρχικά περιέχει την αγγλική αλφαβήτα (26 χαρακτήρες) και θέλουμε να αντιστρέψουμε την σειρά με την οποία αποθηκεύονται οι χαρακτήρες στον πίνακα. Η μέθοδος invertArray παρακάτω αντιμεταθέτει τα στοιχεία του πίνακα array με χρήση της στοίβας stk. 
- 
-<code java ArrayManipulator.java> 
-public class ArrayManipulator { 
-  public static void main(String []args) { 
-    Character alphabet[] = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'}; 
-    printCharacterArray(alphabet); 
-    System.out.println("-----------"); 
-     
-    Stack stack = new ArrayStack(); 
-    invertArray(alphabet, stack); 
-     
-    printCharacterArray(alphabet); 
-  } 
-   
-  public static void invertArray(Object []array, Stack stk) { 
-    for(int i=0; i<array.length; i++) 
-      stk.push(array[i]); 
-    for(int i=0; i<array.length; i++) 
-      array[i] = stk.pop(); 
-  } 
-   
-  public static void printCharacterArray(Character []array) { 
-    for(Character c : array) 
-      System.out.print(c+"   "); 
-    System.out.println(""); 
-  } 
-} 
-</code> 
- 
-<WRAP important 80% center round> 
-Παρατηρήστε ότι η μέθοδος //invertArray// παίρνει ως 2ο όρισμα όχι το αντικείμενο μίας κλάσης αλλά ένα αντικείμενο τύπου interface //Stack//! Στην πραγματικότητα αυτό που δηλώνει η συγκεκριμένη μέθοδος είναι ότι ως 2ο όρισμα παίρνει ένα αντικείμενο του οποίου η κλάση υλοποιεί το interface //Stack//. Επίσης, δηλώνει ότι από όλες τις διαθέσιμες μεθόδους της κλάσης μπορούν να χρησιμοποιηθούν μόνο οι μέθοδοι που δηλώνονται στο interface. 
-</WRAP> 
- 
-<WRAP todo 80% center round> 
-Στο παραπάνω πρόγραμμα, αλλάξτε την γραμμή 
-<code java> 
-Stack stack = new ArrayStack(); 
-</code> 
-σε 
-<code java> 
-Stack stack = new LinkedStack(); 
-</code> 
- 
-Παρατηρήστε αν σας βγάζει κάποιο μήνυμα ο //compiler// και εάν εκτελώντας το πρόγραμμα παράγεται το ίδιο τελικό αποτέλεσμα. Τι συμπέρασμα βγάζετε για τη χρήση της στοίβας από την μέθοδο //invertArray()//; 
-</WRAP> 
- 
-===== Μεταβάλλοντας ένα υφιστάμενο Interface ===== 
- 
-Κάποιες φορές στον προγραμματισμό εμφανίζεται η ανάγκη να μεταβάλλουμε ένα υφιστάμενο interface ή να το επεκτείνουμε. Το πρόβλημα σε αυτές τις περιπτώσεις είναι ότι οι κλάσεις που υλοποιούν το συγκεκριμένο interface με την προσθήκη ή αλλαγή των υφιστάμενων μεθόδων θα πάψουν να το υλοποιούν. Επομένως μία μονομερής αλλαγή του interface δεν είναι εφικτή. Παρόλα αυτά υπάρχουν δύο εναλλακτικές που μπορούν να μας καλύψουν. 
- 
-  
-**1η εναλλακτική:** Να υλοποιήσουμε ένα νέο interface με παραπλήσιο όνομα, το οποίο να αποτελέσει τη νέα έκδοση του υφιστάμενου interface. 
-<code java> 
-public interface DoIt { 
-  
-   void doSomething(int i, double x); 
-   int doSomethingElse(String s); 
-} 
- 
-public interface DoItPlus extends DoIt { 
- 
-   boolean didItWork(int i, double x, String s); 
-    
-} 
-</code> 
- 
-**2η εναλλακτική:** Να υλοποιήσουμε μία ή περισσότερες //default// μέθοδους (όσες είναι και οι αλλαγές ή οι προσθήκες) μέσα στο υφιστάμενο interface, ώστε ακόμη και εάν οι κλάσεις που υλοποιούν το συγκεκριμένο interface δεν υλοποιούν τις νέες μεθόδους, οι υλοποιήσεις στις κλάσεις αυτές να γίνονται από το ίδιο το interface. 
-<code java> 
-public interface DoIt { 
- 
-   void doSomething(int i, double x); 
-   int doSomethingElse(String s); 
-   default boolean didItWork(int i, double x, String s) { 
-       // Method body  
-   }   
-} 
-</code> 
- 
-===== Default μέθοδοι ===== 
- 
-<WRAP 80% important center round> 
-**__Παρατήρηση:__** Οι **default** μέθοδοι στην Java υλοποιούνται στην έκδοση 8 της γλώσσας. Μεταγλωττιστές που ανήκουν σε προηγούμενες εκδόσεις αδυνατούν να μεταγλωττίσουν τον παρακάτω κώδικά. 
-</WRAP> 
- 
-Ας θεωρήσουμε το παρακάτω υποθετικό interface 
- 
-<code java StringInverter.java> 
-    public interface StringInverter { 
-        /** inverts a string 
-         */ 
-        String invert(String str); 
-    } 
-</code> 
- 
-Ας υποθέσουμε τώρα ότι η κλάση MyString υλοποιεί το παραπάνω interface. 
- 
-<code java MyString.java> 
-import java.lang.*; 
-public class MyString implements StringInverter { 
-    String invert(String str) { 
-        StringBuffer strb = new StringBuffer(str); 
-        return strb.reverse().toString(); 
-    } 
-} 
-</code> 
- 
-Αν υποθέσουμε ότι το αρχικό interface αλλάζει στο παρακάτω 
- 
-<code java StringInverter.java> 
-    public interface StringInverter { 
-        /** inverts a string 
-         */ 
-        String invert(String str); 
-        /** splits a string in half and inverts 
-         * the two parts. 
-         */ 
-        String invertHalf(String str); 
-    } 
-</code> 
- 
-Τώρα η κλάση MyString δεν υλοποιεί πλέον το αρχικό interface. Αυτό μπορεί να συνεχίσει να υλοποιείται εάν η νέα μέθοδος έχει μία default υλοποίηση μέσα στο interface όπως παρακάτω. 
- 
-<code java StringInverter.java> 
-    public interface StringInverter { 
-        /* inverts a string 
-         */ 
-        String invert(String str); 
-        /* splits a string in half and inverts 
-         * the two parts 
-         */ 
-        default String invertHalf(String str) { 
-           int len = str.length(); 
-           String str1 = str.substring(0, len/2); 
-           String str2 = str.substring(len/2, len); 
-           return str2+str1; 
-        } 
-    } 
-</code> 
- 
- 
- 
-===== Στατικές μέθοδοι ===== 
- 
-Εκτός από //default// μεθόδους ένα interface μπορεί να περιέχει και στατικές (//static//) μεθόδους. Οι στατικές μέθοδοι ορίζονται σε αναλογία με τις στατικές μεθόδους των κλάσεων και ουσιαστικά ανήκουν στις κλάσεις που θα υλοποιήσουν το interface και όχι στα αντικείμενα. Ένα παράδειγμα μπορείτε να δείτε παρακάτω. 
- 
-<code java StringInverter.java> 
-    public interface StringInverter { 
-        /** inverts a string 
-         */ 
-        String invert(String str); 
-        /** splits a string in half and inverts 
-         * the two parts 
-         */ 
-        default String invertHalf(String str) { 
-           int len = str.length(); 
-           String str1 = str.substring(0, len/2); 
-           String str2 = str.substring(len/2, len); 
-           return str2+str1; 
-        } 
-         
-        /** removes character at index from String str 
-         * and returns the new String. 
-         */ 
-        static String removeChar(String str, int index) { 
-            if( str.length()-1 < index ) { 
-                return str; 
-            } 
-            String str1 = str.substring(0,index); 
-            String str2; 
-            if( str.length() >= index-1 ) { 
-                str2 = new String(""); 
-            } 
-            else { 
-                str2 = str.substring(index+1); 
-            } 
-            return str1+str2; 
-         
-        } 
-    } 
-</code> 
- 
-<WRAP 80% important center round> 
-**__Παρατήρηση:__** Οι **static** μέθοδοι στην Java υλοποιούνται στην έκδοση 8 της γλώσσας. Μεταγλωττιστές που ανήκουν σε προηγούμενες εκδόσεις αδυνατούν να μεταγλωττίσουν τον παραπάνω κώδικά. 
-</WRAP> 
- 
- 
-| Προηγούμενο : [[ :java:ant | Αυτόματη μεταγλώττιση με χρήση Apache Ant ]] | [[ :toc | Περιεχόμενα ]] | Επόμενο: [[ :java:access_modifiers | Περιοριστές πρόσβασης ]]  | 
- 
- 
  
java/interfaces.1486113445.txt.gz · Last modified: 2017/02/03 09:17 (external edit)