Συχνές ερωτήσεις για το DI (FAQ)

Είναι το DI άλλο όνομα για το IoC;

Το Inversion of Control (IoC) είναι μια αρχή που εστιάζει στον τρόπο εκτέλεσης του κώδικα – εάν ο κώδικάς σας εκτελεί ξένο κώδικα ή εάν ο κώδικάς σας ενσωματώνεται σε ξένο κώδικα, ο οποίος στη συνέχεια τον καλεί. Το IoC είναι ένας ευρύς όρος που περιλαμβάνει γεγονότα, το λεγόμενο Hollywood principle και άλλες πτυχές. Μέρος αυτής της έννοιας είναι και τα factories, για τα οποία μιλά ο Κανόνας #3: άφησέ το στο factory, και τα οποία αντιπροσωπεύουν μια αντιστροφή για τον τελεστή new.

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

Μπορούμε λοιπόν να πούμε ότι το DI είναι μια συγκεκριμένη μορφή IoC. Ωστόσο, δεν είναι όλες οι μορφές IoC κατάλληλες από την άποψη της καθαρότητας του κώδικα. Για παράδειγμα, μεταξύ των αντι-προτύπων (antipatterns) περιλαμβάνονται τεχνικές που λειτουργούν με καθολική κατάσταση ή το λεγόμενο Service Locator.

Τι είναι το Service Locator;

Πρόκειται για μια εναλλακτική λύση στο Dependency Injection. Λειτουργεί δημιουργώντας ένα κεντρικό αποθετήριο όπου καταχωρούνται όλες οι διαθέσιμες υπηρεσίες ή εξαρτήσεις. Όταν ένα αντικείμενο χρειάζεται μια εξάρτηση, τη ζητά από το Service Locator.

Σε σύγκριση με το Dependency Injection, ωστόσο, χάνει σε διαφάνεια: οι εξαρτήσεις δεν περνούν απευθείας στα αντικείμενα και δεν είναι τόσο εύκολα αναγνωρίσιμες, πράγμα που απαιτεί την εξέταση του κώδικα για να αποκαλυφθούν και να κατανοηθούν όλες οι συνδέσεις. Ο έλεγχος (testing) είναι επίσης πιο περίπλοκος, επειδή δεν μπορούμε απλώς να περάσουμε mock αντικείμενα στα υπό έλεγχο αντικείμενα, αλλά πρέπει να το κάνουμε μέσω του Service Locator. Επιπλέον, το Service Locator διαταράσσει τον σχεδιασμό του κώδικα, καθώς τα μεμονωμένα αντικείμενα πρέπει να γνωρίζουν την ύπαρξή του, πράγμα που διαφέρει από το Dependency Injection, όπου τα αντικείμενα δεν έχουν επίγνωση του DI container.

Πότε είναι καλύτερο να μην χρησιμοποιηθεί το DI;

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

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

Έχει η χρήση του DI τα μειονεκτήματά της;

Συνεπάγεται η χρήση του Dependency Injection κάποια μειονεκτήματα, όπως για παράδειγμα αυξημένη δυσκολία στη συγγραφή κώδικα ή χειρότερη απόδοση; Τι χάνουμε όταν αρχίζουμε να γράφουμε κώδικα σύμφωνα με το DI;

Το DI δεν επηρεάζει την απόδοση ή τις απαιτήσεις μνήμης της εφαρμογής. Ορισμένο ρόλο μπορεί να παίξει η απόδοση του DI Container, ωστόσο στην περίπτωση του Nette DI, το container μεταγλωττίζεται σε καθαρή PHP, οπότε η επιβάρυνσή του κατά την εκτέλεση της εφαρμογής είναι ουσιαστικά μηδενική.

Κατά τη συγγραφή κώδικα, συχνά είναι απαραίτητο να δημιουργηθούν κατασκευαστές που δέχονται εξαρτήσεις. Παλαιότερα αυτό μπορούσε να είναι χρονοβόρο, ωστόσο χάρη στα σύγχρονα IDE και το constructor property promotion, είναι πλέον θέμα δευτερολέπτων. Τα factories μπορούν εύκολα να δημιουργηθούν με το Nette DI και το plugin για το PhpStorm με ένα κλικ του ποντικιού. Από την άλλη πλευρά, εξαλείφεται η ανάγκη συγγραφής singletons και στατικών σημείων πρόσβασης.

Μπορούμε να συμπεράνουμε ότι μια σωστά σχεδιασμένη εφαρμογή που χρησιμοποιεί DI δεν είναι ούτε συντομότερη ούτε μακρύτερη σε σύγκριση με μια εφαρμογή που χρησιμοποιεί singletons. Τα τμήματα του κώδικα που εργάζονται με εξαρτήσεις απλώς αφαιρούνται από τις μεμονωμένες κλάσεις και μεταφέρονται σε νέα σημεία, δηλαδή στο DI container και στα factories.

Πώς να ξαναγράψετε μια legacy εφαρμογή σε DI;

