Προγραμματισμός

Μια εσωτερική άποψη του Observer

Πριν από λίγο καιρό το συμπλέκτη μου έδωσε, έτσι είχα το τζιπ μου να ρυμουλκηθεί σε έναν τοπικό αντιπρόσωπο. Δεν ήξερα κανέναν στην αντιπροσωπεία, και κανένας από αυτούς δεν με γνώριζε, γι 'αυτό τους έδωσα τον αριθμό τηλεφώνου μου, ώστε να μπορούν να με ειδοποιούν με μια εκτίμηση. Αυτή η ρύθμιση λειτούργησε τόσο καλά, κάναμε το ίδιο πράγμα όταν τελείωσε η δουλειά. Επειδή όλα αυτά αποδείχθηκαν τέλεια για μένα, υποψιάζομαι ότι το τμήμα εξυπηρέτησης του αντιπροσώπου χρησιμοποιεί το ίδιο μοτίβο με τους περισσότερους πελάτες του.

Αυτό το μοτίβο δημοσίευσης-εγγραφής, όπου παρατηρητής καταχωρεί με ένα θέμα και στη συνέχεια λαμβάνει ειδοποιήσεις, είναι αρκετά κοινό, τόσο στην καθημερινή ζωή όσο και στον εικονικό κόσμο της ανάπτυξης λογισμικού. Στην πραγματικότητα, το Παρατηρητής Το μοτίβο, όπως είναι γνωστό, είναι ένας από τους άξονες της αντικειμενοστρεφούς ανάπτυξης λογισμικού, διότι επιτρέπει σε διαφορετικά αντικείμενα να επικοινωνούν. Αυτή η ικανότητα σάς επιτρέπει να συνδέετε αντικείμενα σε ένα πλαίσιο κατά το χρόνο εκτέλεσης, το οποίο επιτρέπει εξαιρετικά ευέλικτο, επεκτάσιμο και επαναχρησιμοποιήσιμο λογισμικό.

Σημείωση: Μπορείτε να κατεβάσετε τον πηγαίο κώδικα αυτού του άρθρου από τους πόρους.

Το μοτίβο Παρατηρητής

Σε Σχεδιαστικά πρότυπα, οι συγγραφείς περιγράφουν το μοτίβο Observer ως εξής:

Ορίστε την εξάρτηση από ένα προς πολλά μεταξύ αντικειμένων, έτσι ώστε όταν ένα αντικείμενο αλλάζει κατάσταση, όλα τα εξαρτώμενα μέλη του ειδοποιούνται και ενημερώνονται αυτόματα.

Το σχέδιο Observer έχει ένα θέμα και ενδεχομένως πολλούς παρατηρητές. Οι παρατηρητές εγγράφονται στο θέμα, το οποίο ειδοποιεί τους παρατηρητές όταν συμβαίνουν γεγονότα. Το πρωτότυπο παράδειγμα του Observer είναι μια γραφική διεπαφή χρήστη (GUI) που εμφανίζει ταυτόχρονα δύο προβολές ενός μόνο μοντέλου. οι προβολές καταχωρούνται στο μοντέλο και όταν το μοντέλο αλλάζει, ειδοποιεί τις προβολές, οι οποίες ενημερώνονται αναλόγως. Ας δούμε πώς λειτουργεί.

Παρατηρητές σε δράση

Η εφαρμογή που φαίνεται στο Σχήμα 1 περιέχει ένα μοντέλο και δύο προβολές. Η τιμή του μοντέλου, που αντιπροσωπεύει μεγέθυνση εικόνας, χειρίζεται μετακινώντας το ρυθμιστικό. Οι προβολές, γνωστές ως στοιχεία στο Swing, είναι μια ετικέτα που δείχνει την τιμή του μοντέλου και ένα πλαίσιο κύλισης που κλιμακώνει μια εικόνα σύμφωνα με την τιμή του μοντέλου.

Το μοντέλο στην εφαρμογή είναι μια παρουσία του DefaultBoundedRangeModel (), η οποία παρακολουθεί μια οριακή ακέραια τιμή - σε αυτήν την περίπτωση από 0 προς την 100—Με αυτές τις μεθόδους:

  • int getMaximum ()
  • int getMinimum ()
  • int getValue ()
  • boolean getValueIsAdjusting ()
  • int getExtent ()
  • άκυρο σετ Μέγιστο (int)
  • void setMinimum (int)
  • void setValue (int)
  • void setValueIsAdjusting (boolean)
  • void setExtent (int)
  • void setRangeProperties (τιμή int, int έκταση, int min, int max, boolean προσαρμογή)
  • void addChangeListener (ChangeListener)
  • void removeChangeListener (ChangeListener)

