Nette Documentation Preview

syntax
Τι είναι το Dependency Injection;
*********************************

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

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

Οι αρχές που θα παρουσιάσουμε εδώ είναι αρκετά απλές. Δεν χρειάζεται να ανησυχείτε για τίποτα.


Θυμάστε το πρώτο σας πρόγραμμα; .[#toc-remember-your-first-program]
-------------------------------------------------------------------

Δεν ξέρουμε σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, μπορεί να έμοιαζε κάπως έτσι:

```php
function addition(float $a, float $b): float
{
	return $a + $b;
}

echo addition(23, 1); // εκτυπώσεις 24
```

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

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

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

Φανταστείτε τώρα ότι η υπογραφή της συνάρτησης έμοιαζε ως εξής:

```php
function addition(float $x): float
```

Μια προσθήκη με μία παράμετρο; Αυτό είναι παράξενο... Τι λέτε γι' αυτό;

```php
function addition(): float
```

Τώρα αυτό είναι πραγματικά περίεργο, σωστά; Πώς χρησιμοποιείται η συνάρτηση;

```php
echo addition(); // τι εκτυπώνει;
```

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

Αναρωτιέστε πώς θα έμοιαζε στην πραγματικότητα μια τέτοια συνάρτηση στο εσωτερικό της; Από πού θα έπαιρνε τα αθροίσματα; Πιθανότατα θα τις έπαιρνε *με κάποιο τρόπο* από μόνη της, ίσως κάπως έτσι:

```php
function addition(): float
{
	$a = Input::get('a');
	$b = Input::get('b');
	return $a + $b;
}
```

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


Όχι με αυτόν τον τρόπο! .[#toc-not-this-way]
--------------------------------------------

Ο σχεδιασμός που μόλις δείξαμε είναι η ουσία πολλών αρνητικών χαρακτηριστικών:

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

Και μήπως είναι καν δουλειά της συνάρτησης πρόσθεσης να προμηθεύεται εισόδους; Φυσικά και δεν είναι.  Η ευθύνη της είναι μόνο να προσθέτει.


Δεν θέλουμε να συναντήσουμε τέτοιο κώδικα και σίγουρα δεν θέλουμε να τον γράψουμε. Η λύση είναι απλή: επιστρέψτε στα βασικά και χρησιμοποιήστε απλά παραμέτρους:


```php
function addition(float $a, float $b): float
{
	return $a + $b;
}
```


Κανόνας #1: Αφήστε να περάσει σε σας .[#toc-rule-1-let-it-be-passed-to-you]
---------------------------------------------------------------------------

Ο πιο σημαντικός κανόνας είναι: **Όλα τα δεδομένα που χρειάζονται οι συναρτήσεις ή οι κλάσεις πρέπει να τους μεταβιβάζονται**.

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

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

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

.[note]
Παρακαλώ μην συγχέετε την έγχυση εξαρτήσεων, η οποία είναι ένα πρότυπο σχεδίασης, με ένα "dependency injection container", το οποίο είναι ένα εργαλείο, κάτι διαμετρικά διαφορετικό. Θα ασχοληθούμε με τους περιέκτες αργότερα.


Από τις συναρτήσεις στις κλάσεις .[#toc-from-functions-to-classes]
------------------------------------------------------------------

Και πώς συνδέονται οι τάξεις; Μια κλάση είναι μια πιο σύνθετη μονάδα από μια απλή συνάρτηση, αλλά ο κανόνας #1 ισχύει πλήρως και εδώ. Απλά υπάρχουν [περισσότεροι τρόποι για να περάσετε ορίσματα |passing-dependencies]. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης:

```php
class Math
{
	public function addition(float $a, float $b): float
	{
		return $a + $b;
	}
}

$math = new Math;
echo $math->addition(23, 1); // 24
```

Ή μέσω άλλων μεθόδων ή απευθείας μέσω του κατασκευαστή:

```php
class Addition
{
	public function __construct(
		private float $a,
		private float $b,
	) {
	}

	public function calculate(): float
	{
		return $this->a + $this->b;
	}

}

$addition = new Addition(23, 1);
echo $addition->calculate(); // 24
```

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


Παραδείγματα πραγματικής ζωής .[#toc-real-life-examples]
--------------------------------------------------------

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

Ας έχουμε μια κλάση `Article` που αναπαριστά μια δημοσίευση σε ιστολόγιο:

```php
class Article
{
	public int $id;
	public string $title;
	public string $content;

	public function save(): void
	{
		// αποθήκευση του άρθρου στη βάση δεδομένων
	}
}
```

και η χρήση θα είναι η εξής:

```php
$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 |database:] θα είναι πανεύκολη, αν δεν υπήρχε ένα πρόβλημα: από πού παίρνει η `Article` τη σύνδεση με τη βάση δεδομένων, δηλαδή ένα αντικείμενο της κλάσης `Nette\Database\Connection`;

Φαίνεται ότι έχουμε πολλές επιλογές. Μπορεί να την πάρει από μια στατική μεταβλητή κάπου. Ή να κληρονομήσει από μια κλάση που παρέχει μια σύνδεση με τη βάση δεδομένων. Ή να επωφεληθεί από ένα [singleton |global-state#Singleton]. Ή να χρησιμοποιήσει τα λεγόμενα facades, τα οποία χρησιμοποιούνται στο Laravel:

```php
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: Let It Be Passed to |#rule #1: Let It Be Passed to You] You: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να περάσουν σε αυτήν. Διότι αν παραβιάσουμε τον κανόνα, έχουμε ξεκινήσει μια πορεία προς βρώμικο κώδικα γεμάτο κρυφές εξαρτήσεις, ακατανόητο και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι οδυνηρή στη συντήρηση και την ανάπτυξη.

Ο χρήστης της κλάσης `Article` δεν έχει ιδέα πού αποθηκεύει το άρθρο η μέθοδος `save()`. Σε έναν πίνακα της βάσης δεδομένων; Σε ποιον, στην παραγωγή ή στις δοκιμές; Και πώς μπορεί να αλλάξει;

Ο χρήστης πρέπει να κοιτάξει πώς υλοποιείται η μέθοδος `save()` και να βρει τη χρήση της μεθόδου `DB::insert()`. Έτσι, πρέπει να ψάξει περαιτέρω για να βρει πώς αυτή η μέθοδος αποκτά μια σύνδεση με τη βάση δεδομένων. Και οι κρυφές εξαρτήσεις μπορεί να σχηματίσουν μια αρκετά μεγάλη αλυσίδα.

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

```php
class Article
{
	public function save(Nette\Database\Connection $db): void
	{
		$db->query('INSERT INTO articles', [
			'title' => $this->title,
			'content' => $this->content,
		]);
	}
}
```

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

```php
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,
		]);
	}
}
```

.[note]
Αν είστε έμπειρος προγραμματιστής, μπορεί να σκεφτείτε ότι το `Article` δεν θα έπρεπε να έχει καθόλου τη μέθοδο `save()` - θα έπρεπε να αντιπροσωπεύει ένα καθαρά στοιχείο δεδομένων και ένα ξεχωριστό αποθετήριο θα έπρεπε να φροντίζει για την αποθήκευση. Αυτό είναι λογικό. Αλλά αυτό θα μας πήγαινε πολύ πέρα από το αντικείμενο του θέματος, που είναι η έγχυση εξαρτήσεων, και την προσπάθεια να δώσουμε απλά παραδείγματα.

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

Και τι γίνεται με αυτή την κλάση, η οποία καταγράφει μηνύματα σφάλματος:

```php
class Logger
{
	public function log(string $message)
	{
		$file = LOG_DIR . '/log.txt';
		file_put_contents($file, $message . "\n", FILE_APPEND);
	}
}
```

Τι νομίζετε, ακολουθήσαμε τον [κανόνα #1: Αφήστε να περάσει σε σας |#rule #1: Let It Be Passed to You];

Δεν το κάναμε.

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

Κοιτάξτε το παράδειγμα χρήσης:

```php
$logger = new Logger;
$logger->log('The temperature is 23 °C');
$logger->log('The temperature is 10 °C');
```

Χωρίς να γνωρίζετε την υλοποίηση, μπορείτε να απαντήσετε στο ερώτημα πού γράφονται τα μηνύματα; Θα μπορούσατε να υποθέσετε ότι η ύπαρξη της σταθεράς `LOG_DIR` είναι απαραίτητη για τη λειτουργία του; Και θα μπορούσατε να δημιουργήσετε μια δεύτερη περίπτωση που θα έγραφε σε διαφορετική θέση; Σίγουρα όχι.

Ας διορθώσουμε την κλάση:

```php
class Logger
{
	public function __construct(
		private string $file,
	) {
	}

	public function log(string $message): void
	{
		file_put_contents($this->file, $message . "\n", FILE_APPEND);
	}
}
```

Η κλάση είναι τώρα πολύ πιο κατανοητή, παραμετροποιήσιμη και επομένως πιο χρήσιμη.

```php
$logger = new Logger('/path/to/log.txt');
$logger->log('The temperature is 15 °C');
```


Αλλά δεν με νοιάζει! .[#toc-but-i-don-t-care]
---------------------------------------------

*"Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχοληθώ με τη βάση δεδομένων- θέλω απλώς να αποθηκευτεί σε αυτήν που έχω ορίσει στη ρύθμιση παραμέτρων."*

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

Αυτά είναι βάσιμα σημεία.

Ως παράδειγμα, ας δούμε μια κλάση που στέλνει ενημερωτικά δελτία και καταγράφει πώς πήγε:

```php
class NewsletterDistributor
{
	public function distribute(): void
	{
		$logger = new Logger(/* ... */);
		try {
			$this->sendEmails();
			$logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}
```

Το βελτιωμένο `Logger`, το οποίο δεν χρησιμοποιεί πλέον τη σταθερά `LOG_DIR`, απαιτεί τον προσδιορισμό της διαδρομής του αρχείου στον κατασκευαστή. Πώς να το λύσετε αυτό; Η κλάση `NewsletterDistributor` δεν ενδιαφέρεται για το πού γράφονται τα μηνύματα- θέλει απλώς να τα γράψει.

Η λύση είναι και πάλι ο [κανόνας #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: περάστε όλα τα δεδομένα που χρειάζεται η κλάση.

Αυτό λοιπόν σημαίνει ότι περνάμε τη διαδρομή προς το αρχείο καταγραφής μέσω του κατασκευαστή, την οποία στη συνέχεια χρησιμοποιούμε κατά τη δημιουργία του αντικειμένου `Logger`;

```php
class NewsletterDistributor
{
	public function __construct(
		private string $file, // ⛔ ΌΧΙ ΜΕ ΑΥΤΌΝ ΤΟΝ ΤΡΌΠΟ!
	) {
	}

	public function distribute(): void
	{
		$logger = new Logger($this->file);
```

Όχι, όχι έτσι! Το μονοπάτι δεν ανήκει στα δεδομένα που χρειάζεται η κλάση `NewsletterDistributor` - στην πραγματικότητα, το `Logger` το χρειάζεται. Βλέπετε τη διαφορά; Η κλάση `NewsletterDistributor` χρειάζεται τον ίδιο τον καταγραφέα. Έτσι, αυτό είναι που θα περάσουμε:

```php
class NewsletterDistributor
{
	public function __construct(
		private Logger $logger, // ✅
	) {
	}

	public function distribute(): void
	{
		try {
			$this->sendEmails();
			$this->logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$this->logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}
```

Τώρα είναι σαφές από τις υπογραφές της κλάσης `NewsletterDistributor` ότι η καταγραφή αποτελεί επίσης μέρος της λειτουργικότητάς της. Και το έργο της ανταλλαγής του καταγραφέα με έναν άλλο, ίσως για δοκιμές, είναι εντελώς ασήμαντο.
Επιπλέον, αν αλλάξει ο κατασκευαστής της κλάσης `Logger`, αυτό δεν θα επηρεάσει την κλάση μας.


Κανόνας #2: Πάρτε ό,τι σας ανήκει .[#toc-rule-2-take-what-s-yours]
------------------------------------------------------------------

Μην παρασύρεστε και μην αφήνετε τον εαυτό σας να περάσει τις εξαρτήσεις των εξαρτημένων σας. Περάστε μόνο τις δικές σας εξαρτήσεις.

Χάρη σε αυτό, ο κώδικας που χρησιμοποιεί άλλα αντικείμενα θα είναι εντελώς ανεξάρτητος από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο ειλικρινές. Και πάνω απ' όλα, θα είναι τετριμμένο να αντικαταστήσετε αυτές τις εξαρτήσεις με άλλες.


Νέο μέλος της οικογένειας .[#toc-new-family-member]
---------------------------------------------------

Η ομάδα ανάπτυξης αποφάσισε να δημιουργήσει έναν δεύτερο καταγραφέα που γράφει στη βάση δεδομένων. Έτσι δημιουργούμε μια κλάση `DatabaseLogger`. Έτσι έχουμε δύο κλάσεις, `Logger` και `DatabaseLogger`, η μία γράφει σε ένα αρχείο, η άλλη σε μια βάση δεδομένων ... δεν σας φαίνεται περίεργη η ονομασία;
Δεν θα ήταν καλύτερα να μετονομάσετε την `Logger` σε `FileLogger`; Σίγουρα ναι.

Αλλά ας το κάνουμε έξυπνα. Δημιουργούμε μια διεπαφή με το αρχικό όνομα:

```php
interface Logger
{
	function log(string $message): void;
}
```

... το οποίο θα εφαρμόσουν και οι δύο καταγραφείς:

```php
class FileLogger implements Logger
// ...

class DatabaseLogger implements Logger
// ...
```

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

**Αυτός είναι ο λόγος για τον οποίο δεν προσθέτουμε ποτέ το επίθημα `Interface` ή το πρόθεμα `I` στα ονόματα των διεπαφών.** Διαφορετικά, δεν θα ήταν δυνατή η ανάπτυξη του κώδικα τόσο όμορφα.


Χιούστον, έχουμε ένα πρόβλημα .[#toc-houston-we-have-a-problem]
---------------------------------------------------------------

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

Ένα παράδειγμα μπορεί να είναι ένας ελεγκτής που θα πρέπει να αποθηκεύσει ένα άρθρο στη βάση δεδομένων μετά την υποβολή μιας φόρμας:

```php
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: πάρτε ό,τι σας ανήκει |#rule #2: take what's yours]. Αν ο κατασκευαστής της κλάσης `Article` αλλάξει (προστίθεται μια νέα παράμετρος), θα πρέπει να τροποποιήσετε τον κώδικα όπου δημιουργούνται περιπτώσεις. Ufff.

Χιούστον, τι προτείνεις;


Κανόνας #3: Αφήστε το εργοστάσιο να το χειριστεί .[#toc-rule-3-let-the-factory-handle-it]
-----------------------------------------------------------------------------------------

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

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

Τα εργοστάσια είναι μια πιο έξυπνη αντικατάσταση του τελεστή `new` στον κόσμο της έγχυσης εξαρτήσεων.

.[note]
Μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των εργοστασίων και δεν σχετίζεται με αυτό το θέμα.


Εργοστάσιο .[#toc-factory]
--------------------------

Ένα εργοστάσιο είναι μια μέθοδος ή μια κλάση που δημιουργεί και ρυθμίζει αντικείμενα. Θα ονομάσουμε την κλάση που παράγει το `Article` ως `ArticleFactory`, και θα μπορούσε να μοιάζει ως εξής:

```php
class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}
```

Η χρήση του στον ελεγκτή θα έχει ως εξής:

```php
class EditController extends Controller
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function formSubmitted($data)
	{
		// αφήστε το εργοστάσιο να δημιουργήσει ένα αντικείμενο
		$article = $this->articleFactory->create();
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}
```

Σε αυτό το σημείο, αν αλλάξει η υπογραφή του κατασκευαστή της κλάσης `Article`, το μόνο μέρος του κώδικα που χρειάζεται να αντιδράσει είναι το ίδιο το `ArticleFactory`. Όλος ο υπόλοιπος κώδικας που εργάζεται με αντικείμενα `Article`, όπως το `EditController`, δεν θα επηρεαστεί.

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

Μην ανησυχείτε, σύντομα θα φτάσουμε στο δοχείο Nette DI. Και έχει αρκετά κόλπα στο μανίκι του, τα οποία θα απλοποιήσουν σημαντικά τη δημιουργία εφαρμογών που χρησιμοποιούν dependency injection. Για παράδειγμα, αντί για την κλάση `ArticleFactory`, θα χρειαστεί να [γράψετε |factory] μόνο [μια απλή διεπαφή |factory]:

```php
interface ArticleFactory
{
	function create(): Article;
}
```

Αλλά βρισκόμαστε μπροστά από τους εαυτούς μας- σας παρακαλούμε να είστε υπομονετικοί :-)


Περίληψη .[#toc-summary]
------------------------

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

- [να περνούν τις εξαρτήσεις που χρειάζονται |#Rule #1: Let It Be Passed to You]
- [αντίστροφα, να μην περνούν ό,τι δεν χρειάζονται άμεσα |#Rule #2: Take What's Yours]
- [και ότι τα αντικείμενα με εξαρτήσεις είναι καλύτερο να δημιουργούνται σε εργοστάσια |#Rule #3: Let the Factory Handle it]

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

Τι γίνεται όμως αν ο κώδικας δεν χρησιμοποιεί με συνέπεια την έγχυση εξαρτήσεων; Τι γίνεται αν βασίζεται σε στατικές μεθόδους ή singletons; Προκαλεί αυτό προβλήματα; [Ναι, δημιουργεί, και μάλιστα πολύ θεμελιώδη |global-state].

Τι είναι το Dependency Injection;

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

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

Οι αρχές που θα παρουσιάσουμε εδώ είναι αρκετά απλές. Δεν χρειάζεται να ανησυχείτε για τίποτα.

Θυμάστε το πρώτο σας πρόγραμμα;

Δεν ξέρουμε σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, μπορεί να έμοιαζε κάπως έτσι:

function addition(float $a, float $b): float
{
	return $a + $b;
}

echo addition(23, 1); // εκτυπώσεις 24

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

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

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

Φανταστείτε τώρα ότι η υπογραφή της συνάρτησης έμοιαζε ως εξής:

function addition(float $x): float

Μια προσθήκη με μία παράμετρο; Αυτό είναι παράξενο… Τι λέτε γι' αυτό;

function addition(): float

Τώρα αυτό είναι πραγματικά περίεργο, σωστά; Πώς χρησιμοποιείται η συνάρτηση;

echo addition(); // τι εκτυπώνει;

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

Αναρωτιέστε πώς θα έμοιαζε στην πραγματικότητα μια τέτοια συνάρτηση στο εσωτερικό της; Από πού θα έπαιρνε τα αθροίσματα; Πιθανότατα θα τις έπαιρνε με κάποιο τρόπο από μόνη της, ίσως κάπως έτσι:

function addition(): float
{
	$a = Input::get('a');
	$b = Input::get('b');
	return $a + $b;
}

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

Όχι με αυτόν τον τρόπο!

Ο σχεδιασμός που μόλις δείξαμε είναι η ουσία πολλών αρνητικών χαρακτηριστικών:

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

Και μήπως είναι καν δουλειά της συνάρτησης πρόσθεσης να προμηθεύεται εισόδους; Φυσικά και δεν είναι. Η ευθύνη της είναι μόνο να προσθέτει.

Δεν θέλουμε να συναντήσουμε τέτοιο κώδικα και σίγουρα δεν θέλουμε να τον γράψουμε. Η λύση είναι απλή: επιστρέψτε στα βασικά και χρησιμοποιήστε απλά παραμέτρους:

function addition(float $a, float $b): float
{
	return $a + $b;
}

Κανόνας #1: Αφήστε να περάσει σε σας

Ο πιο σημαντικός κανόνας είναι: Όλα τα δεδομένα που χρειάζονται οι συναρτήσεις ή οι κλάσεις πρέπει να τους μεταβιβάζονται.

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

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

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

Παρακαλώ μην συγχέετε την έγχυση εξαρτήσεων, η οποία είναι ένα πρότυπο σχεδίασης, με ένα „dependency injection container“, το οποίο είναι ένα εργαλείο, κάτι διαμετρικά διαφορετικό. Θα ασχοληθούμε με τους περιέκτες αργότερα.

Από τις συναρτήσεις στις κλάσεις

Και πώς συνδέονται οι τάξεις; Μια κλάση είναι μια πιο σύνθετη μονάδα από μια απλή συνάρτηση, αλλά ο κανόνας #1 ισχύει πλήρως και εδώ. Απλά υπάρχουν περισσότεροι τρόποι για να περάσετε ορίσματα. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης:

class Math
{
	public function addition(float $a, float $b): float
	{
		return $a + $b;
	}
}

$math = new Math;
echo $math->addition(23, 1); // 24

Ή μέσω άλλων μεθόδων ή απευθείας μέσω του κατασκευαστή:

class Addition
{
	public function __construct(
		private float $a,
		private float $b,
	) {
	}

	public function calculate(): float
	{
		return $this->a + $this->b;
	}

}

$addition = new Addition(23, 1);
echo $addition->calculate(); // 24

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

Παραδείγματα πραγματικής ζωής

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

Ας έχουμε μια κλάση Article που αναπαριστά μια δημοσίευση σε ιστολόγιο:

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: Let It Be Passed to You: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να περάσουν σε αυτήν. Διότι αν παραβιάσουμε τον κανόνα, έχουμε ξεκινήσει μια πορεία προς βρώμικο κώδικα γεμάτο κρυφές εξαρτήσεις, ακατανόητο και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι οδυνηρή στη συντήρηση και την ανάπτυξη.

Ο χρήστης της κλάσης Article δεν έχει ιδέα πού αποθηκεύει το άρθρο η μέθοδος save(). Σε έναν πίνακα της βάσης δεδομένων; Σε ποιον, στην παραγωγή ή στις δοκιμές; Και πώς μπορεί να αλλάξει;

Ο χρήστης πρέπει να κοιτάξει πώς υλοποιείται η μέθοδος save() και να βρει τη χρήση της μεθόδου DB::insert(). Έτσι, πρέπει να ψάξει περαιτέρω για να βρει πώς αυτή η μέθοδος αποκτά μια σύνδεση με τη βάση δεδομένων. Και οι κρυφές εξαρτήσεις μπορεί να σχηματίσουν μια αρκετά μεγάλη αλυσίδα.

Σε καθαρό και καλά σχεδιασμένο κώδικα, δεν υπάρχουν ποτέ κρυφές εξαρτήσεις, προσόψεις του 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() – θα έπρεπε να αντιπροσωπεύει ένα καθαρά στοιχείο δεδομένων και ένα ξεχωριστό αποθετήριο θα έπρεπε να φροντίζει για την αποθήκευση. Αυτό είναι λογικό. Αλλά αυτό θα μας πήγαινε πολύ πέρα από το αντικείμενο του θέματος, που είναι η έγχυση εξαρτήσεων, και την προσπάθεια να δώσουμε απλά παραδείγματα.

Αν γράψετε μια κλάση που απαιτεί, για παράδειγμα, μια βάση δεδομένων για τη λειτουργία της, μην επινοήσετε από πού θα την πάρετε, αλλά να την έχετε περάσει. Είτε ως παράμετρο του κατασκευαστή είτε ως κάποια άλλη μέθοδο. Παραδεχτείτε εξαρτήσεις. Παραδεχτείτε τις στο 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('The temperature is 23 °C');
$logger->log('The temperature is 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('The temperature is 15 °C');

Αλλά δεν με νοιάζει!

„Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχοληθώ με τη βάση δεδομένων- θέλω απλώς να αποθηκευτεί σε αυτήν που έχω ορίσει στη ρύθμιση παραμέτρων.“

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

Αυτά είναι βάσιμα σημεία.

Ως παράδειγμα, ας δούμε μια κλάση που στέλνει ενημερωτικά δελτία και καταγράφει πώς πήγε:

class NewsletterDistributor
{
	public function distribute(): void
	{
		$logger = new Logger(/* ... */);
		try {
			$this->sendEmails();
			$logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}

Το βελτιωμένο Logger, το οποίο δεν χρησιμοποιεί πλέον τη σταθερά LOG_DIR, απαιτεί τον προσδιορισμό της διαδρομής του αρχείου στον κατασκευαστή. Πώς να το λύσετε αυτό; Η κλάση NewsletterDistributor δεν ενδιαφέρεται για το πού γράφονται τα μηνύματα- θέλει απλώς να τα γράψει.

Η λύση είναι και πάλι ο κανόνας #1: Let It Be Passed to You: περάστε όλα τα δεδομένα που χρειάζεται η κλάση.

Αυτό λοιπόν σημαίνει ότι περνάμε τη διαδρομή προς το αρχείο καταγραφής μέσω του κατασκευαστή, την οποία στη συνέχεια χρησιμοποιούμε κατά τη δημιουργία του αντικειμένου Logger;

class NewsletterDistributor
{
	public function __construct(
		private string $file, // ⛔ ΌΧΙ ΜΕ ΑΥΤΌΝ ΤΟΝ ΤΡΌΠΟ!
	) {
	}

	public function distribute(): void
	{
		$logger = new Logger($this->file);

Όχι, όχι έτσι! Το μονοπάτι δεν ανήκει στα δεδομένα που χρειάζεται η κλάση NewsletterDistributor – στην πραγματικότητα, το Logger το χρειάζεται. Βλέπετε τη διαφορά; Η κλάση NewsletterDistributor χρειάζεται τον ίδιο τον καταγραφέα. Έτσι, αυτό είναι που θα περάσουμε:

class NewsletterDistributor
{
	public function __construct(
		private Logger $logger, // ✅
	) {
	}

	public function distribute(): void
	{
		try {
			$this->sendEmails();
			$this->logger->log('Emails have been sent out');

		} catch (Exception $e) {
			$this->logger->log('An error occurred during the sending');
			throw $e;
		}
	}
}

Τώρα είναι σαφές από τις υπογραφές της κλάσης NewsletterDistributor ότι η καταγραφή αποτελεί επίσης μέρος της λειτουργικότητάς της. Και το έργο της ανταλλαγής του καταγραφέα με έναν άλλο, ίσως για δοκιμές, είναι εντελώς ασήμαντο. Επιπλέον, αν αλλάξει ο κατασκευαστής της κλάσης Logger, αυτό δεν θα επηρεάσει την κλάση μας.

Κανόνας #2: Πάρτε ό,τι σας ανήκει

Μην παρασύρεστε και μην αφήνετε τον εαυτό σας να περάσει τις εξαρτήσεις των εξαρτημένων σας. Περάστε μόνο τις δικές σας εξαρτήσεις.

Χάρη σε αυτό, ο κώδικας που χρησιμοποιεί άλλα αντικείμενα θα είναι εντελώς ανεξάρτητος από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο ειλικρινές. Και πάνω απ' όλα, θα είναι τετριμμένο να αντικαταστήσετε αυτές τις εξαρτήσεις με άλλες.

Νέο μέλος της οικογένειας

Η ομάδα ανάπτυξης αποφάσισε να δημιουργήσει έναν δεύτερο καταγραφέα που γράφει στη βάση δεδομένων. Έτσι δημιουργούμε μια κλάση DatabaseLogger. Έτσι έχουμε δύο κλάσεις, Logger και DatabaseLogger, η μία γράφει σε ένα αρχείο, η άλλη σε μια βάση δεδομένων … δεν σας φαίνεται περίεργη η ονομασία; Δεν θα ήταν καλύτερα να μετονομάσετε την Logger σε FileLogger; Σίγουρα ναι.

Αλλά ας το κάνουμε έξυπνα. Δημιουργούμε μια διεπαφή με το αρχικό όνομα:

interface Logger
{
	function log(string $message): void;
}

… το οποίο θα εφαρμόσουν και οι δύο καταγραφείς:

class FileLogger implements Logger
// ...

class DatabaseLogger implements Logger
// ...

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

Αυτός είναι ο λόγος για τον οποίο δεν προσθέτουμε ποτέ το επίθημα Interface ή το πρόθεμα I στα ονόματα των διεπαφών. Διαφορετικά, δεν θα ήταν δυνατή η ανάπτυξη του κώδικα τόσο όμορφα.

Χιούστον, έχουμε ένα πρόβλημα

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

Ένα παράδειγμα μπορεί να είναι ένας ελεγκτής που θα πρέπει να αποθηκεύσει ένα άρθρο στη βάση δεδομένων μετά την υποβολή μιας φόρμας:

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 αλλάξει (προστίθεται μια νέα παράμετρος), θα πρέπει να τροποποιήσετε τον κώδικα όπου δημιουργούνται περιπτώσεις. Ufff.

Χιούστον, τι προτείνεις;

Κανόνας #3: Αφήστε το εργοστάσιο να το χειριστεί

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

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

Τα εργοστάσια είναι μια πιο έξυπνη αντικατάσταση του τελεστή new στον κόσμο της έγχυσης εξαρτήσεων.

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

Εργοστάσιο

Ένα εργοστάσιο είναι μια μέθοδος ή μια κλάση που δημιουργεί και ρυθμίζει αντικείμενα. Θα ονομάσουμε την κλάση που παράγει το Article ως ArticleFactory, και θα μπορούσε να μοιάζει ως εξής:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Η χρήση του στον ελεγκτή θα έχει ως εξής:

class EditController extends Controller
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function formSubmitted($data)
	{
		// αφήστε το εργοστάσιο να δημιουργήσει ένα αντικείμενο
		$article = $this->articleFactory->create();
		$article->title = $data->title;
		$article->content = $data->content;
		$article->save();
	}
}

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

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

Μην ανησυχείτε, σύντομα θα φτάσουμε στο δοχείο Nette DI. Και έχει αρκετά κόλπα στο μανίκι του, τα οποία θα απλοποιήσουν σημαντικά τη δημιουργία εφαρμογών που χρησιμοποιούν dependency injection. Για παράδειγμα, αντί για την κλάση ArticleFactory, θα χρειαστεί να γράψετε μόνο μια απλή διεπαφή:

interface ArticleFactory
{
	function create(): Article;
}

Αλλά βρισκόμαστε μπροστά από τους εαυτούς μας- σας παρακαλούμε να είστε υπομονετικοί :-)

Περίληψη

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

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

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