Τι είναι το Dependency Injection;
Αυτό το κεφάλαιο θα σας εισαγάγει στις βασικές πρακτικές προγραμματισμού που πρέπει να ακολουθείτε κατά τη συγγραφή όλων των εφαρμογών. Αυτά είναι τα θεμέλια που απαιτούνται για τη συγγραφή καθαρού, κατανοητού και συντηρήσιμου κώδικα.
Εάν υιοθετήσετε αυτούς τους κανόνες και τους ακολουθήσετε, το Nette θα σας βοηθήσει σε κάθε βήμα. Θα χειριστεί τις εργασίες ρουτίνας για εσάς και θα σας προσφέρει μέγιστη άνεση, ώστε να μπορείτε να επικεντρωθείτε στην ίδια τη λογική.
Οι αρχές που θα παρουσιάσουμε εδώ είναι αρκετά απλές. Δεν χρειάζεται να ανησυχείτε για τίποτα.
Θυμάστε το πρώτο σας πρόγραμμα;
Δεν ξέρουμε σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, πιθανότατα θα έμοιαζε κάπως έτσι:
function soucet(float $a, float $b): float
{
return $a + $b;
}
echo soucet(23, 1); // εκτυπώνει 24
Λίγες ασήμαντες γραμμές κώδικα, αλλά περιέχουν τόσες πολλές βασικές έννοιες. Ότι υπάρχουν μεταβλητές. Ότι ο κώδικας χωρίζεται σε μικρότερες μονάδες, όπως συναρτήσεις. Ότι τους περνάμε ορίσματα εισόδου και επιστρέφουν αποτελέσματα. Λείπουν μόνο οι συνθήκες και οι βρόχοι.
Το γεγονός ότι περνάμε δεδομένα εισόδου σε μια συνάρτηση και αυτή επιστρέφει ένα αποτέλεσμα είναι μια απολύτως κατανοητή έννοια που χρησιμοποιείται και σε άλλους τομείς, όπως τα μαθηματικά.
Μια συνάρτηση έχει την υπογραφή της, η οποία αποτελείται από το όνομά της, μια λίστα παραμέτρων και τους τύπους τους, και τέλος τον τύπο της τιμής επιστροφής. Ως χρήστες, μας ενδιαφέρει η υπογραφή· συνήθως δεν χρειάζεται να γνωρίζουμε τίποτα για την εσωτερική υλοποίηση.
Τώρα φανταστείτε η υπογραφή της συνάρτησης να έμοιαζε κάπως έτσι:
function soucet(float $x): float
Άθροισμα με μία παράμετρο; Αυτό είναι περίεργο… Και τι θα λέγατε για αυτό;
function soucet(): float
Αυτό είναι πραγματικά πολύ περίεργο, έτσι δεν είναι; Πώς χρησιμοποιείται η συνάρτηση;
echo soucet(); // τι θα εκτυπώσει άραγε;
Κοιτάζοντας έναν τέτοιο κώδικα, θα ήμασταν μπερδεμένοι. Όχι μόνο ένας αρχάριος δεν θα τον καταλάβαινε, αλλά ούτε και ένας έμπειρος προγραμματιστής δεν καταλαβαίνει τέτοιο κώδικα.
Αναρωτιέστε πώς θα έμοιαζε μια τέτοια συνάρτηση εσωτερικά; Από πού θα έπαιρνε τους προσθετέους; Προφανώς, θα τους έβρισκε με κάποιο τρόπο μόνη της, ίσως κάπως έτσι:
function soucet(): float
{
$a = Input::get('a');
$b = Input::get('b');
return $a + $b;
}
Στο σώμα της συνάρτησης, ανακαλύψαμε κρυφές εξαρτήσεις από άλλες καθολικές συναρτήσεις ή στατικές μεθόδους. Για να μάθουμε από πού προέρχονται πραγματικά οι προσθετέοι, πρέπει να ψάξουμε περαιτέρω.
Όχι από εδώ!
Ο σχεδιασμός που μόλις δείξαμε είναι η ουσία πολλών αρνητικών χαρακτηριστικών:
- η υπογραφή της συνάρτησης προσποιούνταν ότι δεν χρειαζόταν προσθετέους, πράγμα που μας μπέρδεψε
- δεν ξέρουμε καθόλου πώς να κάνουμε τη συνάρτηση να προσθέσει δύο άλλους αριθμούς
- έπρεπε να κοιτάξουμε τον κώδικα για να δούμε από πού έπαιρνε τους προσθετέους
- ανακαλύψαμε κρυφές εξαρτήσεις
- για πλήρη κατανόηση, είναι απαραίτητο να εξετάσουμε και αυτές τις εξαρτήσεις
Και είναι καθόλου έργο της συνάρτησης πρόσθεσης να αποκτά εισόδους; Φυσικά και όχι. Η ευθύνη της είναι μόνο η ίδια η πρόσθεση.
Δεν θέλουμε να συναντήσουμε τέτοιο κώδικα, και σίγουρα δεν θέλουμε να τον γράψουμε. Η διόρθωση είναι απλή: επιστροφή στα βασικά και απλή χρήση παραμέτρων:
function soucet(float $a, float $b): float
{
return $a + $b;
}
Κανόνας αρ. 1: αφήστε το να σας παραδοθεί
Ο πιο σημαντικός κανόνας είναι: όλα τα δεδομένα που χρειάζονται οι συναρτήσεις ή οι κλάσεις πρέπει να τους παραδίδονται.
Αντί να επινοείτε κρυφούς τρόπους με τους οποίους θα μπορούσαν να τα αποκτήσουν μόνοι τους, απλά περάστε τις παραμέτρους. Θα εξοικονομήσετε χρόνο που απαιτείται για την επινόηση κρυφών μονοπατιών, τα οποία σίγουρα δεν θα βελτιώσουν τον κώδικά σας.
Αν ακολουθείτε πάντα και παντού αυτόν τον κανόνα, βρίσκεστε στο δρόμο για κώδικα χωρίς κρυφές εξαρτήσεις. Για κώδικα που είναι κατανοητός όχι μόνο στον συγγραφέα, αλλά και σε οποιονδήποτε τον διαβάσει μετά από αυτόν. Όπου όλα είναι κατανοητά από τις υπογραφές των συναρτήσεων και των κλάσεων και δεν χρειάζεται να ψάχνετε για κρυμμένα μυστικά στην υλοποίηση.
Αυτή η τεχνική ονομάζεται τεχνικά dependency injection. Και αυτά τα δεδομένα ονομάζονται εξαρτήσεις (dependencies). Στην πραγματικότητα, είναι απλή παράδοση παραμέτρων, τίποτα περισσότερο.
Παρακαλώ μην συγχέετε το dependency injection, το οποίο είναι ένα πρότυπο σχεδίασης, με το “dependency injection container”, το οποίο είναι ένα εργαλείο, δηλαδή κάτι διαμετρικά αντίθετο. Θα ασχοληθούμε με τα containers αργότερα.
Από συναρτήσεις σε κλάσεις
Και πώς σχετίζονται οι κλάσεις με αυτό; Μια κλάση είναι μια πιο σύνθετη οντότητα από μια απλή συνάρτηση, ωστόσο ο κανόνας αρ. 1 ισχύει πλήρως και εδώ. Απλώς υπάρχουν περισσότερες επιλογές για την παράδοση ορισμάτων. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης:
class Matematika
{
public function soucet(float $a, float $b): float
{
return $a + $b;
}
}
$math = new Matematika;
echo $math->soucet(23, 1); // 24
Ή χρησιμοποιώντας άλλες μεθόδους, ή απευθείας τον κατασκευαστή:
class Soucet
{
public function __construct(
private float $a,
private float $b,
) {
}
public function spocti(): float
{
return $this->a + $this->b;
}
}
$soucet = new Soucet(23, 1);
echo $soucet->spocti(); // 24
Και τα δύο παραδείγματα είναι πλήρως σύμφωνα με το dependency injection.
Πραγματικά παραδείγματα
Στον πραγματικό κόσμο, δεν θα γράφετε κλάσεις για την πρόσθεση αριθμών. Ας προχωρήσουμε σε παραδείγματα από την πράξη.
Έστω μια κλάση Article
που αντιπροσωπεύει ένα άρθρο σε ένα blog:
class Article
{
public int $id;
public string $title;
public string $content;
public function save(): void
{
// αποθηκεύουμε το άρθρο στη βάση δεδομένων
}
}
και η χρήση θα είναι η εξής:
$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();
Η μέθοδος save()
αποθηκεύει το άρθρο σε έναν πίνακα βάσης
δεδομένων. Η υλοποίησή της με τη βοήθεια του Nette
Database θα ήταν παιχνιδάκι, αν δεν υπήρχε ένα εμπόδιο: πού παίρνει η
Article
τη σύνδεση με τη βάση δεδομένων, δηλαδή το αντικείμενο της
κλάσης Nette\Database\Connection
;
Φαίνεται ότι έχουμε πολλές επιλογές. Μπορεί να την πάρει από κάπου από μια στατική μεταβλητή. Ή να κληρονομήσει από μια κλάση που εξασφαλίζει τη σύνδεση με τη βάση δεδομένων. Ή να χρησιμοποιήσει το λεγόμενο singleton. Ή τις λεγόμενες facades, που χρησιμοποιούνται στο Laravel:
use Illuminate\Support\Facades\DB;
class Article
{
public int $id;
public string $title;
public string $content;
public function save(): void
{
DB::insert(
'INSERT INTO articles (title, content) VALUES (?, ?)',
[$this->title, $this->content],
);
}
}
Υπέροχα, λύσαμε το πρόβλημα.
Ή μήπως όχι;
Ας θυμηθούμε τον Κανόνας αρ. 1: αφήστε το να σας παραδοθεί: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να της παραδίδονται. Επειδή αν παραβιάσουμε τον κανόνα, έχουμε πάρει τον δρόμο για βρώμικο κώδικα γεμάτο κρυφές εξαρτήσεις, ασάφεια, και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι επώδυνο να συντηρηθεί και να αναπτυχθεί.
Ο χρήστης της κλάσης Article
δεν έχει ιδέα πού αποθηκεύει η
μέθοδος save()
το άρθρο. Σε έναν πίνακα βάσης δεδομένων; Σε ποιον,
τον παραγωγικό ή τον δοκιμαστικό; Και πώς μπορεί να αλλάξει αυτό;
Ο χρήστης πρέπει να δει πώς υλοποιείται η μέθοδος save()
και
βρίσκει τη χρήση της μεθόδου DB::insert()
. Άρα πρέπει να ψάξει
περαιτέρω, πώς αυτή η μέθοδος αποκτά τη σύνδεση με τη βάση δεδομένων.
Και οι κρυφές εξαρτήσεις μπορούν να σχηματίσουν μια αρκετά μεγάλη
αλυσίδα.
Σε καθαρό και καλά σχεδιασμένο κώδικα, δεν υπάρχουν ποτέ κρυφές εξαρτήσεις, facades του Laravel ή στατικές μεταβλητές. Σε καθαρό και καλά σχεδιασμένο κώδικα, παραδίδονται ορίσματα:
class Article
{
public function save(Nette\Database\Connection $db): void
{
$db->query('INSERT INTO articles', [
'title' => $this->title,
'content' => $this->content,
]);
}
}
Ακόμα πιο πρακτικό, όπως θα δούμε παρακάτω, θα είναι με τον κατασκευαστή:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function save(): void
{
$this->db->query('INSERT INTO articles', [
'title' => $this->title,
'content' => $this->content,
]);
}
}
Αν είστε έμπειρος προγραμματιστής, ίσως σκέφτεστε ότι η
Article
δεν θα έπρεπε καθόλου να έχει τη μέθοδο save()
, θα
έπρεπε να αντιπροσωπεύει ένα καθαρά δεδομενικό component και η αποθήκευση
θα έπρεπε να γίνεται από ένα ξεχωριστό repository. Αυτό έχει νόημα. Αλλά αυτό
θα μας πήγαινε πολύ πέρα από το θέμα, το οποίο είναι το dependency injection, και
την προσπάθεια να δώσουμε απλά παραδείγματα.
Αν γράφετε μια κλάση που απαιτεί, για παράδειγμα, μια βάση δεδομένων για τη λειτουργία της, μην επινοείτε από πού να την πάρετε, αλλά αφήστε την να σας παραδοθεί. Ίσως ως παράμετρος του κατασκευαστή ή άλλης μεθόδου. Αναγνωρίστε τις εξαρτήσεις. Αναγνωρίστε τις στο API της κλάσης σας. Θα αποκτήσετε κατανοητό και προβλέψιμο κώδικα.
Και τι θα λέγατε για αυτήν την κλάση, η οποία καταγράφει μηνύματα σφάλματος:
class Logger
{
public function log(string $message)
{
$file = LOG_DIR . '/log.txt';
file_put_contents($file, $message . "\n", FILE_APPEND);
}
}
Τι πιστεύετε, τηρήσαμε τον Κανόνας αρ. 1: αφήστε το να σας παραδοθεί?
Δεν τον τηρήσαμε.
Η κλάση αποκτά μόνη της την κρίσιμη πληροφορία, δηλαδή τον κατάλογο με το αρχείο καταγραφής, από μια σταθερά.
Δείτε το παράδειγμα χρήσης:
$logger = new Logger;
$logger->log('Η θερμοκρασία είναι 23 °C');
$logger->log('Η θερμοκρασία είναι 10 °C');
Χωρίς γνώση της υλοποίησης, θα μπορούσατε να απαντήσετε στην ερώτηση
πού γράφονται τα μηνύματα; Θα σκεφτόσασταν ότι για τη λειτουργία
απαιτείται η ύπαρξη της σταθεράς LOG_DIR
; Και θα μπορούσατε να
δημιουργήσετε μια δεύτερη παρουσία που θα γράφει αλλού; Σίγουρα όχι.
Ας διορθώσουμε την κλάση:
class Logger
{
public function __construct(
private string $file,
) {
}
public function log(string $message): void
{
file_put_contents($this->file, $message . "\n", FILE_APPEND);
}
}
Η κλάση είναι τώρα πολύ πιο κατανοητή, διαμορφώσιμη και επομένως πιο χρήσιμη.
$logger = new Logger('/path/to/log.txt');
$logger->log('Η θερμοκρασία είναι 15 °C');
Αλλά αυτό δεν με ενδιαφέρει!
«Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχολούμαι με τη βάση δεδομένων, απλά θέλω να αποθηκευτεί σε αυτήν που έχω ορίσει στη διαμόρφωση.»
«Όταν χρησιμοποιώ το Logger, απλά θέλω το μήνυμα να καταγραφεί, και δεν θέλω να ασχολούμαι με το πού. Ας χρησιμοποιηθεί η καθολική ρύθμιση.»
Αυτές είναι σωστές παρατηρήσεις.
Ως παράδειγμα, θα δείξουμε μια κλάση που στέλνει newsletters, η οποία καταγράφει πώς πήγε:
class NewsletterDistributor
{
public function distribute(): void
{
$logger = new Logger(/* ... */);
try {
$this->sendEmails();
$logger->log('Τα emails στάλθηκαν');
} catch (Exception $e) {
$logger->log('Παρουσιάστηκε σφάλμα κατά την αποστολή');
throw $e;
}
}
}
Ο βελτιωμένος Logger
, ο οποίος δεν χρησιμοποιεί πλέον τη σταθερά
LOG_DIR
, απαιτεί τη διαδρομή προς το αρχείο στον κατασκευαστή. Πώς
να το λύσουμε αυτό; Η κλάση NewsletterDistributor
δεν ενδιαφέρεται καθόλου
για το πού γράφονται τα μηνύματα, θέλει απλώς να τα γράψει.
Η λύση είναι και πάλι ο Κανόνας αρ. 1: αφήστε το να σας παραδοθεί: παραδίδουμε όλα τα δεδομένα που χρειάζεται η κλάση.
Άρα αυτό σημαίνει ότι παραδίδουμε τη διαδρομή προς το αρχείο
καταγραφής μέσω του κατασκευαστή, την οποία στη συνέχεια
χρησιμοποιούμε κατά τη δημιουργία του αντικειμένου Logger
;
class NewsletterDistributor
{
public function __construct(
private string $file, // ⛔ ΟΧΙ ΕΤΣΙ!
) {
}
public function distribute(): void
{
$logger = new Logger($this->file);
Όχι έτσι! Η διαδρομή δεν ανήκει στα δεδομένα που χρειάζεται η
κλάση NewsletterDistributor
· αυτά τα χρειάζεται ο Logger
.
Αντιλαμβάνεστε τη διαφορά; Η κλάση NewsletterDistributor
χρειάζεται τον
logger ως τέτοιο. Άρα αυτόν θα παραδώσουμε:
class NewsletterDistributor
{
public function __construct(
private Logger $logger, // ✅
) {
}
public function distribute(): void
{
try {
$this->sendEmails();
$this->logger->log('Τα emails στάλθηκαν');
} catch (Exception $e) {
$this->logger->log('Παρουσιάστηκε σφάλμα κατά την αποστολή');
throw $e;
}
}
}
Τώρα είναι σαφές από τις υπογραφές της κλάσης NewsletterDistributor
ότι
η καταγραφή αποτελεί μέρος της λειτουργικότητάς της. Και η εργασία της
αντικατάστασης του logger με έναν άλλο, για παράδειγμα για δοκιμές, είναι
εντελώς ασήμαντη. Επιπλέον, αν ο κατασκευαστής της κλάσης Logger
άλλαζε, αυτό δεν θα είχε καμία επίδραση στην κλάση μας.
Κανόνας αρ. 2: πάρε ό,τι είναι δικό σου
Μην μπερδεύεστε και μην αφήνετε να σας παραδίδουν τις εξαρτήσεις των εξαρτήσεών σας. Αφήστε να σας παραδίδουν μόνο τις δικές σας εξαρτήσεις.
Χάρη σε αυτό, ο κώδικας που χρησιμοποιεί άλλα αντικείμενα θα είναι εντελώς ανεξάρτητος από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο αληθινό. Και κυρίως, θα είναι ασήμαντο να αντικαταστήσετε αυτές τις εξαρτήσεις με άλλες.
Νέο μέλος της οικογένειας
Στην ομάδα ανάπτυξης, αποφασίστηκε να δημιουργηθεί ένας δεύτερος
logger, ο οποίος γράφει στη βάση δεδομένων. Έτσι, δημιουργούμε την κλάση
DatabaseLogger
. Έχουμε λοιπόν δύο κλάσεις, Logger
και
DatabaseLogger
, η μία γράφει σε αρχείο, η άλλη στη βάση δεδομένων… δεν
σας φαίνεται κάτι περίεργο στην ονομασία; Δεν θα ήταν καλύτερα να
μετονομάσουμε τον Logger
σε FileLogger
; Σίγουρα ναι.
Αλλά θα το κάνουμε έξυπνα. Κάτω από το αρχικό όνομα, θα δημιουργήσουμε ένα interface:
interface Logger
{
function log(string $message): void;
}
… το οποίο θα υλοποιούν και οι δύο loggers:
class FileLogger implements Logger
// ...
class DatabaseLogger implements Logger
// ...
Και χάρη σε αυτό, δεν θα χρειαστεί να αλλάξουμε τίποτα στον υπόλοιπο
κώδικα όπου χρησιμοποιείται ο logger. Για παράδειγμα, ο κατασκευαστής της
κλάσης NewsletterDistributor
θα είναι ακόμα ικανοποιημένος με το ότι
απαιτεί Logger
ως παράμετρο. Και θα εξαρτάται από εμάς ποια
παρουσία θα του παραδώσουμε.
Γι' αυτό ποτέ δεν δίνουμε στα ονόματα των interfaces την κατάληξη
Interface
ή το πρόθεμα I
. Διαφορετικά, δεν θα ήταν δυνατόν να
αναπτύξουμε τον κώδικα τόσο όμορφα.
Χιούστον, έχουμε πρόβλημα
Ενώ σε ολόκληρη την εφαρμογή μπορούμε να αρκεστούμε σε μία μόνο
παρουσία του logger, είτε αρχείου είτε βάσης δεδομένων, και απλά να τον
παραδίδουμε παντού όπου κάτι καταγράφεται, η κατάσταση είναι εντελώς
διαφορετική στην περίπτωση της κλάσης Article
. Οι παρουσίες της
δημιουργούνται ανάλογα με τις ανάγκες, ακόμα και πολλές φορές. Πώς να
αντιμετωπίσουμε την εξάρτηση από τη βάση δεδομένων στον
κατασκευαστή της;
Ως παράδειγμα μπορεί να χρησιμεύσει ένας controller, ο οποίος μετά την υποβολή μιας φόρμας πρέπει να αποθηκεύσει το άρθρο στη βάση δεδομένων:
class EditController extends Controller
{
public function formSubmitted($data)
{
$article = new Article(/* ... */);
$article->title = $data->title;
$article->content = $data->content;
$article->save();
}
}
Μια πιθανή λύση προσφέρεται άμεσα: αφήνουμε το αντικείμενο της βάσης
δεδομένων να παραδοθεί μέσω του κατασκευαστή στον EditController
και
χρησιμοποιούμε $article = new Article($this->db)
.
Όπως και στην προηγούμενη περίπτωση με τον Logger
και τη διαδρομή
προς το αρχείο, αυτή δεν είναι η σωστή προσέγγιση. Η βάση δεδομένων δεν
είναι εξάρτηση του EditController
, αλλά του Article
. Η παράδοση της
βάσης δεδομένων λοιπόν αντιβαίνει στον Κανόνα αρ. 2: πάρε ό,τι είναι δικό σου. Όταν
αλλάξει ο κατασκευαστής της κλάσης Article
(προστεθεί μια νέα
παράμετρος), θα είναι απαραίτητο να τροποποιηθεί ο κώδικας σε όλα τα
σημεία όπου δημιουργούνται παρουσίες. Ουφ.
Χιούστον, τι προτείνεις;
Κανόνας αρ. 3: άφησέ το στο factory
Καταργώντας τις κρυφές εξαρτήσεις και παραδίδοντας όλες τις εξαρτήσεις ως ορίσματα, αποκτήσαμε πιο διαμορφώσιμες και ευέλικτες κλάσεις. Και επομένως χρειαζόμαστε κάτι ακόμα, το οποίο θα δημιουργήσει και θα διαμορφώσει αυτές τις πιο ευέλικτες κλάσεις για εμάς. Θα το ονομάσουμε factories.
Ο κανόνας λέει: αν μια κλάση έχει εξαρτήσεις, άφησε τη δημιουργία των παρουσιών της σε ένα factory.
Τα factories είναι μια πιο έξυπνη αντικατάσταση του τελεστή new
στον
κόσμο του dependency injection.
Παρακαλώ μην συγχέετε με το πρότυπο σχεδίασης factory method, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των factories και δεν σχετίζεται με αυτό το θέμα.
Factory
Ένα factory είναι μια μέθοδος ή μια κλάση που παράγει και διαμορφώνει
αντικείμενα. Την κλάση που παράγει Article
θα την ονομάσουμε
ArticleFactory
και θα μπορούσε να μοιάζει κάπως έτσι:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Η χρήση της στον controller θα είναι η εξής:
class EditController extends Controller
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function formSubmitted($data)
{
// αφήνουμε το factory να δημιουργήσει το αντικείμενο
$article = $this->articleFactory->create();
$article->title = $data->title;
$article->content = $data->content;
$article->save();
}
}
Αν αυτή τη στιγμή αλλάξει η υπογραφή του κατασκευαστή της κλάσης
Article
, το μόνο μέρος του κώδικα που πρέπει να αντιδράσει σε αυτό
είναι το ίδιο το factory ArticleFactory
. Όλος ο υπόλοιπος κώδικας που
λειτουργεί με αντικείμενα Article
, όπως για παράδειγμα ο
EditController
, δεν θα επηρεαστεί καθόλου.
Ίσως τώρα χτυπάτε το κεφάλι σας, αν βοηθήσαμε καθόλου. Η ποσότητα του κώδικα αυξήθηκε και όλο αυτό αρχίζει να φαίνεται ύποπτα περίπλοκο.
Μην ανησυχείτε, σε λίγο θα φτάσουμε στο Nette DI container. Και αυτός έχει
πολλούς άσους στο μανίκι του, οι οποίοι θα απλοποιήσουν εξαιρετικά την
κατασκευή εφαρμογών που χρησιμοποιούν dependency injection. Έτσι, για
παράδειγμα, αντί για την κλάση ArticleFactory
, θα αρκεί να γράψουμε απλώς ένα interface:
interface ArticleFactory
{
function create(): Article;
}
Αλλά προτρέχουμε, περιμένετε λίγο ακόμα :-)
Σύνοψη
Στην αρχή αυτού του κεφαλαίου, υποσχεθήκαμε ότι θα δείξουμε μια διαδικασία για τον σχεδιασμό καθαρού κώδικα. Αρκεί στις κλάσεις
- να παραδίδονται οι εξαρτήσεις που χρειάζονται
- και αντίστροφα, να μην παραδίδονται ό,τι δεν χρειάζονται άμεσα
- και ότι τα αντικείμενα με εξαρτήσεις κατασκευάζονται καλύτερα σε factories
Μπορεί να μην φαίνεται έτσι με την πρώτη ματιά, αλλά αυτοί οι τρεις κανόνες έχουν εκτεταμένες συνέπειες. Οδηγούν σε μια ριζικά διαφορετική άποψη για τον σχεδιασμό του κώδικα. Αξίζει τον κόπο; Οι προγραμματιστές που εγκατέλειψαν τις παλιές συνήθειες και άρχισαν να χρησιμοποιούν με συνέπεια το dependency injection θεωρούν αυτό το βήμα ως μια κρίσιμη στιγμή στην επαγγελματική τους ζωή. Τους άνοιξε τον κόσμο των σαφών και συντηρήσιμων εφαρμογών.
Τι γίνεται όμως αν ο κώδικας δεν χρησιμοποιεί με συνέπεια το dependency injection; Τι γίνεται αν βασίζεται σε στατικές μεθόδους ή singletons; Προκαλεί αυτό προβλήματα; Προκαλεί, και μάλιστα πολύ σοβαρά.