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

Εισαγωγή στα σχέδια σχεδίασης, Μέρος 2: Επανεξετάστηκε η συμμορία των τεσσάρων κλασικών

Στο Μέρος 1 αυτής της σειράς τριών μερών που παρουσιάζει σχέδια σχεδίασης, αναφέρθηκα Σχέδια σχεδίασης: Στοιχεία επαναχρησιμοποιήσιμου αντικειμενοστραφής σχεδιασμού. Αυτό το κλασικό γράφτηκε από τους Erich Gamma, Richard Helm, Ralph Johnson και John Vlissides, οι οποίοι ήταν συλλογικά γνωστοί ως η συμμορία των τεσσάρων. Όπως θα γνωρίζουν οι περισσότεροι αναγνώστες, Σχεδιαστικά πρότυπα παρουσιάζει 23 σχέδια σχεδιασμού λογισμικού που ταιριάζουν στις κατηγορίες που συζητούνται στο Μέρος 1: Δημιουργική, δομική και συμπεριφορική.

Σχέδια σχεδίασης στο JavaWorld

Η σειρά σχεδίων Java του David Geary είναι μια αριστοτεχνική εισαγωγή σε πολλά από τα σχέδια των Gang of Four στον κώδικα Java.

Σχεδιαστικά πρότυπα είναι κανονική ανάγνωση για προγραμματιστές λογισμικού, αλλά πολλοί νέοι προγραμματιστές αμφισβητούνται από τη μορφή και το εύρος αναφοράς. Καθένα από τα 23 μοτίβα περιγράφεται λεπτομερώς, σε μορφή προτύπου που αποτελείται από 13 ενότητες, οι οποίες μπορεί να είναι πολύ εύπεπτες. Μια άλλη πρόκληση για τους νέους προγραμματιστές Java είναι ότι η συμμορία των τεσσάρων προτύπων προέρχεται από αντικειμενοστρεφή προγραμματισμό, με παραδείγματα βασισμένα σε C ++ και Smalltalk, όχι κώδικα Java.

Σε αυτό το σεμινάριο θα αποσυμπιέσω δύο από τα κοινά χρησιμοποιούμενα μοτίβα - Στρατηγική και Επισκέπτης - από την προοπτική ενός προγραμματιστή Java. Η στρατηγική είναι ένα αρκετά απλό μοτίβο που χρησιμεύει ως παράδειγμα για το πώς να βραχείτε τα πόδια σας με τα σχέδια σχεδίασης GoF γενικά. Ο επισκέπτης είναι πιο περίπλοκος και ενδιάμεσος. Θα ξεκινήσω με ένα παράδειγμα που θα πρέπει να απομυθοποιήσει τον μηχανισμό διπλής αποστολής, ο οποίος είναι ένα σημαντικό μέρος του μοτίβου επισκεπτών. Τότε θα δείξω το μοτίβο επισκεπτών σε μια περίπτωση χρήσης μεταγλωττιστή.

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

Στρατηγική αποσυσκευασίας

ο Στρατηγική Το μοτίβο σάς επιτρέπει να ορίσετε μια οικογένεια αλγορίθμων, όπως εκείνοι που χρησιμοποιούνται για ταξινόμηση, σύνθεση κειμένου ή διαχείριση διάταξης. Η στρατηγική σάς επιτρέπει επίσης να ενσωματώνετε κάθε αλγόριθμο στη δική του κατηγορία και να τους κάνετε εναλλάξιμους. Κάθε αλγόριθμος σε κάψουλα είναι γνωστός ως στρατηγική. Κατά το χρόνο εκτέλεσης, ένας πελάτης επιλέγει τον κατάλληλο αλγόριθμο για τις απαιτήσεις του.

Τι είναι ο πελάτης;

ΕΝΑ πελάτης είναι οποιοδήποτε λογισμικό που αλληλεπιδρά με ένα σχέδιο σχεδίασης. Αν και συνήθως ένα αντικείμενο, ένας πελάτης θα μπορούσε επίσης να είναι κωδικός εντός της εφαρμογής δημόσιο στατικό κενό κενών (String [] args) μέθοδος.

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

Από μια αφηρημένη προοπτική, η στρατηγική περιλαμβάνει Στρατηγική, ΣτρατηγικήΧ, και Συμφραζόμενα τύποι.

Στρατηγική