Όπως δείχνουν οι δύο τελευταίες μέθοδοι που αναφέρονται παραπάνω, περιπτώσεις DefaultBoundedRangeModel () υποστήριξη ακροατών αλλαγής. Το παράδειγμα 1 δείχνει πώς η εφαρμογή εκμεταλλεύεται αυτήν τη δυνατότητα:

Παράδειγμα 1. Δύο παρατηρητές αντιδρούν στις αλλαγές του μοντέλου

εισαγωγή javax.swing. *; εισαγωγή javax.swing.event. *; εισαγωγή java.awt. *; εισαγωγή java.awt.event. *; εισαγωγή java.util. *; δημόσια τάξη Test επεκτείνει JFrame { ιδιωτικό μοντέλο DefaultBoundedRangeModel = νέο DefaultBoundedRangeModel (100,0,0,100); ιδιωτικό JSlider slider = νέο JSlider (μοντέλο); ιδιωτικό JLabel readOut = νέο JLabel ("100%"); ιδιωτικό ImageIcon image = νέο ImageIcon ("shortcake.jpg"); ιδιωτικό ImageView imageView = νέο ImageView (εικόνα, μοντέλο); δημόσια δοκιμή () {super ("The Observer Design Pattern") · Container contentPane = getContentPane (); Πίνακας JPanel = νέο JPanel (); panel.add (νέα ετικέτα JL ("Ορισμός μεγέθους εικόνας:")); panel.add (ρυθμιστικό); panel.add (readOut); contentPane.add (πλαίσιο, BorderLayout.NORTH); contentPane.add (imageView, BorderLayout.CENTER); model.addChangeListener (νέο ReadOutSynchronizer ()); } δημόσιος στατικός κενός κενός (String args []) {Test test = new Test (); test.setBounds (100.100.400.350); test.show (); } Η κλάση ReadOutSynchronizer εφαρμόζει το ChangeListener {δημόσιο κενό κατάστασηΑλλαγή(ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}} Η κλάση ImageView επεκτείνει το JScrollPane {private JPanel panel = new JPanel (); ιδιωτική διάσταση originalSize = νέα διάσταση (); ιδιωτική εικόνα originalImage; ιδιωτικό εικονίδιο ImageIcon; δημόσιο ImageView (εικονίδιο ImageIcon, μοντέλο BoundedRangeModel) {panel.setLayout (νέο BorderLayout ()); panel.add (νέα ετικέτα JL (εικονίδιο)); this.icon = εικονίδιο; this.originalImage = icon.getImage (); setViewportView (πάνελ); model.addChangeListener (νέο ModelListener ()); originalSize.width = icon.getIconWidth (); originalSize.height = icon.getIconHeight (); } Η κλάση ModelListener εφαρμόζει το ChangeListener {δημόσιο κενό κατάστασηΑλλαγή(ChangeEvent e) {BoundedRangeModel model = (BoundedRangeModel)e.getSource (); if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, value = model.getValue (); διπλός πολλαπλασιαστής = (διπλή) τιμή / (διπλό) εύρος; πολλαπλασιαστής = πολλαπλασιαστής == 0,0; 0,01: πολλαπλασιαστής Image scaled = originalImage.getScaledInstance ((int) (originalSize.width * multiplier), (int) (originalSize.height * πολλαπλασιαστής), Image.SCALE_FAST); icon.setImage (κλιμάκωση); panel.revalidate (); panel.repaint (); }}}} 

Όταν μετακινείτε το ρυθμιστικό, το ρυθμιστικό αλλάζει την τιμή του μοντέλου του. Αυτή η αλλαγή ενεργοποιεί ειδοποιήσεις συμβάντων στους δύο ακροατές αλλαγών που είναι εγγεγραμμένοι στο μοντέλο, οι οποίοι προσαρμόζουν την ανάγνωση και κλιμακώνουν την εικόνα. Και οι δύο ακροατές χρησιμοποιούν το συμβάν αλλαγής

κατάστασηΑλλαγή ()

για να προσδιορίσετε τη νέα τιμή του μοντέλου.

Το Swing είναι ένας βαρύς χρήστης του μοτίβου Observer - εφαρμόζει περισσότερους από 50 ακροατές συμβάντων για την εφαρμογή συμπεριφοράς για συγκεκριμένες εφαρμογές, από την αντίδραση σε ένα πατημένο κουμπί έως το βέτο ενός γεγονότος κλεισίματος παραθύρου για ένα εσωτερικό πλαίσιο. Αλλά το Swing δεν είναι το μόνο πλαίσιο που κάνει το μοτίβο Observer σε καλή χρήση - χρησιμοποιείται ευρέως στο Java 2 SDK. για παράδειγμα: το Abstract Window Toolkit, το JavaBeans framework, το javax.naming πακέτο και χειριστές εισόδου / εξόδου.