Η μετάβαση από μια legacy εφαρμογή στο Dependency Injection μπορεί να είναι μια απαιτητική διαδικασία, ειδικά σε μεγάλες και πολύπλοκες εφαρμογές. Είναι σημαντικό να προσεγγίσετε αυτή τη διαδικασία συστηματικά.

  • Κατά τη μετάβαση στο Dependency Injection, είναι σημαντικό όλα τα μέλη της ομάδας να κατανοούν τις αρχές και τις διαδικασίες που χρησιμοποιούνται.
  • Πρώτα, πραγματοποιήστε μια ανάλυση της υπάρχουσας εφαρμογής και εντοπίστε τα βασικά στοιχεία και τις εξαρτήσεις τους. Δημιουργήστε ένα σχέδιο για το ποια τμήματα θα αναδιαρθρωθούν και με ποια σειρά.
  • Υλοποιήστε ένα DI container ή, ακόμα καλύτερα, χρησιμοποιήστε μια υπάρχουσα βιβλιοθήκη, για παράδειγμα το Nette DI.
  • Σταδιακά αναδιαρθρώστε τα μεμονωμένα τμήματα της εφαρμογής ώστε να χρησιμοποιούν το Dependency Injection. Αυτό μπορεί να περιλαμβάνει τροποποιήσεις των κατασκευαστών ή των μεθόδων ώστε να δέχονται εξαρτήσεις ως παραμέτρους.
  • Τροποποιήστε τα σημεία στον κώδικα όπου δημιουργούνται αντικείμενα με εξαρτήσεις, ώστε αντί γι' αυτό οι εξαρτήσεις να εισάγονται από το container. Αυτό μπορεί να περιλαμβάνει τη χρήση factories.

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

Γιατί προτιμάται η σύνθεση (composition) έναντι της κληρονομικότητας;

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

Μπορεί να χρησιμοποιηθεί το Nette DI Container εκτός του Nette;

Σίγουρα. Το Nette DI Container είναι μέρος του Nette, αλλά έχει σχεδιαστεί ως μια αυτόνομη βιβλιοθήκη που μπορεί να χρησιμοποιηθεί ανεξάρτητα από τα υπόλοιπα μέρη του framework. Αρκεί να την εγκαταστήσετε μέσω του Composer, να δημιουργήσετε ένα αρχείο διαμόρφωσης με τον ορισμό των υπηρεσιών σας και στη συνέχεια, με λίγες γραμμές κώδικα PHP, να δημιουργήσετε το DI container. Και αμέσως μπορείτε να αρχίσετε να επωφελείστε από το Dependency Injection στα έργα σας.

Πώς μοιάζει η συγκεκριμένη χρήση, συμπεριλαμβανομένων των κωδίκων, περιγράφεται στο κεφάλαιο Nette DI Container.

Γιατί η διαμόρφωση είναι σε αρχεία NEON;

Το NEON είναι μια απλή και ευανάγνωστη γλώσσα διαμόρφωσης, η οποία αναπτύχθηκε στο πλαίσιο του Nette για τη ρύθμιση εφαρμογών, υπηρεσιών και των εξαρτήσεών τους. Σε σύγκριση με το JSON ή το YAML, προσφέρει για τον σκοπό αυτό πολύ πιο διαισθητικές και ευέλικτες δυνατότητες. Στο NEON μπορούν να περιγραφούν φυσικά συνδέσεις, οι οποίες στο Symfony & YAMLu δεν θα ήταν δυνατόν να γραφτούν είτε καθόλου, είτε μόνο μέσω πολύπλοκης περιγραφής.

Δεν επιβραδύνει την εφαρμογή η ανάλυση (parsing) των αρχείων NEON;

Παρόλο που τα αρχεία NEON αναλύονται πολύ γρήγορα, αυτή η πτυχή δεν έχει καμία σημασία. Ο λόγος είναι ότι η ανάλυση των αρχείων πραγματοποιείται μόνο μία φορά κατά την πρώτη εκκίνηση της εφαρμογής. Στη συνέχεια, δημιουργείται ο κώδικας του DI container, αποθηκεύεται στον δίσκο και εκτελείται σε κάθε επόμενο αίτημα, χωρίς να είναι απαραίτητη η περαιτέρω ανάλυση.

Έτσι λειτουργεί στο περιβάλλον παραγωγής. Κατά την ανάπτυξη, τα αρχεία NEON αναλύονται κάθε φορά που αλλάζει το περιεχόμενό τους, ώστε ο προγραμματιστής να έχει πάντα τον τρέχοντα DI container. Η ίδια η ανάλυση είναι, όπως ειπώθηκε, θέμα στιγμής.

Πώς μπορώ να αποκτήσω πρόσβαση στις παραμέτρους στο αρχείο διαμόρφωσης από την κλάση μου;

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

Σε αυτό το παράδειγμα, το %myParameter% είναι ένα placeholder για την τιμή της παραμέτρου myParameter, η οποία περνά στον κατασκευαστή της κλάσης MyClass:

# config.neon
parameters:
	myParameter: Some value

services:
	- MyClass(%myParameter%)

Για να περάσετε πολλαπλές παραμέτρους ή να χρησιμοποιήσετε autowiring, είναι σκόπιμο να ενσωματώσετε τις παραμέτρους σε ένα αντικείμενο.

Υποστηρίζει το Nette το PSR-11: Container interface;

Το Nette DI Container δεν υποστηρίζει απευθείας το PSR-11. Ωστόσο, εάν χρειάζεστε διαλειτουργικότητα μεταξύ του Nette DI Container και βιβλιοθηκών ή frameworks που αναμένουν το PSR-11 Container Interface, μπορείτε να δημιουργήσετε έναν απλό προσαρμογέα, ο οποίος θα χρησιμεύσει ως γέφυρα μεταξύ του Nette DI Container και του PSR-11.

έκδοση: 3.x