Newsgroup: it.comp.www.php
Titolo: Iniezione delle dipendenze vs Service Locator

https://medium.com/@ivorobioff/dependency-injection-vs-service-locator-2bb8484c2e20
 
 
cosa ne pensate?

 cosa ne pensate?
 
Dipende quanto controllo vuoi cedere.
 
Partiamo da una premessa: un SL implementato come nell'esempio è
pessimo, ma è anche vero che la maggior parte degli sviluppatori in
linguaggi dinamici tende a implementarlo così, per poi "vendere" un
framework che lo fa per te.
 
Il mio punto è che non serve una libreria esterna per implementare un SL:
 
class ServiceLocator {
  static function getHouse(): House {
    static $house = null;
    if (is_null($house)) {
      $house = new MyHouse();
    }
    return $house;
  }
 
  static function getWindow(): Window {
    return new Window();
  }
  ...
}
 
Vantaggi:
1- puoi avere strict typing
2- l'editor sa che oggetto torni e può autocompletarti i metodi
3- l'editor sa se il SL fornisce un service o meno
4- hai un errore (potenzialmente) molto prima da parte del compilatore
 
Svantaggi:
1- Devi modificare il SL quando aggiungi un servizio
 
Io non ho dubbi su cosa scegliere. :P
 
Ora veniamo alla DI. La DI dà allo sviluppatore controllo totale su cosa
passare a un oggetto. Invece di istanziare un oggetto e passargli il SL,
gli passi direttamente le dipendenze. Lo svantaggio è che se istanzi un
oggetto in profondità nel codice devi passare le dipendenze attraverso
tutto l'albero.
 
Nella mia esperienza conviene sempre tenere la struttura più piatta
possibile, ma siccome non è sempre facile, io preferisco avere un SL che
passo ai livelli più alti, i quali poi usano DI prendendo la roba dal SL
quando istanziano un oggetto.
 
Esempio insulso. Classico sito web "MV*".
 
Istanzio i vari repository (interfacce verso DB, servizi esterni,
logger, ecc.) e li infilo nel SL (o li faccio creare dal SL direttamente
passandogli i parametri di connessione, di solito via env vars).
 
Poi istanzio il router e passo il SL a ogni
Controller/ViewModel/Presenter/quellocheè.
 
Il controller può chiedere al SL la connessione al DB, il logger, il
mailer e magari il template engine e (se non li usa direttamente) può
istanziare altra roba passandola con DI. Per esempio può create uno
UserRepository passandogli il DB e il logger e prendere i dati, poi
creare un RichMailer passandogli il mailer, il logger e il template
engine, e infine creare un HtmlRenderer passandogli il template engine,
il template in HTML e i dati tornati dallo UserRepository per mostrare
il risultato all'utente.
 
Insomma: non è che uno esclude l'altro. :)
 
Bye.


 
La fine:
 
"Hope this article cleared some things up for you and you enjoyed reading it.
If not, then you must be already smartest programer out there"
Well maybe not, Igor "software developer since 2010" you can just ... .. .
'nuf said.
Ha solo scritto in breve come si usano.
 
Alessandro in un post ha scritto meglio e più chiaramente quello di cui
voleva parlare.
 
Quindi niente, se conosci Igor di persona puoi dirgli che, visto che ho un
ad-blocker, oltre la figura meschia, nemmno si prenderà il centesimo del
mio click.
 
Ah, ma prima o poi tutti 'sti poveracci spariranno, mica che no.
 
 
Ciao!

 quando istanziano un oggetto.
 
Per non violare il principio di DI, gli istanziamenti non andrebbero 
fatti solo in uno dei file principali (index.php, Controller.php, ecc.)?

 fatti solo in uno dei file principali (index.php, Controller.php, ecc.)?
 
Gli istanziamenti li fai o nel index.php (e poi li infili nel SL) o nel 
SL stesso. In questo modo hai tutto nello stesso posto. Nei controller 
chiedi al SL di darti l'istanza, e lui sa se deve dartene una già fatta 
o deve crearne una al volo.
 
Questo è uno dei modi. Non è detto che sia il migliore. Spero non sia il 
peggiore, visto che è quello che uso di solito. :D
 
Bye.

 SL stesso.
 
Oppure, per tenere ben organizzato il codice, tramite una factory 
(ServiceLocationFactory)

 (ServiceLocationFactory)
 
Una Factory dovrebbe servire a creare oggetti che implementano la stessa 
interfaccia.
 