Το παράδειγμα 1 δείχνει συγκεκριμένα τη χρήση του μοτίβου Observer με Swing. Πριν συζητήσουμε περισσότερες λεπτομέρειες μοτίβου Observer, ας δούμε πώς εφαρμόζεται γενικά το μοτίβο.

Πώς λειτουργεί το μοτίβο Observer

Το σχήμα 2 δείχνει πώς συσχετίζονται τα αντικείμενα στο μοτίβο Observer.

Το θέμα, το οποίο είναι πηγή συμβάντων, διατηρεί μια συλλογή παρατηρητών και παρέχει μεθόδους για την προσθήκη και αφαίρεση παρατηρητών από αυτήν τη συλλογή. Το θέμα εφαρμόζει επίσης α κοινοποιώ() μέθοδος που ειδοποιεί κάθε εγγεγραμμένο παρατηρητή για συμβάντα που ενδιαφέρουν τον παρατηρητή. Τα θέματα ενημερώνουν τους παρατηρητές επικαλούμενοι τους παρατηρητές εκσυγχρονίζω() μέθοδος.

Το σχήμα 3 δείχνει ένα διάγραμμα ακολουθίας για το σχέδιο Observer.

Συνήθως, κάποιο άσχετο αντικείμενο θα επικαλεστεί τη μέθοδο ενός θέματος που τροποποιεί την κατάσταση του θέματος. Όταν συμβεί αυτό, το θέμα επικαλείται το δικό του κοινοποιώ() μέθοδο, η οποία επαναλαμβάνει τη συλλογή των παρατηρητών, καλώντας κάθε παρατηρητή εκσυγχρονίζω() μέθοδος.

Το μοτίβο Observer είναι ένα από τα πιο θεμελιώδη μοτίβα σχεδίασης, επειδή επιτρέπει την επικοινωνία με πολύ αποσυνδεδεμένα αντικείμενα. Στο Παράδειγμα 1, το μόνο που γνωρίζει το μοντέλο οριοθετημένης εμβέλειας για τους ακροατές του είναι ότι εφαρμόζουν ένα κατάστασηΑλλαγή () μέθοδος. Οι ακροατές ενδιαφέρονται μόνο για την αξία του μοντέλου και όχι για τον τρόπο υλοποίησης του μοντέλου. Το μοντέλο και οι ακροατές του γνωρίζουν ελάχιστα το ένα για το άλλο, αλλά χάρη στο σχέδιο Observer, μπορούν να επικοινωνήσουν. Αυτός ο υψηλός βαθμός αποσύνδεσης μεταξύ μοντέλων και ακροατών σάς επιτρέπει να δημιουργείτε λογισμικό που αποτελείται από συνδέσιμα αντικείμενα, καθιστώντας τον κώδικά σας εξαιρετικά ευέλικτο και επαναχρησιμοποιήσιμο.

Το Java 2 SDK και το μοτίβο Observer

Το Java 2 SDK παρέχει μια κλασική εφαρμογή του μοτίβου Observer με το Παρατηρητής διεπαφή και το Αισθητός τάξη από το java.util Ευρετήριο. ο Αισθητός τάξη αντιπροσωπεύει το θέμα? παρατηρητές εφαρμόζουν το Παρατηρητής διεπαφή. Είναι ενδιαφέρον ότι αυτή η κλασική εφαρμογή μοτίβου Observer χρησιμοποιείται σπάνια στην πράξη επειδή απαιτεί τα θέματα να επεκτείνουν το Αισθητός τάξη. Η απαίτηση κληρονομιάς σε αυτήν την περίπτωση είναι κακός σχεδιασμός, επειδή ενδεχομένως οποιοσδήποτε τύπος αντικειμένου είναι υποψήφιος υποψήφιος και επειδή η Java δεν υποστηρίζει πολλαπλή κληρονομιά. Συχνά, αυτοί οι υποψήφιοι έχουν ήδη ένα superclass.

