Opierając się na szczegółowej implementacji klasy podczas wstrzykiwania zależności tworzymy sprzężenie pomiędzy klasą a zależnością. Kod staje się sztywny, a ewentualna podmiana zależności stanowi problem. W celu stworzenia elastycznego rozwiązania powinniśmy przestrzegać zasady Dependency inversion principle, która prezentuje się następująco:
Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Jedne i drugie powinny zależeć od abstrakcji. Abstrakcje nie powinny zależeć od szczegółów. To szczegóły powinny zależeć od abstrakcji.
Przykład złamania
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <?php
/**
* Class EntityManager
*/
final class EntityManager
{
/**
* @var MysqlConnection
*/
private $connection;
/**
* EntityManager constructor.
* @param MysqlConnection $connection
*/
public function __construct(MysqlConnection $connection)
{
$this->connection = $connection;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
| <?php
/**
* Class MysqlConnection
*/
final class MysqlConnection
{
public function connect(): void
{
}
}
|
W powyższym przykładzie mamy dwie klasy:
- EntityManager – utrwalanie obiektów w bazie danych
- MysqlConnection – udostępnianie operacji na bazie danych MySQL Baz danych możemy używać jednak różnych, więc kod napisany w ten sposób nie jest elastyczny i łamie zasadę DIP. EntityManager operuje na szczegółowej implementacji MysqlConnection zamiast na abstrakcji.
Przykład poprawnej implementacji
1
2
3
4
5
6
7
8
9
| <?php
/**
* Interface DatabaseConnectionInterface
*/
interface DatabaseConnectionInterface
{
public function connect(): void;
}
|
1
2
3
4
5
6
7
8
9
10
11
| <?php
/**
* Class MysqlConnection
*/
final class MysqlConnection implements DatabaseConnectionInterface
{
public function connect(): void
{
}
}
|
1
2
3
4
5
6
7
8
| <?php
/**
* Interface EntityManagerInterface
*/
interface EntityManagerInterface
{
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <?php
/**
* Class EntityManager
*/
final class EntityManager implements EntityManagerInterface
{
/**
* @var DatabaseConnectionInterface
*/
private $connection;
/**
* EntityManager constructor.
* @param DatabaseConnectionInterface $connection
*/
public function __construct(DatabaseConnectionInterface $connection)
{
$this->connection = $connection;
}
}
|
Obecnie EntityManager operuje na interfejsie DatabaseConnectionInterface, więc dodanie innej szczegółowej implementacji tego interfejsu nie stanowi problemu. Kod został tak zrefaktoryzowany, że spełnia zasadę DIP.
Należy pamiętać, że ta, jak i pozostałe zasady SOLID dotyczą głównie kodu, który będzie w przyszłości zmieniany, więc jeśli w niektórych przypadkach posłużymy się szczegółową implementacją zamiast abstrakcją, to nie powinno to wpłynąć na trudność utrzymania projektu. Powinniśmy umieć wyważyć w jakich sytuacjach pisanie kodu w stu procentach zgodnego z SOLID jest konieczne, a w jakich nie.