Στρατηγική παρέχει μια κοινή διεπαφή σε όλους τους υποστηριζόμενους αλγόριθμους. Η λίστα 1 παρουσιάζει το Στρατηγική διεπαφή.

Λίστα 1. Το void execute (int x) πρέπει να εφαρμοστεί από όλες τις συγκεκριμένες στρατηγικές

δημόσια διεπαφή στρατηγική {public void execute (int x); }

Όπου συγκεκριμένες στρατηγικές δεν παραμετροποιούνται με κοινά δεδομένα, μπορείτε να τις εφαρμόσετε μέσω Java διεπαφή χαρακτηριστικό. Όπου παραμετροποιούνται, θα δηλώνατε μια αφηρημένη τάξη. Για παράδειγμα, οι στρατηγικές ευθυγράμμισης προς τα δεξιά, κεντρικής ευθυγράμμισης και αιτιολόγησης κειμένου μοιράζονται την έννοια του a πλάτος στο οποίο θα εκτελεστεί ευθυγράμμιση κειμένου. Έτσι θα το δηλώνατε πλάτος στην αφηρημένη τάξη.

ΣτρατηγικήΧ

Καθε ΣτρατηγικήΧ εφαρμόζει την κοινή διεπαφή και παρέχει μια εφαρμογή αλγορίθμου. Η Λίστα 2 υλοποιεί την Καταχώριση 1 Στρατηγική διεπαφή για να περιγράψει μια συγκεκριμένη συγκεκριμένη στρατηγική.

Λίστα 2. Το ConcreteStrategyA εκτελεί έναν αλγόριθμο

δημόσια τάξη Το ConcreteStrategyA εφαρμόζει στρατηγική {@Override public void execute (int x) {System.out.println ("στρατηγική εκτέλεσης A: x =" + x); }}

ο άκυρη εκτέλεση (int x) Η μέθοδος στην Λίστα 2 προσδιορίζει μια συγκεκριμένη στρατηγική. Σκεφτείτε αυτήν τη μέθοδο ως αφαίρεση για κάτι πιο χρήσιμο, όπως ένα συγκεκριμένο είδος αλγορίθμου ταξινόμησης (π.χ. Bubble Sort, Insertion Sort ή Quick Sort) ή ένα συγκεκριμένο είδος διαχείρισης διάταξης (π.χ. Flow Layout, Border Layout ή Διάταξη πλέγματος).

Η λίστα 3 παρουσιάζει ένα δευτερόλεπτο Στρατηγική εκτέλεση.

Λίστα 3. Το ConcreteStrategyB εκτελεί έναν άλλο αλγόριθμο

δημόσια κλάση ConcreteStrategyB εφαρμόζει στρατηγική {@Override public void execute (int x) {System.out.println ("στρατηγική εκτέλεσης B: x =" + x); }}

Συμφραζόμενα

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

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

Λίστα 4. Το περιβάλλον διαμορφώνεται με μια παρουσία ConcreteStrategyx

Πλαίσιο τάξης {ιδιωτική στρατηγική στρατηγικής · δημόσιο πλαίσιο (στρατηγική στρατηγικής) {setStrategy (στρατηγική); } public void executeStrategy (int x) {strategi.execute (x); } public void setStrategy (στρατηγική στρατηγικής) {this.strategy = στρατηγική; }}

ο Συμφραζόμενα Το class στην Λίστα 4 αποθηκεύει μια στρατηγική όταν δημιουργείται, παρέχει μια μέθοδο για να αλλάξει στη συνέχεια τη στρατηγική και παρέχει μια άλλη μέθοδο για την εκτέλεση της τρέχουσας στρατηγικής. Εκτός από τη μετάδοση μιας στρατηγικής στον κατασκευαστή, αυτό το μοτίβο μπορεί να δει στην κλάση java.awt .Container, του οποίου void setLayout (LayoutManager mgr) και άκυρο doLayout () οι μέθοδοι καθορίζουν και εκτελούν τη στρατηγική διαχείρισης διάταξης.

Στρατηγική Demo

Χρειαζόμαστε έναν πελάτη για να δείξουμε τους προηγούμενους τύπους. Η λίστα 5 παρουσιάζει α Στρατηγική Demo τάξη πελάτη.

Λίστα 5. Στρατηγική Demo

δημόσια τάξη StrategyDemo {public static void main (String [] args) {Context context = new Context (new ConcreteStrategyA ()); konteks.executeStrategy (1); konteks.setStrategy (νέο ConcreteStrategyB ()); konteks.executeStrategy (2); }}