Η υλοποίηση βάσει συμβάντος του μοτίβου Observer, που χρησιμοποιήθηκε στο προηγούμενο παράδειγμα, είναι η συντριπτική επιλογή για την εφαρμογή μοτίβου Observer, επειδή δεν απαιτεί θέματα για την επέκταση μιας συγκεκριμένης τάξης. Αντ 'αυτού, τα θέματα ακολουθούν μια σύμβαση που απαιτεί τις ακόλουθες δημόσιες μεθόδους εγγραφής ακροατών:

  • void addXXXListener (XXXListener)
  • void removeXXXListener (XXXListener)

Όποτε το θέμα είναι δεσμευμένη ιδιοκτησία (μια ιδιότητα που έχει παρατηρηθεί από τους ακροατές) αλλάζει, το θέμα επαναλαμβάνει τους ακροατές του και επικαλείται τη μέθοδο που ορίζεται από το XXXListener διεπαφή.

Μέχρι τώρα θα πρέπει να έχετε καλή κατανόηση του προτύπου Observer. Το υπόλοιπο αυτού του άρθρου επικεντρώνεται σε μερικά από τα καλύτερα σημεία του μοτίβου Observer.

Ανώνυμες εσωτερικές τάξεις

Στο Παράδειγμα 1, χρησιμοποίησα εσωτερικές τάξεις για να εφαρμόσω τους ακροατές της εφαρμογής, επειδή τα μαθήματα ακροατών ήταν στενά συνδεδεμένα με την κλάση τους. Ωστόσο, μπορείτε να εφαρμόσετε τους ακροατές με όποιον τρόπο θέλετε. Μία από τις πιο δημοφιλείς επιλογές για το χειρισμό συμβάντων διεπαφής χρήστη είναι η ανώνυμη εσωτερική κλάση, η οποία είναι μια τάξη χωρίς όνομα που έχει δημιουργηθεί εν σειρά, όπως φαίνεται στο Παράδειγμα 2:

Παράδειγμα 2. Εφαρμόστε παρατηρητές με ανώνυμες εσωτερικές τάξεις

... η δοκιμή δημόσιας τάξης επεκτείνει το JFrame {... δημόσια δοκιμή () {... model.addChangeListener (νέο ChangeListener () {public void stateChanged (ChangeEvent e) {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }}); } ...} η κλάση ImageView επεκτείνει το JScrollPane {... δημόσιο ImageView (τελικό εικονίδιο ImageIcon, μοντέλο BoundedRangeModel) {... model.addChangeListener (νέο ChangeListener () {public void stateChanged (ChangeEvent e) {BoundedRangeModel μοντέλο = (BoundedRangeModel)e.getSource (); if (model.getValueIsAdjusting ()) {int min = model.getMinimum (), max = model.getMaximum (), span = max - min, value = model.getValue (); διπλός πολλαπλασιαστής = (διπλή) τιμή / (διπλό) εύρος; πολλαπλασιαστής = πολλαπλασιαστής == 0,0; 0,01: πολλαπλασιαστής Image scaled = originalImage.getScaledInstance ((int) (originalSize.width * multiplier), (int) (originalSize.height * πολλαπλασιαστής), Image.SCALE_FAST); icon.setImage (κλιμάκωση); panel.revalidate (); }}}); }} 

Ο κωδικός του παραδείγματος 2 είναι λειτουργικά ισοδύναμος με τον κωδικό του παραδείγματος 1. Ωστόσο, ο παραπάνω κώδικας χρησιμοποιεί ανώνυμες εσωτερικές κλάσεις για να ορίσει την κλάση και να δημιουργήσει μια παρουσία σε ένα μειωμένο κύκλωμα.

Χειριστής συμβάντων JavaBeans

Η χρήση ανώνυμων εσωτερικών τάξεων, όπως φαίνεται στο προηγούμενο παράδειγμα, ήταν πολύ δημοφιλής στους προγραμματιστές, οπότε ξεκινώντας από την Java 2 Platform, Standard Edition (J2SE) 1.4, η προδιαγραφή JavaBeans έχει αναλάβει την ευθύνη για την εφαρμογή και την εμφάνιση αυτών των εσωτερικών τάξεων για εσάς με το EventHandler τάξη, όπως φαίνεται στο Παράδειγμα 3:

Παράδειγμα 3. Χρήση του java.beans.EventHandler

εισαγωγή java.beans.EventHandler; ... Η δοκιμή δημόσιας τάξης επεκτείνει το JFrame {... δημόσια δοκιμή () {... model.addChangeListener (EventHandler.create (ChangeListener.class, αυτό, "updateReadout")); } ... δημόσιο κενό updateReadout () {String s = Integer.toString (model.getValue ()); readOut.setText (s + "%"); readOut.revalidate (); }} ... 

$config[zx-auto] not found$config[zx-overlay] not found