Navigation Menu

Skip to content

Commit

Permalink
di/passing-dependencies: added constructor hell
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 29, 2023
1 parent 4dfe968 commit 73b3c27
Show file tree
Hide file tree
Showing 17 changed files with 1,023 additions and 3 deletions.
60 changes: 60 additions & 0 deletions dependency-injection/bg/passing-dependencies.texy
Expand Up @@ -65,6 +65,66 @@ class MyClass
Контейнерът DI предава зависимостите на конструктора автоматично, като използва [автоматично свързване |autowiring]. Аргументите, които не могат да бъдат предадени по този начин (напр. низове, числа, булеви стойности), [се записват в конфигурацията |services#Arguments].


Адът на конструкторите .[#toc-constructor-hell]
-----------------------------------------------

Терминът *ад на конструкторите* се отнася до ситуация, при която наследник наследява родителски клас, чийто конструктор изисква зависимости, и наследникът също изисква зависимости. То също трябва да поеме и предаде зависимостите на родителя:

```php
abstract class BaseClass
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass extends BaseClass
{
private Database $db;

// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
```

Проблемът възниква, когато искаме да променим конструктора на класа `BaseClass`, например когато се добави нова зависимост. Тогава трябва да променим и всички конструктори на децата. Което превръща подобна модификация в ад.

Как да предотвратим това? Решението е да се даде **приоритет на композицията пред наследяването**.

Така че нека да проектираме кода по различен начин. Ще избягваме абстрактните класове `Base*`. Вместо `MyClass` да получава някаква функционалност, наследявайки я от `BaseClass`, тя ще има тази функционалност, предадена като зависимост:

```php
final class SomeFunctionality
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass
{
private SomeFunctionality $sf;
private Database $db;

public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
```


Зависимости чрез задаващи елементи .[#toc-setter-injection]
===========================================================

Expand Down
60 changes: 60 additions & 0 deletions dependency-injection/cs/passing-dependencies.texy
Expand Up @@ -65,6 +65,66 @@ class MyClass
DI kontejner předá konstruktoru závislosti automaticky pomocí [autowiringu |autowiring]. Argumenty, které takto předat nelze (např. řetězce, čísla, booleany) [zapíšeme v konfiguraci |services#Argumenty].


Constructor hell
----------------

Termín *constructor hell* označuje situaci, když potomek dědí od rodičovské třídy, jejíž konstruktor vyžaduje závislosti, a zároveň potomek vyžaduje závislosti. Přitom musí převzít a předat i ty rodičovské:

```php
abstract class BaseClass
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass extends BaseClass
{
private Database $db;

// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
```

Problém nastane v okamžiku, kdy budeme chtít změnit kontruktor třídy `BaseClass`, třeba když přibude nová závislost. Pak je totiž nutné upravit také všechny konstruktory potomků. Což z takové úpravy dělá peklo.

Jak tomu předcházet? Řešením je **dávat přednost kompozici před dědičností.**

Tedy navrhneme kód jinak. Budeme se vyhýbat abstraktním `Base*` třídám. Místo toho, aby `MyClass` získávala určitou funkčnost tím, že dědí od `BaseClass`, si tuto funkčnost nechá předat jako závislost:

```php
final class SomeFunctionality
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass
{
private SomeFunctionality $sf;
private Database $db;

public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
```


Předávání setterem
==================

Expand Down
60 changes: 60 additions & 0 deletions dependency-injection/de/passing-dependencies.texy
Expand Up @@ -65,6 +65,66 @@ class MyClass
Der DI-Container übergibt Abhängigkeiten automatisch an den Konstruktor mittels [Autowiring |autowiring]. Argumente, die nicht auf diese Weise übergeben werden können (z.B. Strings, Zahlen, Booleans), [werden in die Konfiguration geschrieben |services#Arguments].


Konstrukteur-Hölle .[#toc-constructor-hell]
-------------------------------------------

Der Begriff *Konstruktorhölle* bezieht sich auf eine Situation, in der ein Kind von einer Elternklasse erbt, deren Konstruktor Abhängigkeiten benötigt, und das Kind benötigt ebenfalls Abhängigkeiten. Es muss auch die Abhängigkeiten der Elternklasse übernehmen und weitergeben:

```php
abstract class BaseClass
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass extends BaseClass
{
private Database $db;

// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
```

Das Problem tritt auf, wenn wir den Konstruktor der Klasse `BaseClass` ändern wollen, zum Beispiel wenn eine neue Abhängigkeit hinzugefügt wird. Dann müssen wir auch alle Konstruktoren der Kinder ändern. Das macht eine solche Änderung zur Hölle.

Wie lässt sich das verhindern? Die Lösung besteht darin, **Komposition gegenüber Vererbung** zu bevorzugen.

Lassen Sie uns also den Code anders gestalten. Wir werden abstrakte `Base*` Klassen vermeiden. Anstatt dass `MyClass` eine bestimmte Funktionalität durch Vererbung von `BaseClass` erhält, wird diese Funktionalität als Abhängigkeit übergeben:

```php
final class SomeFunctionality
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass
{
private SomeFunctionality $sf;
private Database $db;

public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
```


Setter-Injektion .[#toc-setter-injection]
=========================================

Expand Down
60 changes: 60 additions & 0 deletions dependency-injection/el/passing-dependencies.texy
Expand Up @@ -65,6 +65,66 @@ class MyClass
Το DI container περνάει τις εξαρτήσεις στον κατασκευαστή αυτόματα χρησιμοποιώντας [την αυτόματη σύνδεση (autowiring |autowiring]). Τα επιχειρήματα που δεν μπορούν να περάσουν με αυτόν τον τρόπο (π.χ. συμβολοσειρές, αριθμοί, booleans) [γράφονται στη διαμόρφωση |services#Arguments].


Κόλαση του κατασκευαστή .[#toc-constructor-hell]
------------------------------------------------

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

```php
abstract class BaseClass
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass extends BaseClass
{
private Database $db;

// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
```

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

Πώς μπορεί να αποφευχθεί αυτό; Η λύση είναι η **προτεραιότητα της σύνθεσης έναντι της κληρονομικότητας**.

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

```php
final class SomeFunctionality
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass
{
private SomeFunctionality $sf;
private Database $db;

public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
```


Έγχυση ρυθμιστή .[#toc-setter-injection]
========================================

Expand Down
60 changes: 60 additions & 0 deletions dependency-injection/en/passing-dependencies.texy
Expand Up @@ -65,6 +65,66 @@ class MyClass
DI container passes dependencies to the constructor automatically using [autowiring]. Arguments that cannot be passed in this way (e.g. strings, numbers, booleans) [write in configuration |services#Arguments].


Constructor Hell
----------------

The term *constructor hell* refers to a situation where a child inherits from a parent class whose constructor requires dependencies, and the child requires dependencies too. It must also take over and pass on the parent's dependencies:

```php
abstract class BaseClass
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass extends BaseClass
{
private Database $db;

// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
```

The problem occurs when we want to change the constructor of the `BaseClass` class, for example when a new dependency is added. Then we have to modify all the constructors of the children as well. Which makes such a modification hell.

How to prevent this? The solution is to **prioritize composition over inheritance**.

So let's design the code differently. We'll avoid abstract `Base*` classes. Instead of `MyClass` getting some functionality by inheriting from `BaseClass`, it will have that functionality passed as a dependency:

```php
final class SomeFunctionality
{
private Cache $cache;

public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

final class MyClass
{
private SomeFunctionality $sf;
private Database $db;

public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
```


Setter Injection
================

Expand Down

0 comments on commit 73b3c27

Please sign in to comment.