Μια συγκεκριμένη στρατηγική σχετίζεται με το α Συμφραζόμενα περίπτωση κατά τη δημιουργία του περιβάλλοντος. Η στρατηγική μπορεί στη συνέχεια να αλλάξει μέσω μιας κλήσης μεθόδου περιβάλλοντος.

Εάν συντάξετε αυτές τις τάξεις και εκτελέστε Στρατηγική Demo, πρέπει να παρατηρήσετε την ακόλουθη έξοδο:

στρατηγική εκτέλεσης A: x = 1 στρατηγική εκτέλεσης B: x = 2

Επανεξέταση του μοτίβου επισκεπτών

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

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

Τι είναι η διπλή αποστολή;

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

  • Ενιαία αποστολή: Λαμβάνοντας υπόψη μια ιεραρχία τάξης όπου κάθε τάξη εφαρμόζει την ίδια μέθοδο (δηλαδή, κάθε υποκατηγορία παρακάμπτει την έκδοση της προηγούμενης κλάσης της μεθόδου) και δεδομένης μιας μεταβλητής που έχει εκχωρηθεί μια παρουσία μιας από αυτές τις τάξεις, ο τύπος μπορεί να προσδιοριστεί μόνο χρόνος εκτέλεσης. Για παράδειγμα, ας υποθέσουμε ότι κάθε τάξη εφαρμόζει τη μέθοδο Τυπώνω(). Ας υποθέσουμε επίσης ότι μία από αυτές τις τάξεις είναι εγκατεστημένη στο χρόνο εκτέλεσης και η μεταβλητή της αντιστοιχεί στη μεταβλητή ένα. Όταν συναντά τον μεταγλωττιστή Java α. εκτύπωση ();, μπορεί να το επιβεβαιώσει μόνο έναΟ τύπος περιέχει ένα Τυπώνω() μέθοδος. Δεν ξέρει ποια μέθοδο να καλέσει. Κατά το χρόνο εκτέλεσης, η εικονική μηχανή εξετάζει την αναφορά στη μεταβλητή ένα και υπολογίζει τον πραγματικό τύπο για να καλέσετε τη σωστή μέθοδο. Αυτή η κατάσταση, στην οποία μια εφαρμογή βασίζεται σε έναν μόνο τύπο (τον τύπο της παρουσίας), είναι γνωστή ως μία αποστολή.
  • Πολλαπλή αποστολή: Σε αντίθεση με τη μεμονωμένη αποστολή, όπου ένα μόνο όρισμα καθορίζει ποια μέθοδο αυτού του ονόματος θα επικαλεστεί, πολλαπλή αποστολή χρησιμοποιεί όλα τα επιχειρήματά του. Με άλλα λόγια, γενικεύει τη δυναμική αποστολή για εργασία με δύο ή περισσότερα αντικείμενα. (Σημειώστε ότι το όρισμα σε μία αποστολή καθορίζεται συνήθως με διαχωριστικό τελείας στα αριστερά του ονόματος της μεθόδου που καλείται, όπως το ένα σε α. εκτύπωση ().)

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

Βασιζόμαστε υπερβολικά σε διπλή αποστολή;

Ο Blogger Derek Greer πιστεύει ότι η χρήση διπλής αποστολής μπορεί να υποδηλώνει ένα ζήτημα σχεδιασμού, το οποίο θα μπορούσε να επηρεάσει τη συντήρηση μιας εφαρμογής. Διαβάστε την ανάρτηση ιστολογίου του Greer's "Double dispatch is a code bau" και σχετικά σχόλια.

Προσομοίωση διπλής αποστολής σε κώδικα Java

Η καταχώριση της Wikipedia σε διπλή αποστολή παρέχει ένα παράδειγμα με βάση το C ++ που δείχνει ότι είναι κάτι παραπάνω από υπερφόρτωση λειτουργίας. Στην Λίστα 6, παρουσιάζω το αντίστοιχο Java.

Λίστα 6. Διπλή αποστολή σε κώδικα Java

