Skip to content

Commit 4f8c7ed

Browse files
committed
di/passing-dependencies: added constructor hell
1 parent 9fed71b commit 4f8c7ed

17 files changed

+1023
-3
lines changed

dependency-injection/bg/passing-dependencies.texy

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,66 @@ class MyClass
6565
Контейнерът DI предава зависимостите на конструктора автоматично, като използва [автоматично свързване |autowiring]. Аргументите, които не могат да бъдат предадени по този начин (напр. низове, числа, булеви стойности), [се записват в конфигурацията |services#Arguments].
6666

6767

68+
Адът на конструкторите .[#toc-constructor-hell]
69+
-----------------------------------------------
70+
71+
Терминът *ад на конструкторите* се отнася до ситуация, при която наследник наследява родителски клас, чийто конструктор изисква зависимости, и наследникът също изисква зависимости. То също трябва да поеме и предаде зависимостите на родителя:
72+
73+
```php
74+
abstract class BaseClass
75+
{
76+
private Cache $cache;
77+
78+
public function __construct(Cache $cache)
79+
{
80+
$this->cache = $cache;
81+
}
82+
}
83+
84+
final class MyClass extends BaseClass
85+
{
86+
private Database $db;
87+
88+
// ⛔ CONSTRUCTOR HELL
89+
public function __construct(Cache $cache, Database $db)
90+
{
91+
parent::__construct($cache);
92+
$this->db = $db;
93+
}
94+
}
95+
```
96+
97+
Проблемът възниква, когато искаме да променим конструктора на класа `BaseClass`, например когато се добави нова зависимост. Тогава трябва да променим и всички конструктори на децата. Което превръща подобна модификация в ад.
98+
99+
Как да предотвратим това? Решението е да се даде **приоритет на композицията пред наследяването**.
100+
101+
Така че нека да проектираме кода по различен начин. Ще избягваме абстрактните класове `Base*`. Вместо `MyClass` да получава някаква функционалност, наследявайки я от `BaseClass`, тя ще има тази функционалност, предадена като зависимост:
102+
103+
```php
104+
final class SomeFunctionality
105+
{
106+
private Cache $cache;
107+
108+
public function __construct(Cache $cache)
109+
{
110+
$this->cache = $cache;
111+
}
112+
}
113+
114+
final class MyClass
115+
{
116+
private SomeFunctionality $sf;
117+
private Database $db;
118+
119+
public function __construct(SomeFunctionality $sf, Database $db) // ✅
120+
{
121+
$this->sf = $sf;
122+
$this->db = $db;
123+
}
124+
}
125+
```
126+
127+
68128
Зависимости чрез задаващи елементи .[#toc-setter-injection]
69129
===========================================================
70130

dependency-injection/cs/passing-dependencies.texy

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,66 @@ class MyClass
6565
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].
6666

6767

68+
Constructor hell
69+
----------------
70+
71+
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é:
72+
73+
```php
74+
abstract class BaseClass
75+
{
76+
private Cache $cache;
77+
78+
public function __construct(Cache $cache)
79+
{
80+
$this->cache = $cache;
81+
}
82+
}
83+
84+
final class MyClass extends BaseClass
85+
{
86+
private Database $db;
87+
88+
// ⛔ CONSTRUCTOR HELL
89+
public function __construct(Cache $cache, Database $db)
90+
{
91+
parent::__construct($cache);
92+
$this->db = $db;
93+
}
94+
}
95+
```
96+
97+
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.
98+
99+
Jak tomu předcházet? Řešením je **dávat přednost kompozici před dědičností.**
100+
101+
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:
102+
103+
```php
104+
final class SomeFunctionality
105+
{
106+
private Cache $cache;
107+
108+
public function __construct(Cache $cache)
109+
{
110+
$this->cache = $cache;
111+
}
112+
}
113+
114+
final class MyClass
115+
{
116+
private SomeFunctionality $sf;
117+
private Database $db;
118+
119+
public function __construct(SomeFunctionality $sf, Database $db) // ✅
120+
{
121+
$this->sf = $sf;
122+
$this->db = $db;
123+
}
124+
}
125+
```
126+
127+
68128
Předávání setterem
69129
==================
70130

dependency-injection/de/passing-dependencies.texy

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,66 @@ class MyClass
6565
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].
6666

6767

68+
Konstrukteur-Hölle .[#toc-constructor-hell]
69+
-------------------------------------------
70+
71+
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:
72+
73+
```php
74+
abstract class BaseClass
75+
{
76+
private Cache $cache;
77+
78+
public function __construct(Cache $cache)
79+
{
80+
$this->cache = $cache;
81+
}
82+
}
83+
84+
final class MyClass extends BaseClass
85+
{
86+
private Database $db;
87+
88+
// ⛔ CONSTRUCTOR HELL
89+
public function __construct(Cache $cache, Database $db)
90+
{
91+
parent::__construct($cache);
92+
$this->db = $db;
93+
}
94+
}
95+
```
96+
97+
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.
98+
99+
Wie lässt sich das verhindern? Die Lösung besteht darin, **Komposition gegenüber Vererbung** zu bevorzugen.
100+
101+
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:
102+
103+
```php
104+
final class SomeFunctionality
105+
{
106+
private Cache $cache;
107+
108+
public function __construct(Cache $cache)
109+
{
110+
$this->cache = $cache;
111+
}
112+
}
113+
114+
final class MyClass
115+
{
116+
private SomeFunctionality $sf;
117+
private Database $db;
118+
119+
public function __construct(SomeFunctionality $sf, Database $db) // ✅
120+
{
121+
$this->sf = $sf;
122+
$this->db = $db;
123+
}
124+
}
125+
```
126+
127+
68128
Setter-Injektion .[#toc-setter-injection]
69129
=========================================
70130

dependency-injection/el/passing-dependencies.texy

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,66 @@ class MyClass
6565
Το DI container περνάει τις εξαρτήσεις στον κατασκευαστή αυτόματα χρησιμοποιώντας [την αυτόματη σύνδεση (autowiring |autowiring]). Τα επιχειρήματα που δεν μπορούν να περάσουν με αυτόν τον τρόπο (π.χ. συμβολοσειρές, αριθμοί, booleans) [γράφονται στη διαμόρφωση |services#Arguments].
6666

6767

68+
Κόλαση του κατασκευαστή .[#toc-constructor-hell]
69+
------------------------------------------------
70+
71+
Ο όρος *κόλαση κατασκευαστών* αναφέρεται σε μια κατάσταση όπου ένα παιδί κληρονομεί από μια γονική κλάση της οποίας ο κατασκευαστής απαιτεί εξαρτήσεις και το παιδί απαιτεί επίσης εξαρτήσεις. Πρέπει επίσης να αναλάβει και να μεταβιβάσει τις εξαρτήσεις του γονέα:
72+
73+
```php
74+
abstract class BaseClass
75+
{
76+
private Cache $cache;
77+
78+
public function __construct(Cache $cache)
79+
{
80+
$this->cache = $cache;
81+
}
82+
}
83+
84+
final class MyClass extends BaseClass
85+
{
86+
private Database $db;
87+
88+
// ⛔ CONSTRUCTOR HELL
89+
public function __construct(Cache $cache, Database $db)
90+
{
91+
parent::__construct($cache);
92+
$this->db = $db;
93+
}
94+
}
95+
```
96+
97+
Το πρόβλημα εμφανίζεται όταν θέλουμε να αλλάξουμε τον κατασκευαστή της κλάσης `BaseClass`, για παράδειγμα όταν προστίθεται μια νέα εξάρτηση. Τότε πρέπει να τροποποιήσουμε και όλους τους κατασκευαστές των παιδιών. Το οποίο κάνει μια τέτοια τροποποίηση κόλαση.
98+
99+
Πώς μπορεί να αποφευχθεί αυτό; Η λύση είναι η **προτεραιότητα της σύνθεσης έναντι της κληρονομικότητας**.
100+
101+
Ας σχεδιάσουμε λοιπόν τον κώδικα με διαφορετικό τρόπο. Θα αποφύγουμε τις αφηρημένες κλάσεις `Base*`. Αντί το `MyClass` να παίρνει κάποια λειτουργικότητα κληρονομώντας από το `BaseClass`, θα έχει αυτή τη λειτουργικότητα περασμένη ως εξάρτηση:
102+
103+
```php
104+
final class SomeFunctionality
105+
{
106+
private Cache $cache;
107+
108+
public function __construct(Cache $cache)
109+
{
110+
$this->cache = $cache;
111+
}
112+
}
113+
114+
final class MyClass
115+
{
116+
private SomeFunctionality $sf;
117+
private Database $db;
118+
119+
public function __construct(SomeFunctionality $sf, Database $db) // ✅
120+
{
121+
$this->sf = $sf;
122+
$this->db = $db;
123+
}
124+
}
125+
```
126+
127+
68128
Έγχυση ρυθμιστή .[#toc-setter-injection]
69129
========================================
70130

dependency-injection/en/passing-dependencies.texy

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,66 @@ class MyClass
6565
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].
6666

6767

68+
Constructor Hell
69+
----------------
70+
71+
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:
72+
73+
```php
74+
abstract class BaseClass
75+
{
76+
private Cache $cache;
77+
78+
public function __construct(Cache $cache)
79+
{
80+
$this->cache = $cache;
81+
}
82+
}
83+
84+
final class MyClass extends BaseClass
85+
{
86+
private Database $db;
87+
88+
// ⛔ CONSTRUCTOR HELL
89+
public function __construct(Cache $cache, Database $db)
90+
{
91+
parent::__construct($cache);
92+
$this->db = $db;
93+
}
94+
}
95+
```
96+
97+
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.
98+
99+
How to prevent this? The solution is to **prioritize composition over inheritance**.
100+
101+
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:
102+
103+
```php
104+
final class SomeFunctionality
105+
{
106+
private Cache $cache;
107+
108+
public function __construct(Cache $cache)
109+
{
110+
$this->cache = $cache;
111+
}
112+
}
113+
114+
final class MyClass
115+
{
116+
private SomeFunctionality $sf;
117+
private Database $db;
118+
119+
public function __construct(SomeFunctionality $sf, Database $db) // ✅
120+
{
121+
$this->sf = $sf;
122+
$this->db = $db;
123+
}
124+
}
125+
```
126+
127+
68128
Setter Injection
69129
================
70130

0 commit comments

Comments
 (0)