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

Το πρόβλημα της αρχικοποίησης αντικειμένων σε εφαρμογές OOP στην PHP. Εύρεση λύσης χρησιμοποιώντας μοτίβα Registry, Factory Method, Service Locator και Dependency Injection

Συμβαίνει οι προγραμματιστές να ενοποιούν επιτυχημένες λύσεις με τη μορφή μοτίβων σχεδίασης. Υπάρχει πολλή βιβλιογραφία για τα μοτίβα. Το βιβλίο του The Gang of Four "Design Patterns" των Erich Gamma, Richard Helm, Ralph Johnson και John Vlissides" και, ίσως, το "Paterns of Enterprise Application Architecture" του Martin Fowler θεωρούνται σίγουρα κλασικά. Ό,τι καλύτερο έχω διαβάσει με παραδείγματα σε PHP - αυτό. Τυχαίνει ότι όλη αυτή η βιβλιογραφία είναι αρκετά περίπλοκη για άτομα που μόλις άρχισαν να κατακτούν το OOP. Έτσι, είχα την ιδέα να παρουσιάσω μερικά από τα μοτίβα που θεωρώ πιο χρήσιμα σε πολύ απλοποιημένη μορφή. Σε άλλα λόγια, αυτό το άρθρο είναι η πρώτη μου απόπειρα να ερμηνεύσω μοτίβα σχεδίασης στο στυλ KISS.
Σήμερα θα μιλήσουμε για τα προβλήματα που μπορεί να προκύψουν με την προετοιμασία αντικειμένων σε μια εφαρμογή OOP και πώς μπορείτε να χρησιμοποιήσετε ορισμένα δημοφιλή σχέδια σχεδίασης για να λύσετε αυτά τα προβλήματα.

Παράδειγμα

Μια σύγχρονη εφαρμογή OOP λειτουργεί με δεκάδες, εκατοντάδες και μερικές φορές χιλιάδες αντικείμενα. Λοιπόν, ας ρίξουμε μια πιο προσεκτική ματιά στο πώς αρχικοποιούνται αυτά τα αντικείμενα στις εφαρμογές μας. Η προετοιμασία αντικειμένων είναι η μόνη πτυχή που μας ενδιαφέρει σε αυτό το άρθρο, γι' αυτό αποφάσισα να παραλείψω όλη την "επιπλέον" υλοποίηση.
Ας υποθέσουμε ότι δημιουργήσαμε μια χρήσιμη κλάση super-duper που μπορεί να στείλει ένα αίτημα GET σε ένα συγκεκριμένο URI και να επιστρέψει HTML από την απόκριση διακομιστή. Για να μην φαίνεται πολύ απλή η τάξη μας, αφήστε την να ελέγξει επίσης το αποτέλεσμα και να κάνει μια εξαίρεση εάν ο διακομιστής απαντήσει "λανθασμένα".

Κατηγορία Grabber ( δημόσια συνάρτηση get($url) (/** επιστρέφει κώδικα HTML ή ρίχνει μια εξαίρεση */) )

Ας δημιουργήσουμε μια άλλη κλάση της οποίας τα αντικείμενα θα είναι υπεύθυνα για το φιλτράρισμα του ληφθέντος HTML. Η μέθοδος φίλτρου λαμβάνει κώδικα HTML και έναν επιλογέα CSS ως ορίσματα και επιστρέφει μια σειρά στοιχείων που βρέθηκαν για τον συγκεκριμένο επιλογέα.

Κλάση HtmlExtractor ( φίλτρο δημόσιας συνάρτησης ($html, $selector) (/** επιστρέφει πίνακα φιλτραρισμένων στοιχείων */) )

Τώρα, φανταστείτε ότι πρέπει να λαμβάνουμε αποτελέσματα αναζήτησης στο Google για συγκεκριμένες λέξεις-κλειδιά. Για να γίνει αυτό, θα εισαγάγουμε μια άλλη κλάση που θα χρησιμοποιεί την κλάση Grabber για να στείλει ένα αίτημα και την κλάση HtmlExtractor για εξαγωγή του απαραίτητου περιεχομένου. Θα περιέχει επίσης τη λογική για την κατασκευή του URI, έναν επιλογέα για το φιλτράρισμα του ληφθέντος HTML και την επεξεργασία των αποτελεσμάτων που λαμβάνονται.

Κλάση GoogleFinder ( ιδιωτικό $grabber; ιδιωτικό $filter; δημόσια συνάρτηση __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) δημόσια συνάρτηση find($searchString) ( /* * επιστρέφει πίνακα θεμελιωμένων αποτελεσμάτων */) )

Παρατηρήσατε ότι η προετοιμασία των αντικειμένων Grabber και HtmlExtractor βρίσκεται στον κατασκευαστή της κλάσης GoogleFinder; Ας σκεφτούμε πόσο καλή είναι αυτή η απόφαση.
Φυσικά, η σκληρή κωδικοποίηση της δημιουργίας αντικειμένων σε έναν κατασκευαστή δεν είναι καλή ιδέα. Και για αυτο. Πρώτον, δεν θα είμαστε σε θέση να παρακάμψουμε εύκολα την κλάση Grabber στο περιβάλλον δοκιμής για να αποφύγουμε την αποστολή πραγματικού αιτήματος. Για να είμαστε δίκαιοι, αξίζει να πούμε ότι αυτό μπορεί να γίνει χρησιμοποιώντας το Reflection API. Εκείνοι. η τεχνική δυνατότητα υπάρχει, αλλά απέχει πολύ από τον πιο βολικό και προφανή τρόπο.
Δεύτερον, το ίδιο πρόβλημα θα προκύψει εάν θέλουμε να χρησιμοποιήσουμε ξανά τη λογική του GoogleFinder με άλλες υλοποιήσεις Grabber και HtmlExtractor. Η δημιουργία εξαρτήσεων είναι κωδικοποιημένη στον κατασκευαστή κλάσης. Και στην καλύτερη περίπτωση, θα μπορέσουμε να κληρονομήσουμε το GoogleFinder και να παρακάμψουμε τον κατασκευαστή του. Και ακόμη και τότε, μόνο εάν το πεδίο εφαρμογής των ιδιοτήτων αρπαγής και φίλτρου είναι προστατευμένο ή δημόσιο.
Ένα τελευταίο σημείο, κάθε φορά που δημιουργούμε ένα νέο αντικείμενο GoogleFinder, ένα νέο ζεύγος αντικειμένων εξάρτησης θα δημιουργείται στη μνήμη, αν και μπορούμε πολύ εύκολα να χρησιμοποιήσουμε ένα αντικείμενο Grabber και ένα αντικείμενο HtmlExtractor σε πολλά αντικείμενα του GoogleFinder.
Νομίζω ότι έχετε ήδη καταλάβει ότι η προετοιμασία της εξάρτησης πρέπει να μετακινηθεί εκτός της τάξης. Μπορούμε να απαιτήσουμε να μεταβιβαστούν οι ήδη προετοιμασμένες εξαρτήσεις στον κατασκευαστή της κλάσης GoogleFinder.

Κατηγορία GoogleFinder ( ιδιωτικό $grabber; ιδιωτικό $filter; δημόσια συνάρτηση __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) εύρεση δημόσιας συνάρτησης ($searchString) ( /** επιστρέφει πίνακα θεμελιωμένων αποτελεσμάτων */) )

Εάν θέλουμε να δώσουμε σε άλλους προγραμματιστές τη δυνατότητα να προσθέτουν και να χρησιμοποιούν τις δικές τους υλοποιήσεις Grabber και HtmlExtractor, τότε θα πρέπει να εξετάσουμε το ενδεχόμενο εισαγωγής διεπαφών για αυτούς. Σε αυτή την περίπτωση, αυτό δεν είναι μόνο χρήσιμο, αλλά και απαραίτητο. Πιστεύω ότι εάν χρησιμοποιούμε μόνο μία υλοποίηση σε ένα έργο και δεν περιμένουμε να δημιουργήσουμε νέες στο μέλλον, τότε θα πρέπει να αρνηθούμε τη δημιουργία διεπαφής. Είναι καλύτερα να ενεργείτε ανάλογα με την κατάσταση και να κάνετε απλή ανακατασκευή όταν υπάρχει πραγματική ανάγκη.
Τώρα έχουμε όλες τις απαραίτητες κλάσεις και μπορούμε να χρησιμοποιήσουμε την τάξη GoogleFinder στον ελεγκτή.

Ελεγκτής κλάσης ( ενέργεια δημόσιας λειτουργίας() ( /* Μερικά πράγματα */ $finder = νέο GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

Ας συνοψίσουμε τα ενδιάμεσα αποτελέσματα. Γράψαμε πολύ λίγο κώδικα και με την πρώτη ματιά, δεν κάναμε τίποτα λάθος. Αλλά... τι γίνεται αν χρειαστεί να χρησιμοποιήσουμε ένα αντικείμενο όπως το GoogleFinder σε άλλο μέρος; Θα πρέπει να αντιγράψουμε τη δημιουργία του. Στο παράδειγμά μας, αυτή είναι μόνο μία γραμμή και το πρόβλημα δεν είναι τόσο αισθητό. Στην πράξη, η προετοιμασία αντικειμένων μπορεί να είναι αρκετά περίπλοκη και μπορεί να πάρει έως και 10 γραμμές ή και περισσότερες. Προκύπτουν επίσης άλλα προβλήματα που είναι τυπικά της αντιγραφής κώδικα. Εάν κατά τη διαδικασία αναδιαμόρφωσης χρειαστεί να αλλάξετε το όνομα της κλάσης που χρησιμοποιείται ή τη λογική αρχικοποίησης αντικειμένου, θα πρέπει να αλλάξετε με μη αυτόματο τρόπο όλες τις θέσεις. Νομίζω ότι ξέρετε πώς συμβαίνει :)
Συνήθως, ο σκληρός κώδικας αντιμετωπίζεται απλά. Στη διαμόρφωση συνήθως περιλαμβάνονται διπλές τιμές. Αυτό σας επιτρέπει να αλλάξετε τις τιμές κεντρικά σε όλα τα μέρη όπου χρησιμοποιούνται.

Πρότυπο μητρώου.

Έτσι, αποφασίσαμε να μετακινήσουμε τη δημιουργία αντικειμένων στη διαμόρφωση. Ας το κάνουμε.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filter"] = new HtmlExtractor(); $registry["google_finder"] = νέο GoogleFinder($registry["grabber"], $registry["filter"]);
Το μόνο που έχουμε να κάνουμε είναι να περάσουμε το ArrayObject στον ελεγκτή και το πρόβλημα λύνεται.

Ελεγκτής κλάσης ( ιδιωτικό $registry; δημόσια συνάρτηση __construct(ArrayObject $registry) ( $this->registry = $registry; ) ενέργεια δημόσιας λειτουργίας() ( /* Κάποια πράγματα */ $results = $this->registry["google_finder" ]->find("search string"); /* Κάντε κάτι με τα αποτελέσματα */ ) )

Μπορούμε να αναπτύξουμε περαιτέρω την ιδέα του Μητρώου. Κληρονομήστε το ArrayObject, ενθυλακώστε τη δημιουργία αντικειμένων μέσα σε μια νέα κλάση, απαγορεύστε την προσθήκη νέων αντικειμένων μετά την προετοιμασία κ.λπ. Αλλά κατά τη γνώμη μου, ο κωδικός που δίνεται καθιστά πλήρως σαφές τι είναι το πρότυπο Μητρώο. Αυτό το μοτίβο δεν είναι γενεσιουργό, αλλά οδηγεί σε κάποιο τρόπο στην επίλυση των προβλημάτων μας. Το μητρώο είναι απλώς ένα κοντέινερ στο οποίο μπορούμε να αποθηκεύσουμε αντικείμενα και να τα διαβιβάσουμε μέσα στην εφαρμογή. Για να γίνουν διαθέσιμα αντικείμενα, πρέπει πρώτα να τα δημιουργήσουμε και να τα καταχωρήσουμε σε αυτό το κοντέινερ. Ας δούμε τα πλεονεκτήματα και τα μειονεκτήματα αυτής της προσέγγισης.
Με την πρώτη ματιά, πετύχαμε τον στόχο μας. Σταματήσαμε να κωδικοποιούμε ονόματα κλάσεων και δημιουργούμε αντικείμενα σε ένα μέρος. Δημιουργούμε αντικείμενα σε ένα μόνο αντίγραφο, το οποίο εγγυάται την επαναχρησιμοποίησή τους. Εάν αλλάξει η λογική για τη δημιουργία αντικειμένων, τότε μόνο μία θέση στην εφαρμογή θα χρειαστεί να επεξεργαστεί. Ως μπόνους, λάβαμε τη δυνατότητα κεντρικής διαχείρισης αντικειμένων στο Μητρώο. Μπορούμε εύκολα να πάρουμε μια λίστα με όλα τα διαθέσιμα αντικείμενα και να κάνουμε κάποιους χειρισμούς με αυτά. Ας δούμε τώρα τι μπορεί να μην μας αρέσει σε αυτό το πρότυπο.
Αρχικά, πρέπει να δημιουργήσουμε το αντικείμενο πριν το καταχωρήσουμε στο Μητρώο. Αντίστοιχα, υπάρχει μεγάλη πιθανότητα δημιουργίας «περιττών αντικειμένων», π.χ. αυτά που θα δημιουργηθούν στη μνήμη, αλλά δεν θα χρησιμοποιηθούν στην εφαρμογή. Ναι, μπορούμε να προσθέσουμε αντικείμενα στο Μητρώο δυναμικά, π.χ. δημιουργήστε μόνο εκείνα τα αντικείμενα που χρειάζονται για την επεξεργασία ενός συγκεκριμένου αιτήματος. Με τον ένα ή τον άλλο τρόπο, θα πρέπει να το ελέγξουμε χειροκίνητα. Κατά συνέπεια, με την πάροδο του χρόνου θα γίνει πολύ δύσκολο να διατηρηθεί.
Δεύτερον, έχουμε μια νέα εξάρτηση ελεγκτή. Ναι, μπορούμε να λάβουμε αντικείμενα μέσω μιας στατικής μεθόδου στο Μητρώο, έτσι ώστε να μην χρειάζεται να περάσουμε το Μητρώο στον κατασκευαστή. Αλλά κατά τη γνώμη μου, δεν πρέπει να το κάνετε αυτό. Οι στατικές μέθοδοι είναι μια ακόμη πιο στενή σύνδεση από τη δημιουργία εξαρτήσεων μέσα σε ένα αντικείμενο και δυσκολίες στη δοκιμή (σε αυτό το θέμα).
Τρίτον, η διεπαφή ελεγκτή δεν μας λέει τίποτα για τα αντικείμενα που χρησιμοποιεί. Μπορούμε να πάρουμε οποιοδήποτε αντικείμενο είναι διαθέσιμο στο Μητρώο στον ελεγκτή. Θα είναι δύσκολο για εμάς να πούμε ποια αντικείμενα χρησιμοποιεί ο ελεγκτής μέχρι να ελέγξουμε όλο τον πηγαίο κώδικα του.

Εργοστασιακή μέθοδος

Το μεγαλύτερο πρόβλημα με το Μητρώο είναι ότι ένα αντικείμενο πρέπει να προετοιμαστεί για να μπορέσει να αποκτήσει πρόσβαση. Αντί να αρχικοποιήσουμε ένα αντικείμενο στη διαμόρφωση, μπορούμε να διαχωρίσουμε τη λογική για τη δημιουργία αντικειμένων σε μια άλλη κλάση, την οποία μπορούμε να «ζητήσουμε» να δημιουργήσει το αντικείμενο που χρειαζόμαστε. Οι κλάσεις που είναι υπεύθυνες για τη δημιουργία αντικειμένων ονομάζονται εργοστάσια. Και το σχέδιο σχεδίασης ονομάζεται Factory Method. Ας δούμε ένα παράδειγμα εργοστασίου.

Class Factory ( δημόσια συνάρτηση getGoogleFinder() ( επιστροφή νέα GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) ιδιωτική συνάρτηση getGrabber() ( return new Grabber(); ) ιδιωτική συνάρτηση getHtmlExtractor() ( επιστροφή νέου HtmlFiletr(); ) )

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

Class Factory ( ιδιωτικό $finder; δημόσια συνάρτηση getGoogleFinder() ( if (null === $this->finder) ( $this->finder = νέο GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) επιστροφή $this->finder; ))

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

Ελεγκτής κλάσης ( ιδιωτικό $factory; δημόσια συνάρτηση __construct(Factory $factory) ( $this->factory = $factory; ) public function action() ( /* Some stuff */ $results = $this->factory->getGoogleFinder( )->find("search string"); /* Κάντε κάτι με τα αποτελέσματα */ ) )

Τα πλεονεκτήματα αυτής της προσέγγισης περιλαμβάνουν την απλότητά της. Τα αντικείμενά μας δημιουργούνται ρητά και το IDE σας θα σας οδηγήσει εύκολα στο μέρος όπου συμβαίνει αυτό. Επιλύσαμε επίσης το πρόβλημα του Μητρώου, έτσι ώστε τα αντικείμενα στη μνήμη να δημιουργούνται μόνο όταν "ζητήσουμε" από το εργοστάσιο να το κάνει. Αλλά δεν έχουμε αποφασίσει ακόμη πώς θα προμηθεύσουμε τα απαραίτητα εργοστάσια στους ελεγκτές. Υπάρχουν πολλές επιλογές εδώ. Μπορείτε να χρησιμοποιήσετε στατικές μεθόδους. Μπορούμε να αφήσουμε τους ελεγκτές να δημιουργήσουν μόνοι τους τα απαραίτητα εργοστάσια και να ακυρώσουμε όλες τις προσπάθειές μας να απαλλαγούμε από το copy-paste. Μπορείτε να δημιουργήσετε ένα εργοστάσιο εργοστασίων και να περάσετε μόνο αυτό στον ελεγκτή. Αλλά η λήψη αντικειμένων στον ελεγκτή θα γίνει λίγο πιο περίπλοκη και θα χρειαστεί να διαχειριστείτε τις εξαρτήσεις μεταξύ των εργοστασίων. Επιπλέον, δεν είναι απολύτως σαφές τι πρέπει να κάνουμε εάν θέλουμε να χρησιμοποιήσουμε λειτουργικές μονάδες στην εφαρμογή μας, πώς να καταχωρούμε εργοστάσια μονάδων, πώς να διαχειριζόμαστε τις συνδέσεις μεταξύ εργοστασίων από διαφορετικές μονάδες. Γενικά, έχουμε χάσει το κύριο πλεονέκτημα του εργοστασίου - τη ρητή δημιουργία αντικειμένων. Και ακόμα δεν έχουμε λύσει το πρόβλημα της «σιωπηρής» διεπαφής ελεγκτή.

Υπηρεσία εντοπισμού

Το πρότυπο Service Locator σάς επιτρέπει να επιλύσετε την έλλειψη κατακερματισμού των εργοστασίων και να διαχειριστείτε τη δημιουργία αντικειμένων αυτόματα και κεντρικά. Αν το σκεφτούμε, μπορούμε να εισαγάγουμε ένα πρόσθετο στρώμα αφαίρεσης που θα είναι υπεύθυνο για τη δημιουργία αντικειμένων στην εφαρμογή μας και τη διαχείριση των σχέσεων μεταξύ αυτών των αντικειμένων. Για να μπορέσει αυτό το επίπεδο να δημιουργήσει αντικείμενα για εμάς, θα χρειαστεί να του δώσουμε τη γνώση για το πώς να το κάνει αυτό.
Όροι μοτίβου του Service Locator:
  • Η υπηρεσία είναι ένα έτοιμο αντικείμενο που μπορεί να ληφθεί από ένα δοχείο.
  • Ορισμός υπηρεσίας – λογική προετοιμασίας υπηρεσίας.
  • Ένα κοντέινερ (Service Container) είναι ένα κεντρικό αντικείμενο που αποθηκεύει όλες τις περιγραφές και μπορεί να δημιουργήσει υπηρεσίες με βάση αυτές.
Οποιαδήποτε μονάδα μπορεί να καταχωρήσει τις περιγραφές των υπηρεσιών της. Για να λάβουμε κάποια υπηρεσία από το κοντέινερ, θα πρέπει να το ζητήσουμε με κλειδί. Υπάρχουν πολλές επιλογές για την υλοποίηση του Service Locator· στην απλούστερη έκδοση, μπορούμε να χρησιμοποιήσουμε το ArrayObject ως κοντέινερ και ένα κλείσιμο ως περιγραφή υπηρεσιών.

Το Class ServiceContainer επεκτείνει το ArrayObject ( η δημόσια συνάρτηση get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) ρίχνει το νέο \RuntimeException("Δεν είναι δυνατή η εύρεση του ορισμού υπηρεσίας κάτω από το κλειδί [ $key ]"); ))

Στη συνέχεια, η εγγραφή Ορισμών θα μοιάζει με αυτό:

$container = new ServiceContainer(); $container["grabber"] = συνάρτηση () ( return new Grabber(); ); $container["html_filter"] = function () ( return new HtmlExtractor(); ); $container["google_finder"] = function() use ($container) ( return new GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

Και η χρήση στον ελεγκτή είναι η εξής:

Ελεγκτής κλάσης ( ιδιωτικό $container; δημόσια συνάρτηση __construct(ServiceContainer $container) ( $this->container = $container; ) ενέργεια δημόσιας λειτουργίας() ( /* Μερικά πράγματα */ $results = $this->container->get( "google_finder")->find("search string"); /* Κάντε κάτι με τα αποτελέσματα */ ) )

Ένα Service Container μπορεί να είναι πολύ απλό ή μπορεί να είναι πολύ περίπλοκο. Για παράδειγμα, το Symfony Service Container παρέχει πολλές δυνατότητες: παραμέτρους, εύρος υπηρεσιών, αναζήτηση υπηρεσιών ανά ετικέτες, ψευδώνυμα, ιδιωτικές υπηρεσίες, δυνατότητα πραγματοποίησης αλλαγών στο κοντέινερ μετά την προσθήκη όλων των υπηρεσιών (περάσματα μεταγλωττιστή) και πολλά άλλα. Το DIExtraBundle επεκτείνει περαιτέρω τις δυνατότητες της τυπικής υλοποίησης.
Ας επιστρέψουμε όμως στο παράδειγμά μας. Όπως μπορείτε να δείτε, το Service Locator όχι μόνο επιλύει όλα τα ίδια προβλήματα με τα προηγούμενα πρότυπα, αλλά καθιστά επίσης εύκολη τη χρήση λειτουργικών μονάδων με τους δικούς τους ορισμούς υπηρεσιών.
Επιπλέον, σε επίπεδο πλαισίου λάβαμε ένα επιπλέον επίπεδο αφαίρεσης. Δηλαδή, αλλάζοντας τη μέθοδο ServiceContainer::get μπορούμε, για παράδειγμα, να αντικαταστήσουμε το αντικείμενο με έναν διακομιστή μεσολάβησης. Και το πεδίο εφαρμογής των αντικειμένων μεσολάβησης περιορίζεται μόνο από τη φαντασία του προγραμματιστή. Εδώ μπορείτε να εφαρμόσετε το παράδειγμα AOP, LazyLoading, κ.λπ.
Ωστόσο, οι περισσότεροι προγραμματιστές εξακολουθούν να θεωρούν το Service Locator ως αντι-μοτίβο. Γιατί, θεωρητικά, μπορούμε να έχουμε τόσα λεγόμενα Κλάσεις Container Aware (δηλαδή κλάσεις που περιέχουν αναφορά στο κοντέινερ). Για παράδειγμα, ο ελεγκτής μας, μέσα στον οποίο μπορούμε να λάβουμε οποιαδήποτε υπηρεσία.
Ας δούμε γιατί αυτό είναι κακό.
Πρώτα, ξανά δοκιμή. Αντί να δημιουργείτε mocks μόνο για τις κλάσεις που χρησιμοποιούνται στις δοκιμές, θα πρέπει να κοροϊδέψετε ολόκληρο το δοχείο ή να χρησιμοποιήσετε ένα πραγματικό δοχείο. Η πρώτη επιλογή δεν σας ταιριάζει, γιατί... πρέπει να γράψεις πολύ περιττό κώδικα σε δοκιμές, δεύτερον, γιατί έρχεται σε αντίθεση με τις αρχές της δοκιμής μονάδας και μπορεί να οδηγήσει σε πρόσθετο κόστος για τη συντήρηση των δοκιμών.
Δεύτερον, θα είναι δύσκολο για εμάς να αναπαραστήσουμε. Αλλάζοντας οποιαδήποτε υπηρεσία (ή ServiceDefinition) στο κοντέινερ, θα αναγκαστούμε να ελέγξουμε και όλες τις εξαρτημένες υπηρεσίες. Και αυτό το πρόβλημα δεν μπορεί να λυθεί με τη βοήθεια ενός IDE. Η εύρεση τέτοιων θέσεων σε όλη την εφαρμογή δεν θα είναι τόσο εύκολη. Εκτός από τις εξαρτημένες υπηρεσίες, θα χρειαστεί επίσης να ελέγξετε όλα τα σημεία όπου λαμβάνεται η ανακατασκευασμένη υπηρεσία από το κοντέινερ.
Λοιπόν, ο τρίτος λόγος είναι ότι η ανεξέλεγκτη απόσυρση υπηρεσιών από το κοντέινερ αργά ή γρήγορα θα οδηγήσει σε χάος στον κώδικα και περιττή σύγχυση. Αυτό είναι δύσκολο να εξηγηθεί, απλά θα χρειαστεί να αφιερώσετε όλο και περισσότερο χρόνο για να κατανοήσετε πώς λειτουργεί αυτή ή η άλλη υπηρεσία, με άλλα λόγια, μπορείτε να κατανοήσετε πλήρως τι κάνει ή πώς λειτουργεί μια τάξη μόνο διαβάζοντας ολόκληρο τον πηγαίο κώδικα.

Ενεση εξάρτησης

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

$container["google_finder"] = function() use ($container) ( return new Controller(Grabber $grabber); );

Και απαλλαγείτε από το δοχείο στον ελεγκτή:

Ελεγκτής κλάσης ( ιδιωτικό $finder; δημόσια συνάρτηση __construct(GoogleFinder $finder) ( $this->finder = $finder; ) ενέργεια δημόσιας συνάρτησης() ( /* Μερικά πράγματα */ $results = $this->finder->find( "συμβολοσειρά αναζήτησης"); /* Κάντε κάτι με αποτελέσματα */ ) )

Αυτή η προσέγγιση (όταν η πρόσβαση στο Service Container δεν παρέχεται σε κλάσεις πελάτη) ονομάζεται Dependency Injection. Αλλά αυτό το πρότυπο έχει επίσης πλεονεκτήματα και μειονεκτήματα. Εφόσον τηρούμε την αρχή της ενιαίας ευθύνης, ο κώδικας φαίνεται πολύ όμορφος. Πρώτα απ 'όλα, απαλλαγήκαμε από το κοντέινερ στις κλάσεις πελάτη, κάνοντας τον κώδικά τους πολύ πιο σαφή και απλούστερο. Μπορούμε εύκολα να δοκιμάσουμε τον ελεγκτή αντικαθιστώντας τις απαραίτητες εξαρτήσεις. Μπορούμε να δημιουργήσουμε και να δοκιμάσουμε κάθε τάξη ανεξάρτητα από άλλες (συμπεριλαμβανομένων των κλάσεων ελεγκτών) χρησιμοποιώντας μια προσέγγιση TDD ή BDD. Κατά τη δημιουργία δοκιμών, μπορούμε να αφαιρέσουμε την αφαίρεση από το κοντέινερ και αργότερα να προσθέσουμε έναν ορισμό όταν χρειάζεται να χρησιμοποιήσουμε συγκεκριμένες παρουσίες. Όλα αυτά θα κάνουν τον κώδικά μας απλούστερο και σαφέστερο και τη δοκιμή πιο διαφανή.
Είναι όμως απαραίτητο να αναφέρουμε και την άλλη όψη του νομίσματος. Το γεγονός είναι ότι οι ελεγκτές είναι πολύ συγκεκριμένες κλάσεις. Ας ξεκινήσουμε με το γεγονός ότι ο ελεγκτής, κατά κανόνα, περιέχει ένα σύνολο ενεργειών, πράγμα που σημαίνει ότι παραβιάζει την αρχή της ενιαίας ευθύνης. Ως αποτέλεσμα, η κλάση ελεγκτή μπορεί να έχει πολλές περισσότερες εξαρτήσεις από αυτές που είναι απαραίτητες για την εκτέλεση μιας συγκεκριμένης ενέργειας. Η χρήση τεμπέλης αρχικοποίησης (το αντικείμενο εγκαινιάζεται κατά τη στιγμή της πρώτης χρήσης του και πριν από αυτό χρησιμοποιείται ένας ελαφρύς διακομιστής μεσολάβησης) επιλύει το πρόβλημα απόδοσης σε κάποιο βαθμό. Αλλά από αρχιτεκτονική άποψη, η δημιουργία πολλών εξαρτήσεων από έναν ελεγκτή δεν είναι επίσης απολύτως σωστή. Επιπλέον, η δοκιμή ελεγκτών είναι συνήθως μια περιττή λειτουργία. Όλα, φυσικά, εξαρτώνται από τον τρόπο οργάνωσης των δοκιμών στην αίτησή σας και από το πώς αισθάνεστε εσείς για αυτό.
Από την προηγούμενη παράγραφο, καταλάβατε ότι η χρήση του Dependency Injection δεν εξαλείφει εντελώς τα αρχιτεκτονικά προβλήματα. Επομένως, σκεφτείτε πώς θα είναι πιο βολικό για εσάς, εάν θα αποθηκεύσετε έναν σύνδεσμο προς το κοντέινερ σε ελεγκτές ή όχι. Δεν υπάρχει ενιαία σωστή λύση εδώ. Νομίζω ότι και οι δύο προσεγγίσεις είναι καλές, εφόσον ο κώδικας του ελεγκτή παραμένει απλός. Αλλά, σίγουρα, δεν θα πρέπει να δημιουργήσετε υπηρεσίες Conatiner Aware επιπλέον των ελεγκτών.

συμπεράσματα

Λοιπόν, ήρθε η ώρα να συνοψίσουμε όλα όσα έχουν ειπωθεί. Και έχουν ειπωθεί πολλά... :)
Έτσι, για να δομήσουμε το έργο της δημιουργίας αντικειμένων, μπορούμε να χρησιμοποιήσουμε τα ακόλουθα μοτίβα:
  • Αρχείο: Το πρότυπο έχει προφανή μειονεκτήματα, το πιο βασικό από τα οποία είναι η ανάγκη δημιουργίας αντικειμένων πριν τα τοποθετήσετε σε ένα κοινό δοχείο. Προφανώς, θα έχουμε περισσότερα προβλήματα παρά οφέλη από τη χρήση του. Αυτή σαφώς δεν είναι η καλύτερη χρήση του προτύπου.
  • Εργοστασιακή μέθοδος: Το κύριο πλεονέκτημα του μοτίβου: τα αντικείμενα δημιουργούνται ρητά. Το κύριο μειονέκτημα: οι ελεγκτές είτε πρέπει να ανησυχούν για τη δημιουργία εργοστασίων οι ίδιοι, κάτι που δεν λύνει πλήρως το πρόβλημα της κωδικοποίησης των ονομάτων κλάσεων, είτε το πλαίσιο πρέπει να είναι υπεύθυνο για την παροχή στους ελεγκτές με όλα τα απαραίτητα εργοστάσια, κάτι που δεν θα είναι τόσο προφανές. Δεν υπάρχει δυνατότητα κεντρικής διαχείρισης της διαδικασίας δημιουργίας αντικειμένων.
  • Υπηρεσία εντοπισμού: Ένας πιο προηγμένος τρόπος ελέγχου της δημιουργίας αντικειμένων. Ένα επιπλέον επίπεδο αφαίρεσης μπορεί να χρησιμοποιηθεί για την αυτοματοποίηση κοινών εργασιών που συναντώνται κατά τη δημιουργία αντικειμένων. Για παράδειγμα:
    κλάση ServiceContainer επεκτείνει το ArrayObject ( δημόσια συνάρτηση get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj instanceof RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) return $obj; ) ρίχνει το νέο \RuntimeException("Δεν μπορώ να βρω τον ορισμό της υπηρεσίας κάτω από το κλειδί [ $key ]"); ) )
    Το μειονέκτημα του Service Locator είναι ότι το δημόσιο API των κλάσεων παύει να είναι ενημερωτικό. Είναι απαραίτητο να διαβάσετε ολόκληρο τον κώδικα της τάξης για να κατανοήσετε ποιες υπηρεσίες χρησιμοποιούνται σε αυτόν. Μια κλάση που περιέχει μια αναφορά σε ένα κοντέινερ είναι πιο δύσκολο να δοκιμαστεί.
  • Ενεση εξάρτησης: Ουσιαστικά μπορούμε να χρησιμοποιήσουμε το ίδιο Service Container με το προηγούμενο μοτίβο. Η διαφορά είναι πώς χρησιμοποιείται αυτό το δοχείο. Εάν αποφύγουμε να εξαρτήσουμε τις κλάσεις από το κοντέινερ, θα λάβουμε ένα σαφές και σαφές API κλάσης.
Δεν είναι μόνο αυτό που θα ήθελα να σας πω για το πρόβλημα της δημιουργίας αντικειμένων σε εφαρμογές PHP. Υπάρχει επίσης το πρότυπο Prototype, δεν εξετάσαμε τη χρήση του Reflection API, αφήσαμε στην άκρη το πρόβλημα της τεμπέλης φόρτωσης των υπηρεσιών και πολλές άλλες αποχρώσεις. Το άρθρο ήταν αρκετά μεγάλο, οπότε θα το ολοκληρώσω :)
Ήθελα να δείξω ότι το Dependency Injection και άλλα μοτίβα δεν είναι τόσο περίπλοκα όσο πιστεύεται συνήθως.
Αν μιλάμε για Dependency Injection, τότε υπάρχουν για παράδειγμα υλοποιήσεις KISS αυτού του μοτίβου

Αγγίζοντας τη δομή της μελλοντικής βάσης δεδομένων. Έχει γίνει μια αρχή και δεν μπορούμε να υποχωρήσουμε και δεν το σκέφτομαι καν.

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

Η αρχή του χρόνου

Προς το παρόν, έχουμε μόνο κάποιες ιδέες και κατανόηση για τη λειτουργία του συστήματος που θέλουμε να εφαρμόσουμε, αλλά δεν υπάρχει ακόμη η ίδια υλοποίηση. Δεν έχουμε τίποτα να δουλέψουμε: δεν έχουμε καμία λειτουργικότητα - και, όπως θυμάστε, τη χωρίσαμε σε 2 μέρη: εσωτερικό και εξωτερικό. Το αλφάβητο απαιτεί γράμματα, αλλά η εξωτερική λειτουργικότητα απαιτεί εσωτερική λειτουργικότητα—από εκεί θα ξεκινήσουμε.

Όχι όμως τόσο γρήγορα. Για να λειτουργήσει πρέπει να πάτε λίγο πιο βαθιά. Το σύστημά μας αντιπροσωπεύει μια ιεραρχία και κάθε ιεραρχικό σύστημα έχει μια αρχή: ένα σημείο προσάρτησης στο Linux, έναν τοπικό δίσκο στα Windows, ένα σύστημα μιας πολιτείας, μιας εταιρείας, ενός εκπαιδευτικού ιδρύματος κ.λπ. Κάθε στοιχείο ενός τέτοιου συστήματος είναι υποδεέστερο σε κάποιον και μπορεί να έχει πολλούς υφισταμένους, και για να απευθυνθεί στους γείτονές του και στους υφισταμένους τους χρησιμοποιεί ανωτέρους ή την ίδια την αρχή. Ένα καλό παράδειγμα ιεραρχικού συστήματος είναι το οικογενειακό δέντρο: επιλέγεται ένα σημείο εκκίνησης - κάποιος πρόγονος - και φεύγουμε. Στο σύστημά μας, χρειαζόμαστε επίσης ένα σημείο εκκίνησης από το οποίο θα αναπτύξουμε υποκαταστήματα - modules, plugins κ.λπ. Χρειαζόμαστε κάποιου είδους διεπαφή μέσω της οποίας θα «επικοινωνούν» όλες οι μονάδες μας. Για περαιτέρω εργασία, πρέπει να εξοικειωθούμε με την έννοια " μοτίβο σχεδίασης" και μερικές από τις υλοποιήσεις τους.

Σχεδιαστικά πρότυπα

Υπάρχουν πάρα πολλά άρθρα σχετικά με το τι είναι και ποιες ποικιλίες υπάρχουν· το θέμα είναι αρκετά μπερδεμένο και δεν θα σας πω τίποτα νέο. Στο αγαπημένο μου Wiki υπάρχουν πληροφορίες για αυτό το θέμα: μια άμαξα με τσουλήθρα και λίγο περισσότερο.

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

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

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

Μοναχικός (ή singleton) είναι ένα μοτίβο που διασφαλίζει ότι μπορεί να υπάρχει μόνο μία παρουσία μιας κλάσης. Δεν μπορεί να αντιγραφεί, να τεθεί σε κατάσταση ύπνου ή να ξυπνήσει (μιλώντας για τη μαγεία της PHP: __clone(), __sleep(), __wakeup()). Το Singleton έχει ένα παγκόσμιο σημείο πρόσβασης.

Οι ορισμοί δεν είναι πλήρεις ή γενικευμένοι, αλλά αυτό αρκεί για κατανόηση. Έτσι κι αλλιώς δεν τα χρειαζόμαστε ξεχωριστά. Μας ενδιαφέρουν οι δυνατότητες καθενός από αυτά τα μοτίβα, αλλά σε μια κατηγορία: ένα τέτοιο μοτίβο ονομάζεται singleton registry ή Singleton Registry.

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

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

Λοιπόν, αρκετά λόγια, ας δημιουργήσουμε!

Πρώτες γραμμές

Δεδομένου ότι αυτή η κλάση θα σχετίζεται με τη λειτουργικότητα του πυρήνα, θα ξεκινήσουμε δημιουργώντας έναν φάκελο στη ρίζα του έργου μας που ονομάζεται core στον οποίο θα τοποθετήσουμε όλες τις κλάσεις των μονάδων του πυρήνα. Ξεκινάμε με το μητρώο, οπότε ας καλέσουμε το αρχείο registry.php

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

Εφόσον προστατεύουμε κάτι που είναι συνδεδεμένο, θα ονομάσουμε τη σταθερά _PLUGSECURE_:

Εάν (!defined("_PLUGSECURE_")) (die("Η απευθείας κλήση μονάδας απαγορεύεται!"); )

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

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

Διασύνδεση StorableObject ( δημόσια στατική συνάρτηση getClassName(); )

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

Ήρθε η ώρα για την τάξη του ίδιου του μητρώου singles. Θα ξεκινήσουμε δηλώνοντας την κλάση και μερικές από τις μεταβλητές της:

Το Μητρώο κλάσης υλοποιεί το StorableObject ( //όνομα μονάδας αναγνώσιμο ιδιωτικό στατικό $className = "Μητρώο"; //παρουσίαση μητρώου ιδιωτικό στατικό $instance; //πίνακας αντικειμένων private static $objects = array();

Μέχρι στιγμής όλα είναι λογικά και κατανοητά. Τώρα, όπως θυμάστε, έχουμε ένα μητρώο με ιδιότητες singleton, οπότε ας γράψουμε αμέσως μια συνάρτηση που θα μας επιτρέψει να εργαστούμε με το μητρώο με αυτόν τον τρόπο:

Δημόσια στατική συνάρτηση singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Κυριολεκτικά: η συνάρτηση ελέγχει εάν υπάρχει μια παρουσία του μητρώου μας: εάν όχι, τη δημιουργεί και την επιστρέφει· εάν υπάρχει ήδη, απλώς την επιστρέφει. Σε αυτήν την περίπτωση, δεν χρειαζόμαστε κάποια μαγεία, επομένως για προστασία θα το δηλώσουμε ως ιδιωτικό:

Ιδιωτική συνάρτηση __construct()() ιδιωτική συνάρτηση __clone()() ιδιωτική συνάρτηση __wakeup()() ιδιωτική συνάρτηση __sleep() ()

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

//$object - διαδρομή προς το συνδεδεμένο αντικείμενο //$key - κλειδί πρόσβασης στο αντικείμενο στη δημόσια συνάρτηση καταχωρητή addObject($key, $object) ( require_once($object); //δημιουργία ενός αντικειμένου σε έναν πίνακα αντικειμένων self::$objects[ $key] = new $key(self::$instance); ) //μια εναλλακτική μέθοδος μέσω της μαγικής δημόσιας συνάρτησης __set($key, $object) ( $this->addObject($key, $ αντικείμενο);)

Τώρα, για να προσθέσουμε ένα αντικείμενο στο μητρώο μας, μπορούμε να χρησιμοποιήσουμε δύο τύπους καταχωρήσεων (ας πούμε ότι έχουμε ήδη δημιουργήσει μια παρουσία μητρώου $registry και θέλουμε να προσθέσουμε το αρχείο config.php):

$registry->addObject("config", "/core/config.php"); //κανονική μέθοδος $registry->config = "/core/config.php"; //μέσω της μαγικής συνάρτησης PHP __set()

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

Ποια εγγραφή θα χρησιμοποιήσετε εξαρτάται από εσάς. Προτιμώ την ηχογράφηση μέσω της μαγικής μεθόδου - είναι πιο «όμορφη» και πιο σύντομη.

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

//πάρτε το αντικείμενο από τον καταχωρητή //$key - το κλειδί στη δημόσια συνάρτηση πίνακα getObject($key) ( //ελέγξτε αν η μεταβλητή είναι αντικείμενο if (is_object(self::$objects[$key])) ( //αν ναι, τότε επιστρέφουμε αυτό το αντικείμενο return self::$objects[$key];) ) //μια παρόμοια μέθοδο μέσω της μαγικής δημόσιας συνάρτησης __get($key) ( if (is_object(self::$objects[$ κλειδί])) ( επιστρέφει τον εαυτό του: :$objects[$key]; ) )

Όπως και με το ρυθμιστή, για να αποκτήσουμε πρόσβαση στο αντικείμενο θα έχουμε 2 ισοδύναμες καταχωρήσεις:

$registry->getObject("config"); //κανονική μέθοδος $registry->config; //μέσω της μαγικής συνάρτησης PHP __get()

Ο προσεκτικός αναγνώστης θα κάνει αμέσως την ερώτηση: γιατί στη μαγική συνάρτηση __set() καλώ απλώς μια συνάρτηση προσθήκης κανονικού (μη μαγικού) αντικειμένου, αλλά στον λήπτη __get() αντιγράφω τον κωδικό της συνάρτησης getObject() αντί για την ίδια κλήση;Ειλικρινά, δεν μπορώ να απαντήσω σε αυτήν την ερώτηση με αρκετή ακρίβεια, θα πω απλώς ότι είχα προβλήματα όταν εργαζόμουν με τη μαγεία __get() σε άλλες ενότητες, αλλά όταν ξαναγράφω τον κώδικα "προκαταβολικά" δεν υπάρχουν τέτοια προβλήματα.

Ίσως γι' αυτό έβλεπα συχνά σε άρθρα επικρίσεις για μαγικές μεθόδους PHP και συμβουλές για να αποφύγετε τη χρήση τους.

«Όλη η μαγεία έχει ένα τίμημα». © Rumplestiltskin

Σε αυτό το στάδιο, η κύρια λειτουργικότητα του μητρώου μας είναι ήδη έτοιμη: μπορούμε να δημιουργήσουμε ένα μόνο στιγμιότυπο του μητρώου, να προσθέσουμε αντικείμενα και να αποκτήσουμε πρόσβαση σε αυτά τόσο χρησιμοποιώντας συμβατικές μεθόδους όσο και μέσω των μαγικών μεθόδων της γλώσσας PHP. «Τι γίνεται με τη διαγραφή;»— δεν θα χρειαστούμε αυτήν τη λειτουργία προς το παρόν και δεν είμαι σίγουρος ότι θα αλλάξει κάτι στο μέλλον. Στο τέλος, μπορούμε πάντα να προσθέσουμε την απαραίτητη λειτουργικότητα. Αλλά αν τώρα προσπαθήσουμε να δημιουργήσουμε μια παρουσία του μητρώου μας,

$registry = Μητρώο::singleton();

θα λάβουμε ένα σφάλμα:

Μοιραίο λάθος: Το Μητρώο κλάσης περιέχει 1 αφηρημένη μέθοδο και επομένως πρέπει να δηλωθεί ως αφηρημένη ή να εφαρμόσει τις υπόλοιπες μεθόδους (StorableObject::getClassName) στο ...

Όλα επειδή ξεχάσαμε να γράψουμε μια απαιτούμενη συνάρτηση. Θυμάστε στην αρχή που μίλησα για μια συνάρτηση που επιστρέφει το όνομα της μονάδας; Αυτό είναι που μένει να προστεθεί για πλήρη λειτουργικότητα. Είναι απλό:

Δημόσια στατική συνάρτηση getClassName() (επιστρέφει self::$className; )

Τώρα δεν πρέπει να υπάρχουν λάθη. Προτείνω να προσθέσετε μια ακόμη λειτουργία, δεν απαιτείται, αλλά αργά ή γρήγορα μπορεί να σας φανεί χρήσιμη· θα τη χρησιμοποιήσουμε στο μέλλον για έλεγχο και διόρθωση σφαλμάτων. Η συνάρτηση θα επιστρέψει τα ονόματα όλων των αντικειμένων (modules) που έχουν προστεθεί στο μητρώο μας:

Δημόσια συνάρτηση getObjectsList() ( //ο πίνακας που θα επιστρέψουμε $names = array(); //πάρτε το όνομα κάθε αντικειμένου από τον πίνακα αντικειμένων foreach(self::$objects ως $obj) ( $names = $ obj->getClassName() ;) //προσθέστε το όνομα της λειτουργικής μονάδας καταχωρητή στον πίνακα array_push($names, self::getClassName()); //και επιστρέψτε $names; )

Αυτό είναι όλο. Αυτό συμπληρώνει το μητρώο. Ας ελέγξουμε τη δουλειά του; Κατά τον έλεγχο, θα χρειαστεί να συνδέσουμε κάτι - ας υπάρχει ένα αρχείο διαμόρφωσης. Δημιουργήστε ένα νέο αρχείο core/config.php και προσθέστε το ελάχιστο περιεχόμενο που απαιτεί το μητρώο μας:

//μην ξεχάσετε να ελέγξετε τη σταθερά if (!defined("_PLUGSECURE_")) (die("Η άμεση κλήση μονάδας απαγορεύεται!"); ) class Config ( //όνομα μονάδας, αναγνώσιμη ιδιωτική στατική $className = "Config "; δημόσια στατική συνάρτηση getClassName() ( return self::$className; ) )

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

Define("_PLUGSECURE_", true); //καθόρισε μια σταθερά για προστασία από την άμεση πρόσβαση σε αντικείμενα require_once "/core/registry.php"; //σύνδεσε τον καταχωρητή $registry = Registry::singleton(); //δημιούργησε έναν καταχωρητή singleton παρουσία $registry->config = "/core/config.php"; //σύνδεση, μέχρι στιγμής άχρηστη, διαμόρφωση //εμφάνιση των ονομάτων των συνδεδεμένων μονάδων echo " Συνδεδεμένος"; foreach ($registry->

  • " . $names ."
  • "; }

    Ή, εάν εξακολουθείτε να αποφεύγετε τη μαγεία, τότε η 5η γραμμή μπορεί να αντικατασταθεί με μια εναλλακτική μέθοδο:

    Define("_PLUGSECURE_", true); //καθόρισε μια σταθερά για προστασία από την άμεση πρόσβαση σε αντικείμενα require_once "/core/registry.php"; //σύνδεσε τον καταχωρητή $registry = Registry::singleton(); //δημιούργησε μια παρουσία μητρώου singleton $registry->addObject("config", "/core/config.php"); //σύνδεση, μέχρι στιγμής άχρηστη, διαμόρφωση //εμφάνιση των ονομάτων των συνδεδεμένων μονάδων echo " Συνδεδεμένος"; foreach ($registry->getObjectsList() ως $names) ( echo "

  • " . $names ."
  • "; }

    Τώρα ανοίξτε το πρόγραμμα περιήγησης και γράψτε http://localhost/index.php ή απλά http://localhost/ στη γραμμή διευθύνσεων (σχετικό εάν χρησιμοποιείτε τυπικό Open Server ή παρόμοιες ρυθμίσεις διακομιστή web)

    Ως αποτέλεσμα, θα πρέπει να δούμε κάτι σαν αυτό:

    Όπως μπορείτε να δείτε, δεν υπάρχουν σφάλματα, πράγμα που σημαίνει ότι όλα λειτουργούν, για το οποίο σας συγχαίρω :)

    Σήμερα θα σταματήσουμε σε αυτό. Στο επόμενο άρθρο, θα επιστρέψουμε στη βάση δεδομένων και θα γράψουμε μια τάξη για εργασία με το MySQL SUDB, θα το συνδέσουμε στο μητρώο και θα δοκιμάσουμε την εργασία στην πράξη. Τα λέμε!

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

    Ας εξετάσουμε και τις δύο επιλογές με τη σειρά.

    Αυτό που ονομάζεται "καθαρό μητρώο" ή απλά Μητρώο είναι μια υλοποίηση μιας κλάσης με στατική διεπαφή. Η κύρια διαφορά από το μοτίβο Singleton είναι ότι εμποδίζει τη δυνατότητα δημιουργίας τουλάχιστον μιας παρουσίας μιας κλάσης. Λαμβάνοντας υπόψη αυτό, δεν υπάρχει λόγος να κρύβουμε τις μαγικές μεθόδους __clone() και __wakeup() πίσω από τον ιδιωτικό ή προστατευμένο τροποποιητή.

    Τάξη μητρώουπρέπει να έχει δύο στατικές μεθόδους - έναν λήπτη και έναν ρυθμιστή. Ο ρυθμιστής τοποθετεί το αντικείμενο που έχει περάσει σε αποθήκευση με δέσμευση στο δεδομένο κλειδί. Ο λήπτης, κατά συνέπεια, επιστρέφει ένα αντικείμενο από το κατάστημα. Ένα κατάστημα δεν είναι τίποτα άλλο παρά ένας συσχετιστικός πίνακας κλειδιών-τιμών.

    Για πλήρη έλεγχο του μητρώου, εισάγεται ένα άλλο στοιχείο διεπαφής - μια μέθοδος που σας επιτρέπει να διαγράψετε ένα αντικείμενο από τον χώρο αποθήκευσης.

    Εκτός από προβλήματα πανομοιότυπα με το μοτίβο Singleton, υπάρχουν δύο ακόμη:

    • εισαγωγή ενός άλλου τύπου εξάρτησης - από κλειδιά μητρώου.
    • δύο διαφορετικά κλειδιά μητρώου μπορούν να έχουν αναφορά στο ίδιο αντικείμενο

    Στην πρώτη περίπτωση, είναι αδύνατο να αποφευχθεί η πρόσθετη εξάρτηση. Σε κάποιο βαθμό, δεσμευόμαστε με ονόματα κλειδιών.

    Το δεύτερο πρόβλημα επιλύεται εισάγοντας έναν έλεγχο στη μέθοδο Registry::set():

    Δημόσιο σύνολο στατικών συναρτήσεων ($key, $item) ( if (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry ως $val) ( if ($val === $item) ( ρίχνει νέα Εξαίρεση ("Το στοιχείο υπάρχει ήδη"); ) ) self::$_registry[$key] = $item; ) )

    « Καθαρίστε το μοτίβο μητρώου"δημιουργεί ένα άλλο πρόβλημα - την αύξηση της εξάρτησης λόγω της ανάγκης πρόσβασης στον ρυθμιστή και τον λήπτη μέσω του ονόματος της κλάσης. Δεν μπορείτε να δημιουργήσετε μια αναφορά σε ένα αντικείμενο και να εργαστείτε με αυτό, όπως συνέβη με το μοτίβο Singleton, όταν αυτή η προσέγγιση ήταν διαθέσιμη:

    $instance = Singleton::getInstance(); $instance->Foo();

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

    Για την επίλυση αυτού του ζητήματος υπάρχει Εφαρμογή μητρώου Singleton, το οποίο δεν αρέσει σε πολλούς ανθρώπους επειδή φαίνεται σαν περιττός κώδικας. Νομίζω ότι ο λόγος αυτής της στάσης είναι κάποια παρανόηση των αρχών του OOP ή μια σκόπιμη περιφρόνηση τους.

    _registry[$key] = $object; ) στατική δημόσια συνάρτηση get($key) ( return self::getInstance()->_registry[$key]; ) private function __wakeup() ( ) private function __construct() ( ) private function __clone() ( ) ) ?>

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

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

    Αποφάσισα να γράψω εν συντομία για τα μοτίβα που χρησιμοποιούνται συχνά στη ζωή μας, περισσότερα παραδείγματα, λιγότερο νερό, πάμε.

    Μοναδικό χαρτί

    Το κύριο σημείο του "single" είναι ότι όταν λέτε "Χρειάζομαι ένα τηλεφωνικό κέντρο", θα σας έλεγαν "Έχει ήδη κατασκευαστεί εκεί" και όχι "Ας το ξαναχτίσουμε". Ένας «μοναχικός» είναι πάντα μόνος.

    Κλάση Singleton ( ιδιωτική στατική $instance = null; ιδιωτική συνάρτηση __construct())( /* ... @return Singleton */ ) // Προστασία έναντι δημιουργίας μέσω της νέας ιδιωτικής συνάρτησης Singleton __clone() ( /* ... @return Singleton * / ) // Προστασία έναντι δημιουργίας μέσω κλωνοποίησης ιδιωτικής συνάρτησης __wakeup() ( /* ... @return Singleton */ ) // Προστασία έναντι δημιουργίας μέσω μη σειριακής δημόσιας στατικής συνάρτησης getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = νέος εαυτός; ) return self::$instance; ) )

    Μητρώο (μητρώο, ημερολόγιο εγγραφών)

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

    Μητρώο κλάσης ( ιδιωτικό $registry = array(); δημόσιο σύνολο συναρτήσεων($key, $object) ( $this->registry[$key] = $object; ) δημόσια συνάρτηση get($key) ( return $this->registry [$key]; ))

    Μητρώο Singleton- μην μπερδεύεστε με)

    Το "μητρώο" είναι συχνά "μοναχικό", αλλά δεν χρειάζεται να είναι πάντα έτσι. Για παράδειγμα, μπορούμε να δημιουργήσουμε πολλά ημερολόγια στο λογιστήριο, στον έναν υπάλληλο από το «Α» στο «Μ», στον άλλο από το «Ν» στο «Ω». Κάθε τέτοιο περιοδικό θα είναι ένα «μητρώο», αλλά όχι ένα «μονό», επειδή υπάρχουν ήδη 2 περιοδικά.

    Κλάση SingletonRegistry ( private static $instance = null; private $registry = array(); private function __construct() ( /* ... @return Singleton */ ) // Προστασία από δημιουργία μέσω της νέας ιδιωτικής συνάρτησης Singleton __clone() ( / * ... @return Singleton */ ) // Προστασία από δημιουργία μέσω κλωνοποίησης ιδιωτικής συνάρτησης __wakeup() ( /* ... @return Singleton */ ) // Προστασία έναντι δημιουργίας μέσω μη σειριακής δημόσιας στατικής συνάρτησης getInstance() ( if ( is_null(self::$instance)) ( self::$instance = νέος εαυτός; ) return self::$instance; ) δημόσιο σύνολο συναρτήσεων ($key, $object) ( $this->registry[$key] = $ αντικείμενο; ) δημόσια συνάρτηση get($key) (επιστροφή $this->registry[$key]; ) )

    Multiton (δεξαμενή «μονών») ή με άλλα λόγιαΜητρώο Singleton ) - μην συγχέετε με το Singleton Registry

    Συχνά το "μητρώο" χρησιμοποιείται ειδικά για την αποθήκευση "μονών". Αλλά επειδή το μοτίβο "μητρώου" δεν είναι ένα "γεννητικό μοτίβο", αλλά θα ήθελα να εξετάσω το "μητρώο" σε σχέση με το "singleton".Γι' αυτό καταλήξαμε σε ένα μοτίβο Multiton, που σύμφωνα μεΣτον πυρήνα του, είναι ένα "μητρώο" που περιέχει πολλά "single", καθένα από τα οποία έχει το δικό του "όνομα" με το οποίο μπορεί να προσπελαστεί.

    Μικρός: σας επιτρέπει να δημιουργήσετε αντικείμενα αυτής της κλάσης, αλλά μόνο εάν ονομάσετε το αντικείμενο. Δεν υπάρχει πραγματικό παράδειγμα, αλλά βρήκα το ακόλουθο παράδειγμα στο Διαδίκτυο:

    Βάση δεδομένων κλάσης ( ιδιωτική στατική $instances = array(); ιδιωτική συνάρτηση __construct() ( ) ιδιωτική συνάρτηση __clone() ( ) δημόσια στατική συνάρτηση getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Βάση δεδομένων::getInstance("master"); var_dump($master); // αντικείμενο(Βάση δεδομένων)#1 (0) ( ) $logger = Βάση δεδομένων::getInstance("logger"); var_dump($logger); // αντικείμενο(Βάση δεδομένων)#2 (0) ( ) $masterDupe = Βάση δεδομένων::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Μοιραίο σφάλμα: Κλήση σε ιδιωτική βάση δεδομένων::__construct() από μη έγκυρο περιβάλλον $dbFatalError = new Database(); // PHP Μοιραίο σφάλμα: Κλήση σε ιδιωτική βάση δεδομένων::__clone() $dbCloneError = κλωνοποίηση $masterDupe;

    Πισίνα αντικειμένων

    Ουσιαστικά αυτό το μοτίβο είναι ένα «μητρώο» που αποθηκεύει μόνο αντικείμενα, χωρίς συμβολοσειρές, πίνακες κ.λπ. τύπους δεδομένων.

    Εργοστάσιο

    Η ουσία του μοτίβου περιγράφεται σχεδόν πλήρως από το όνομά του. Όταν χρειάζεται να προμηθευτείτε κάποια αντικείμενα, όπως κουτιά χυμών, δεν χρειάζεται να γνωρίζετε πώς κατασκευάζονται σε ένα εργοστάσιο. Απλώς λέτε, «δώστε μου ένα κουτί με χυμό πορτοκαλιού» και το «εργοστάσιο» σας επιστρέφει το απαιτούμενο πακέτο. Πως? Όλα αυτά αποφασίζονται από το ίδιο το εργοστάσιο, για παράδειγμα, "αντιγράφει" ένα ήδη υπάρχον πρότυπο. Ο κύριος σκοπός του "εργοστασίου" είναι να καταστήσει δυνατή, εάν είναι απαραίτητο, την αλλαγή της διαδικασίας "εμφάνισης" μιας συσκευασίας χυμού και ο ίδιος ο καταναλωτής δεν χρειάζεται να του πει τίποτα σχετικά με αυτό, ώστε να μπορεί να το ζητήσει όπως και πριν. Κατά κανόνα, ένα εργοστάσιο ασχολείται με την «παραγωγή» μόνο ενός τύπου «προϊόντος». Δεν συνιστάται η δημιουργία «εργοστάσιο χυμών» λαμβάνοντας υπόψη την παραγωγή ελαστικών αυτοκινήτων. Όπως και στη ζωή, το εργοστασιακό μοτίβο δημιουργείται συχνά από ένα μόνο άτομο.

    Abstract class AnimalAbstract ( προστατευμένα $species; δημόσια συνάρτηση getSpecies() ( return $this->species; ) ) class Cat extensions AnimalAbstract ( protected $species = "cat"; ) class Dog extensions AnimalAbstract ( protected $species = "dog"; ) class AnimalFactory ( δημόσια στατική λειτουργία εργοστάσιο ($animal) ( διακόπτης ($animal) ( case "cat": $obj = new Cat(); break; case "dog": $obj = new Dog(); break; default : throw new Exception("Το εργοστάσιο ζώων δεν μπόρεσε να δημιουργήσει ζώο του είδους "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // object(Cat)#1 echo $cat->getSpecies(); // cat $dog = AnimalFactory::factory("dog"); // object(Dog)#1 echo $dog->getSpecies(); // dog $hippo = AnimalFactory::factory("hippopotamus"); // Αυτό θα δημιουργήσει μια Εξαίρεση

    Θα ήθελα να επιστήσω την προσοχή σας στο γεγονός ότι η εργοστασιακή μέθοδος είναι επίσης ένα μοτίβο· ονομάζεται μέθοδος Factory.

    οικοδόμος (οικοδόμος)

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

    Class Bottle ( public $name; public $liters; ) /** * όλα τα builders πρέπει */ interface BottleBuilderInterface ( δημόσια συνάρτηση setName(); δημόσια συνάρτηση setLiters(); δημόσια συνάρτηση getResult(); ) κλάση CocaColaBuilder υλοποιεί BottleBuilderInterface ( ιδιωτικό $ μπουκάλι; δημόσια συνάρτηση __construct() ( $this->bottle = new Bottle(); ) δημόσια συνάρτηση setName($value) ($this->bottle->name = $value; ) δημόσια συνάρτηση setLiters($value) ($ this->bottle->liters = $value; ) δημόσια συνάρτηση getResult() ( return $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Πρωτότυπο

    Μοιάζει με εργοστάσιο, χρησιμεύει επίσης για τη δημιουργία αντικειμένων, αλλά με μια ελαφρώς διαφορετική προσέγγιση. Φαντάσου τον εαυτό σου σε ένα μπαρ, πίνεις μπύρα και σου τελειώνει, λες στον μπάρμαν - κάνε μου άλλη μια του ίδιου είδους. Ο μπάρμαν με τη σειρά του κοιτάζει την μπύρα που πίνετε και κάνει ένα αντίγραφο όπως ζητήσατε. Η PHP έχει ήδη μια υλοποίηση αυτού του μοτίβου, που ονομάζεται .

    $newJuice = κλώνος $juice;

    Τεμπέλης αρχικοποίηση

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

    Προσαρμογέας ή περιτύλιγμα (προσαρμογέας, περιτύλιγμα)

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

    Κλάση MyClass ( μέθοδος δημόσιας συνάρτησηςA() () ) κλάση MyClassWrapper ( δημόσια συνάρτηση __construct())( $this->myClass = new MyClass(); ) δημόσια συνάρτηση __call($name, $arguments)( Log::info(" Πρόκειται να καλέσετε τη μέθοδο $name."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Ενεση εξάρτησης

    Η ένεση εξάρτησης σάς επιτρέπει να μεταθέσετε μέρος της ευθύνης για κάποια λειτουργικότητα σε άλλα αντικείμενα. Για παράδειγμα, εάν πρέπει να προσλάβουμε νέο προσωπικό, τότε δεν μπορούμε να δημιουργήσουμε το δικό μας τμήμα ανθρώπινου δυναμικού, αλλά να εισαγάγουμε εξάρτηση από μια εταιρεία πρόσληψης, η οποία, με τη σειρά της, με το πρώτο μας αίτημα «χρειαζόμαστε ένα άτομο», είτε θα λειτουργήσει ως Το ίδιο το τμήμα Ανθρώπινου Δυναμικού ή θα βρει άλλη εταιρεία (χρησιμοποιώντας έναν «εντοπιστή υπηρεσιών») που θα παρέχει αυτές τις υπηρεσίες.
    Το "Dependency Injection" σάς επιτρέπει να μετατοπίζετε και να ανταλλάσσετε μεμονωμένα μέρη της εταιρείας χωρίς να χάσετε τη συνολική λειτουργικότητα.

    Κλάση AppleJuice () // αυτή η μέθοδος είναι μια πρωτόγονη υλοποίηση του μοτίβου έγχυσης εξάρτησης και περαιτέρω θα δείτε αυτή τη συνάρτηση getBottleJuice()) ( $obj = νέο Χυμός μήλου Χυμός μήλου)( επιστροφή $obj; ) ) $bottleJuice = getBottleJuice();

    Τώρα φανταστείτε ότι δεν θέλουμε πια χυμό μήλου, θέλουμε χυμό πορτοκαλιού.

    Τάξη AppleJuice() Class Χυμός πορτοκάλι() // αυτή η μέθοδος υλοποιεί τη συνάρτηση ένεσης εξάρτησης getBottleJuice())( $obj = νέο Χυμός πορτοκάλι; // ελέγξτε το αντικείμενο, σε περίπτωση που μας γλίστρησαν μπύρα (η μπύρα δεν είναι χυμός) if($obj instanceof Χυμός πορτοκάλι)( επιστροφή $obj; ) )

    Όπως καταλαβαίνετε, έπρεπε να αλλάξουμε όχι μόνο τον τύπο του χυμού, αλλά και τον έλεγχο για τον τύπο του χυμού, κάτι που δεν είναι πολύ βολικό. Είναι πολύ πιο σωστό να χρησιμοποιήσετε την αρχή της αντιστροφής της εξάρτησης:

    Διασύνδεση Juice () Class AppleJuice υλοποιεί Juice () Class OrangeJuice υλοποιεί Juice () συνάρτηση getBottleJuice())( $obj = νέο OrangeJuice; // ελέγξτε το αντικείμενο, σε περίπτωση που μας γλίστρησε μπύρα (η μπύρα δεν είναι χυμός) if($obj παράδειγμα του Χυμός)( επιστροφή $obj; ) )

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

    Υπηρεσία εντοπισμού

    Το "Service Locator" είναι μια μέθοδος υλοποίησης "Dependency Injection". Επιστρέφει διαφορετικούς τύπους αντικειμένων ανάλογα με τον κωδικό προετοιμασίας. Αφήστε το καθήκον να είναι να παραδώσουμε τη συσκευασία χυμού μας, που δημιουργήθηκε από έναν κατασκευαστή, ένα εργοστάσιο ή κάτι άλλο, όπου θέλει ο αγοραστής. Λέμε στον εντοπιστή «δώστε μας μια υπηρεσία παράδοσης» και ζητάμε από την υπηρεσία να παραδώσει τον χυμό στην επιθυμητή διεύθυνση. Σήμερα υπάρχει μία υπηρεσία και αύριο μπορεί να υπάρχει άλλη. Δεν έχει σημασία για εμάς ποια συγκεκριμένη υπηρεσία είναι, είναι σημαντικό για εμάς να γνωρίζουμε ότι αυτή η υπηρεσία θα προσφέρει αυτό που της λέμε και πού της λέμε. Με τη σειρά τους, οι υπηρεσίες εφαρμόζουν το «Παράδοση<предмет>επί<адрес>».

    Εάν μιλάμε για πραγματική ζωή, τότε πιθανώς ένα καλό παράδειγμα ενός Service Locator θα ήταν η επέκταση PDO PHP, επειδή Σήμερα εργαζόμαστε με μια βάση δεδομένων MySQL και αύριο μπορούμε να εργαστούμε με PostgreSQL. Όπως καταλάβατε ήδη, δεν έχει σημασία για την τάξη μας σε ποια βάση δεδομένων στέλνει τα δεδομένα της, είναι σημαντικό να μπορεί να το κάνει.

    $db = νέο ΠΟΠ(" mysql:dbname=test;host=localhost", $user, $pass); $db = νέο ΠΟΠ(" pgsql:dbname=test host=localhost", $user, $pass);

    Η διαφορά μεταξύ του Dependency injection και του Service Locator

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

    Θα προσπαθήσω να σας πω για την εφαρμογή του μοτίβου μητρώου στην PHP. Το μητρώο είναι μια αντικατάσταση OOP για καθολικές μεταβλητές, σχεδιασμένη για την αποθήκευση δεδομένων και τη μεταφορά τους μεταξύ λειτουργικών μονάδων συστήματος. Κατά συνέπεια, είναι προικισμένο με τυπικές ιδιότητες - εγγραφή, ανάγνωση, διαγραφή. Εδώ είναι μια τυπική υλοποίηση.

    Λοιπόν, με αυτόν τον τρόπο έχουμε μια ανόητη αντικατάσταση των μεθόδων $key = $value - Μητρώο::set($key, $value) $key - Registry::get($key) unset($key) - remove Registry::remove ($key ) Απλώς γίνεται ασαφές - γιατί αυτός ο επιπλέον κωδικός. Λοιπόν, ας διδάξουμε την τάξη μας να κάνει αυτό που δεν μπορούν να κάνουν οι καθολικές μεταβλητές. Ας του προσθέσουμε πιπέρι.

    getMessage()); ) Amdy_Registry::unlock("test"); var_dump(Amdy_Registry::get("test")); ?>

    Στις τυπικές εργασίες του μοτίβου, πρόσθεσα τη δυνατότητα αποκλεισμού μιας μεταβλητής από αλλαγές, αυτό είναι πολύ βολικό σε μεγάλα έργα, δεν θα εισαγάγετε τίποτα κατά λάθος. Για παράδειγμα, βολικό για εργασία με βάσεις δεδομένων
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

    Amdy_Regisrtry::set(DB_HANDLE, new PDO(DB_DNS, DB_USER, DB_PASSWORD));
    Amdy_Registry::lock(DB_HANDLE);

    Τώρα για μια εξήγηση του κώδικα, για να αποθηκεύσουμε δεδομένα, χρησιμοποιούμε τη στατική μεταβλητή $data, η μεταβλητή $lock αποθηκεύει δεδομένα σχετικά με κλειδιά που είναι κλειδωμένα για αλλαγή. Στο δίκτυο, ελέγχουμε αν η μεταβλητή είναι κλειδωμένη και την αλλάζουμε ή προσθέτουμε στον καταχωρητή. Κατά τη διαγραφή, ελέγχουμε επίσης το κλείδωμα· ο λήπτης παραμένει αμετάβλητος, με εξαίρεση την προεπιλεγμένη προαιρετική παράμετρο. Λοιπόν, αξίζει να δώσετε προσοχή στον χειρισμό εξαιρέσεων, ο οποίος για κάποιο λόγο χρησιμοποιείται σπάνια. Παρεμπιπτόντως, έχω ήδη ένα προσχέδιο για τις εξαιρέσεις, περιμένετε το άρθρο. Παρακάτω είναι ένα προσχέδιο κώδικα για δοκιμές, εδώ είναι ένα άρθρο σχετικά με τις δοκιμές, δεν θα ήταν κακό να το γράψω, αν και δεν είμαι λάτρης του TDD.

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