δημόσια τάξη DDDemo {public static void main (String [] args) {Asteroid theAsteroid = new Asteroid (); SpaceShip theSpaceShip = νέο SpaceShip (); ApolloSpacecraft theApolloSpacecraft = νέο ApolloSpacecraft (); τοAsteroid.collideWith (theSpaceShip) τοAsteroid.collideWith (το ApolloSpacecraft) System.out.println (); ExplodingAsteroid theExplodingAsteroid = νέο ExplodingAsteroid (); τοExplodingAsteroid.collideWith (theSpaceShip); τοExplodingAsteroid.collideWith (theApolloSpacecraft) · System.out.println (); Αστεροειδής theAsteroidReference = theExplodingAsteroid; το theAsteroidReference.collideWith (theSpaceShip); τοAsteroidReference.collideWith (το ApolloSpacecraft) System.out.println (); SpaceShip theSpaceShipReference = τοApolloSpacecraft; τοAsteroid.collideWith (theSpaceShipReference) τοAsteroidReference.collideWith (theSpaceShipReference); System.out.println (); theSpaceShipReference = τοApolloSpacecraft; τοAsteroidReference = theExplodingAsteroid; τοSpaceShipReference.collideWith (theAsteroid); τοSpaceShipReference.collideWith (theAsteroidReference); }} class SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (αυτό); }} Η κλάση ApolloSpacecraft επεκτείνει το SpaceShip {void collideWith (Asteroid inAsteroid) {inAsteroid.collideWith (this); }} class Asteroid {void collideWith (SpaceShip s) {System.out.println ("Asteroid hit a SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("Ο αστεροειδής χτύπησε ένα ApolloSpacecraft"); }} Η κλάση ExplodingAsteroid επεκτείνει τον Αστεροειδή {void collideWith (SpaceShip s) {System.out.println ("ExplodingAsteroid hit a SpaceShip"); } void collideWith (ApolloSpacecraft as) {System.out.println ("ExplodingAsteroid χτύπησε ένα ApolloSpacecraft"); }}

Η λίστα 6 ακολουθεί το αντίστοιχο C ++ όσο το δυνατόν πιο κοντά. Οι τελευταίες τέσσερις γραμμές στο κύριος() μέθοδος μαζί με το άκυρη σύγκρουση με (Asteroid inAsteroid) μεθόδους σε ΔΙΑΣΤΗΜΟΠΛΟΙΟ και ApolloSpacecraft επίδειξη και προσομοίωση διπλής αποστολής.

Εξετάστε το ακόλουθο απόσπασμα από το τέλος του κύριος():

theSpaceShipReference = τοApolloSpacecraft; τοAsteroidReference = theExplodingAsteroid; τοSpaceShipReference.collideWith (theAsteroid); τοSpaceShipReference.collideWith (theAsteroidReference);

Η τρίτη και η τέταρτη γραμμή χρησιμοποιούν μία αποστολή για να βρουν το σωστό συγκρούομαι με() μέθοδος (σε ΔΙΑΣΤΗΜΟΠΛΟΙΟ ή ApolloSpacecraft) να επικαλεστεί. Αυτή η απόφαση λαμβάνεται από την εικονική μηχανή με βάση τον τύπο αναφοράς που είναι αποθηκευμένη theSpaceShipReference.

Από μέσα συγκρούομαι με(), στοAsteroid.collideWith (αυτό); χρησιμοποιεί μια αποστολή για να καταλάβει τη σωστή τάξη (Αστεροειδής ή Έκρηξη Αστεροειδές) που περιέχει το επιθυμητό συγκρούομαι με() μέθοδος. Επειδή Αστεροειδής και Έκρηξη Αστεροειδές παραφορτώνω συγκρούομαι με(), το είδος του επιχειρήματος Αυτό (ΔΙΑΣΤΗΜΟΠΛΟΙΟ ή ApolloSpacecraftχρησιμοποιείται για τη διάκριση του σωστού συγκρούομαι με() μέθοδος κλήσης.

Και με αυτό, έχουμε πραγματοποιήσει διπλή αποστολή. Για να ανακεφαλαιώσουμε, κάναμε πρώτα κλήση συγκρούομαι με() σε ΔΙΑΣΤΗΜΟΠΛΟΙΟ ή ApolloSpacecraft, και στη συνέχεια χρησιμοποίησε το επιχείρημά του και Αυτό για να καλέσετε ένα από τα συγκρούομαι με() μεθόδους σε Αστεροειδής ή Έκρηξη Αστεροειδές.

Όταν τρέχετε DDDemo, πρέπει να παρατηρήσετε την ακόλουθη έξοδο: