1. Вы всё верно поняли. Сложность в том что внутри может оказаться не та структура которую вы ожидайте (ожидалась)
Но это не значит что это плохо. Эту проблему можно решить валидацией. В вашем примере достаточно проверять на instanceOf. Тогда код станет правильный. А в symfony сделали это ещё лучше и добавили строгость к типам для сервис локатора через подписку на сервисы.
Что бы понять когда применять сервис локатор, а когда инъекцию пройдите по ссылке
https://symfony.com/doc/current/service_container/... и почитайте.
Там есть пояснение:
Sometimes, a service needs access to several other services without being sure that all of them will actually be used. In those cases, you may want the instantiation of the services to be lazy. However, that’s not possible using the explicit dependency injection since services are not all meant to be lazy
Можно пояснить на примере: допустим у вас есть класс с методом, и внутри метода только в некоторых случаях вам понадобится сделать запись в БД (допустим обращение к сервису в виде get->('db')).
Например:
if($userData = $request->get('user_data')) {
$db = $serviceLocator->get('db')->insert($userData);
} else {
die("user data empty");
}
Если делать через инъекцию, то $db всегда будет инициализирована, например если проводить инъекцию через конструктор или метод, то объект вашего класса всегда будет при любых условиях запускать код инициализации $db и тратить на это ресурсы. Пример через инъекцию (тут тип инъекции через аргумент метода, аналогично можно сделать через конструктор или сеттер)
class MyClass {
// $db будет инициализрован, но используется только в 1 случае для if
// если $db тяжёлый сервис и инициализируется долго это проблема!
function httpResponse(Request $request, Database $db) {
if($userData = $request->get('user_data')) {
$db->insert($userData);
} else {
die("user data empty");
}
}
}
А в случае сервис локатора $db будет инициализирован только в условии когда придёт user_data. Таким образом если у вас контроллер использует 15 сервисов (но это bad code!), а одновременно нужен только 1 из них, лучше использовать сервис локатор.
class MyClass {
// В таком случае рекомендуется воспользоваться сервис локатором!
function httpResponse(Request $request, Database $db, Servive1 $s1, Service2 $s2) {
if($userData = $request->get('user_data')) {
$db->insert($userData);
if ($db->lastId()) {
$s1->makeSuccess();
if($s1->isSuccess()) {
$s2->commit();
}
}
} else {
$s2->rollback();
die("user data empty");
}
}
}
Отсюда следует почему сервис локатор считается антипаттерном. Если ваш класс использует 15 сервисов, и из них все используются выборочно, то вам вероятно следует изменить архитектурный подход, провести рефакторинг, провести декомпозицию класса.
2. Постановка вопроса некорректная. Ответить на ваш вопрос однозначно невозможно. В некоторых случаях сервис локатор удобнее, чисто с технической точки зрения. В некоторых случаях это антипаттерн, в некоторых это вопрос принятых норм и организации кода внутри команды.
P.S:
Использовать service locator можно и в этом нет ничего страшного, особенно когда есть понимание что это такое и для чего он нужен.