Quindi una ServiceLocationFactory creerebbe ServiceLocators, non i 
servizi stessi.
 
Puoi avere diverse factory, una per servizio storato nel SL, ma secondo 
me è un overkill inutile.
 
Bye.

 servizi stessi.
 
Addirittura creare più ServiceLocators :-|
Si perderebbe la centralità :D

 SL stesso. In questo modo hai tutto nello stesso posto.
 
Appunto.
 
 > Nella mia esperienza conviene sempre tenere la struttura più piatta
 > possibile, ma siccome non è sempre facile, io preferisco avere un SL >che
 > passo ai livelli più alti, i quali poi usano DI prendendo la roba dal >SL
 > quando istanziano un oggetto.
 
In profondità suppongo.
Prima dici che vuoi instanziare in profondità, poi in superfice (SL), 
poi di nuovo in profondità, boh :D


Il SL è "parallelo", non in profondità.
 
Il SL viene creato da index.php (profondità 0), e passato ai controller 
(via router o via singleton)
 
I servizi vengono creati alla bisogna dal SL.
 
Siccome è il controller (profondità 1) che invoca i servizi dal SL, si 
istanziano quasi in superficie.
 
Poi il controller può passare i servizi più giù, se necessario, via DI. 
Per esempio passando il logger alla view (prof. 2) che lo passa al 
template engine (prof. 3), che magari usa un minificatore (prof. 4), che 
magari ha un DOM parser (prof. 5), ecc. ecc.
 
Bye.

 Ossia?
 
function loggerFactory($pippo: int): LoggerInterface {
   switch ($pippo) {
     case 0: return new StubLogger();
     case 1: return new ConsoleLogger();
     case 2: return new SuperLogger();
     default: throw new Exception("Unknown logger");
   }
}
 
Come vedi, avere un ServiceLocatorFactory non ha senso.
 
Bye.


E questa cosa cos'è?
Poi i logger, gli gnomi, i folletti?
 
Senti, per come la intendo io, guarda qua
 
class ServiceLocatorFactory {
  function crate(): ServiceLocator {
     $a = ...;
     $b = ...;
     $c = ...;
     return new ServiceLocator($a,$b,$c);
   }
  }
}

 Poi il controller può passare i servizi più giù, se necessario, via DI. 
 
In che modo?
La DI dovrebbe essere fatta (tramite costruttore o setter) solo in 
superfice dall'index.php o dall'SL.


 }
 
Altro esempio
 
class ServiceLocatorFactory {
  function crate(): ServiceLocator {
     $x = new ServiceLocator();
 
     $x->setServiceA(new A);
     $x->setServiceB(new B);
     $x->setServiceC(new C);
 
     return $x;
   }
  }
}

 superfice dall'index.php o dall'SL.
 
O dall'SLF (ServiceFactoryLocator), vedi l'altro post.
 
     $x = new ServiceLocator();// te lo ricordi?
 
     // iniezione di A, B, C (vedi l'altro post)
 
     $tester = new Tester;
     // iniettiamo un debbugger nel tester (ecco la DI di cui parlavo)
     $tester->setDebugger(new Debugger);
     $x->setServiceTest($tester);
 
     return $x;// esattamente come nell'altro post

 }
 
Questa non e` una Factory. Al massimo e` un Builder, ma nemmeno quello.
 
Una Factory (fabbrica, in italiano) produce piu` di un oggetto.
 
Se devi costruirne solo uno, tanto vale farlo nel costruttore del
ServiceLocator (o nell'istanziatore, se e` un singleton).
 
Bye.
 

      return $x;// esattamente come nell'altro post
 
Stai usando la DI per iniettare dipendenze nel Service Locator.
 
Per definizione il SL dovrebbe essere quello che gestisce TUTTE le
dipendenze.
 
Lo stai facendo al contrario.
 
Bye.

 Questa non e` una Factory. Al massimo e` un Builder, ma nemmeno quello.
 
Differenza tra factory e builder?
 


Un factory costruisce un oggetto al volo e lo ritorna, solitamente è giusto
un wrapper per un costruttore.
Un builder restituisce un oggetto non costruibile in un singolo step, e.g.
quando solo il costruttore non da un oggetto utilizzabile.
 
Risposta a parte, non capisco perché parti per la tangente quando qualcuno
ti cerca di spiegare in dettaglio qualcosa che chiedi. Per questo thread,
ad esempio, questa era l'ultima domanda che sarebbe venuta in mente a
qualsiasi altra persona. Così, anche se magari non è vero, sembra che
scrivi per scrivere, e non leggi con attenzione chi s'impegna regalandoti
il suo tempo. M2C.
 
 
Ciao!

 quando solo il costruttore non da un oggetto utilizzabile.
 
Ad esempio?

 quando solo il costruttore non da un oggetto utilizzabile.
 
In pratica:
 
$pizza = new Pizza($farina_adatto, $lievito_adatto);
$pizza->metti($olive_scelte_dal_cliente1);
$pizza->metti($olio_scelto_dal_cliente1);
return $pizza;
 
$pizza = new Pizza($farina_adatto, $lievito_adatto);
$pizza->metti($olive_scelte_dal_cliente2);
$pizza->metti($olio_scelto_dal_cliente2);
return $pizza;
 
????

 Ad esempio?
 
Ad esempio
 
$controller = ControllerBuilder::create()
  ->withRequest($req)
  ->withOutput(new HtmlRenderer())
  ->withDb($contentDb)
  ->build();
 
Il controller non può funzionare senza una Request, senza una
connessione al DB e senza sapere dove mandare l'output.
 
Se, per qualsiasi motivo, non hai tutte le informazioni disponibili
quando lo crei, puoi creare un builder e passarlo alle varie fasi della
pipeline, che aggiungono i pezzi che mancano, e alla fine chiami
`build()` che costruisce il Controller vero e proprio, da usare nel Router.
 
Se chiami `build` senza aver settato tutte le parti richieste, torna un
errore e non ti restituisce il controller. Se non chiami `build` alla
fine, hai un ControllerBuilder e non un Controller, quindi non hai
l'interfaccia adatta al Router.
 
È praticamente Dependency Injection fatta a passi successivi, invece che
passare tutto nel costruttore dell'oggetto.
 
Sono tecniche che si usano spesso in linguaggi strictly typed, e in PHP
perdono un po' di senso, ma sono comunque utili per catturare errori in
certe circostanze.
 
Bye.

 errore e non ti restituisce il controller
 
Quindi naturalmente all'interno di build() bisogna prima di tutto 
verificare che tutto sia stato settato a dovere?


Normalmente la `build()` chiama semplicemente il costruttore
dell'oggetto che sta generando, passandogli tutte le dipendenze.
 
A quel punto è il costruttore stesso che deve verificare che le
dipendenze siano valide (tramite strict-typing e `if !is_null(...)`, se
serve).
 
In questo modo hai i controlli in un posto solo (il costruttore
dell'oggetto finito) e non importa come lo crei, avrai comunque gli errori.
 
I Builder solitamente sono abbastanza "stupidi" come classi.
 
Bye.
 
 

 dell'oggetto che sta generando, passandogli tutte le dipendenze.
 
Appunto, quindi il metodo create() cosa fa?


Te lo spiego con un esempio:
 
class Controller {
  private $request;
  private $output;
  private $logger;
 
  public function __construct($req, $out, $logger) {
    if (is_null($req) || is_null($out) || is_null($logger)) {
      throw new Exception('Missing dependencies');
    }
    $this->request = $req;
    $this->output = $out;
    $this->logger = $logger;
  }
}
 
class ControllerBuilder {
  private $request;
  private $output;
  private $logger;
 
  public static function create() {
    $builder = new ControllerBuilder();
    $builder->logger = new ConsoleLogger();
  }
 
  public function withRequest($req) {
    $this->request = $req;
    return $this;
  }
 
  public function withoutput($out) {
    $this->output = $out;
    return $this;
  }
 
  public function withLogger($logger) {
    $this->logger = $logger;
    return $this;
  }
 
  public function build() {
    return new Controller($this->request, $this->output, $this->logger);
  }
}
 
Come dicevo: assolutamente stupido. Il suo unico scopo è di permetterti
di costruire un oggetto a step, ed eventualmente di definire dei valori
di default.
 
Bye.

    }
 
??????????????????????????????????????????
bohhhhhhhhhh :D
 
$builder a chi ed a che serve????


Complimenti per aver trovato un bug in 70 righe di codice che ho scritto
di getto per aiutarti.
 
Buon Natale anche a te.
 
 

 Buon Natale anche a te.
 
Direi che una pausa natalizia ci serve proprio.
Buon Natale anche a te!!!

ma con che faccia e che coraggio...
altro che buon natale :(

 
 
C'è qualche problema?
Non capisco?