Come programmare object-oriented con un framework DI? [duplicare]

4

La mia impressione è che i progetti che utilizzano un framework DI come Spring o Guice tendono a perdere il loro orientamento agli oggetti e degenerano in un progetto puramente procedurale:

  1. DI non solo centralizza la domanda su quale implementazione di un oggetto usare ma gestisce anche il ciclo di vita di questi oggetti. Molti schemi di progettazione OO , tuttavia, si basano sulla capacità della logica aziendale di collegare oggetti. Non saprei, ad esempio, come implementare un modello di strategia in primavera perché la decisione quale delle strategie concrete da utilizzare è determinata staticamente dalla configurazione dell'applicazione anziché da un pezzo di codice. Lo stesso vale per decoratore, composito, osservatore, ...

  2. Il motivo sopra riportato porta a un design in cui funzionalità e dati sono separati. Poiché non si desidera che il contenitore DI decida quando vengono creati i dati, è stato diviso qualsiasi codice dai dati in modo che solo questa parte fosse gestita dal contenitore DI. Ciò è contrario all'idea di OO in cui codice e dati dovrebbero risiedere insieme in un'unica unità. Questo interrompe l'incapsulamento dei dati perché tutti i campi dei "bean" di dati sono esposti da getter e setter pubblici.

  3. Non puoi più utilizzare il polimorfismo perché il codice non è più collegato ai suoi dati e le "funzioni virtuali" non possono funzionare. Questo porta a quelle istanze di cascate che tutti sappiamo che non dovremmo usare. Inoltre, perdiamo tutti quei progetti che si basano sul polimorfismo.

  4. Il contenitore DI inserisce i bean gestiti solo negli oggetti che sono anche gestiti da esso. Pertanto, non puoi combinare bean gestiti e oggetti normali perché non possono esserci "vuoti non gestiti" nella catena di riferimento dei bean gestiti. Quindi, una volta iniziato con DI, è necessario mettere tutti gli altri codici sotto il controllo del contenitore DI e non è più possibile utilizzare gli oggetti normali. Suppongo che questo sia il motivo per cui la separazione di codice e dati viene eseguita in modo rigoroso nei progetti DI.

Vedo che ci sono dei motivi per cui usare DI ma è davvero importante rinunciare così tanto? Le persone non si preoccupano di OO? Oltre a questo articolo , Non riesco a trovare alcuna discussione su questo argomento.

O sono solo io che non capisco come farlo correttamente? Qualche idea su come affrontare i quattro punti sopra?

Appendice 1: Spiegazione del modello di strategia

Mi riferisco all'esempio qui . Supponiamo che esista una versione DI in cui tutte le implementazioni di Strategy e Context vengono iniettate a StrategyExample . Quindi StrategyExample non decide più quale Strategy iniettare in Context , ma la configurazione avrebbe già deciso quale implementazione iniettare in Context . Quindi sì, DI applica pesantemente il modello di strategia. Ma lo fa sempre in modo statico.

    
posta Wolfgang 19.01.2013 - 17:01
fonte

2 risposte

0

DI not only centralizes the question of which implementation of an object to use but also manages the life-cycle of these objects. Many OO design patterns, however, rely on the ability of business logic to wire objects. I wouldn't know, for example, how to implement a Strategy Pattern in Spring because the decision which of the concrete strategies to use, is statically determined by the configuration of the application instead of a piece of code. Same goes for decorator, composite, observer, ...

Questo semplicemente non è vero. Penso che la strategia sia un ottimo esempio per uno schema che effettivamente benefici di DI ed è di per sé (con o senza contenitore) il prototipo di DI. Puoi esportare tutte le strategie che vuoi e scegliere quella che vuoi nella tua logica aziendale. Inoltre, nessuno, non ripeto nulla e nessuno ti impedisce di istanziare una strategia conosciuta da solo per scavalcare la strategia che è stata iniettata.

The reason above leads to a design where functionality and data are separated. Because you don't want to let the DI container decide when data is created, you split any code off the data in order to have only this part managed by the DI container. This is contrary to the idea of OO where code and data should reside together in one unit. This breaks the encapsulation of data because all fields of the data "beans" are exposed by public getters and setters.

Vedo il tuo punto, ma puoi facilmente risolverlo usando un modello di fabbrica. Nel modo più semplice, puoi istanziare il tuo oggetto da solo e poi farlo costruire e cablare dalla fabbrica (chiamando e nascondendo l'implementazione del contenitore).

You can't use polymorphism anymore because code is not linked to its data anymore and the "virtual functions" can't work. This leads to those instanceof cascades that we all know we shouldn't use. Also, we lose all those designs that rely on polymorphism.

Non vedo assolutamente il tuo punto qui. L'iniezione di dipendenza eseguita correttamente è un ottimo esempio del potere del polimorfismo da solo.

The DI container will inject managed beans only in objects which are also managed by it. So you can't mix managed beans and normal objects because there can't be any "non-managed gaps" in the reference chain of managed beans. So once you started with DI, you need to put all other code under the control of the DI container and you can't use normal objects anymore. I suppose that this is the reason why the separation of code and data is done so rigorously in DI projects.

Sì, ma questo può essere facilmente risolto con un approccio di fabbrica. Puoi totalmente creare software che funziona con o senza un contenitore DI utilizzando una semplice fabbrica.

    
risposta data 19.01.2013 - 17:56
fonte
0

Non credo che DI tiri fuori i principi di OO dalla finestra; in alcuni modi, DI dipende da loro.

Manterrò questa risposta generica, dal momento che la maggior parte dei framework DI si prende molto in prestito l'una dall'altra. I concetti sono simili, sebbene alcuni framework abbiano caratteristiche che gli altri non hanno. Presumo, tuttavia, che il framework accetti la configurazione esterna che viene letta in fase di esecuzione (come XML o file di proprietà), possibilmente oltre alla configurazione in fase di compilazione (come gli attributi).

In un mondo di DI, diventi più dipendente dalle interfacce e dalla composizione. Ad esempio, supponiamo che la tua applicazione debba ottenere il prezzo di un prodotto e che il prezzo possa provenire da un file CSV, da un database o da un servizio REST. Puoi definire l'interfaccia come qualcosa di simile

public interface PricingService {
  public int getPrice(String productId);
}

con implementazioni

public class FilePricingService implements PricingService {
  public void setParser(Parser parser) { ... } // and getter
  public void setPriceFile(File file) { ... }  // and getter

  private Map<String, Integer> prices;
  public void init() {
    getParser().parse(getPriceFile());
     prices = ...;
  }

  public int getPrice(String productId) {
    return prices.get(productId);
  }
}

Un sistema DI discreto sarebbe in grado di introspettare i tipi di proprietà di questo oggetto e chiamare i setter con gli oggetti appropriati. Quando viene prima referenziato un oggetto, il framework ricerca tutti gli oggetti dipendenti e chiama il metodo init () dell'oggetto se ne ha uno. Ad esempio, una configurazione basata su file di proprietà (solo per semplificare l'esempio) può essere simile a:

# Application.properties
class=...
pricingService=CSVPricingService

# CSVPricingService.properties
class=FilePricingService
parser=CSVParser # defined elsewhere
priceFile=/path/to/prices.csv

# XMLPricingService.properties
class=FilePricingService
parser=XMLParser # defined elsewhere
priceFile=/path/to/prices.xml

Quindi in fase di runtime, la configurazione può essere cambiata senza dover ricompilare nulla. Il framework sa che FilePricingService si aspetta che priceFile t sia un file, quindi invoca i propri convertitori per creare automaticamente un file dal nome file che è stato fornito. Sa che Parser non è un tipo semplice, quindi presuppone che si tratti di un riferimento a un altro componente gestito da DI e cerca quel componente. Ciò è particolarmente utile se non si ha la fonte per i componenti che si desidera utilizzare. Estendi l'esempio:

public class RESTPricingService implements PricingService {
  public void setEndpointURL(URL endpoint) { ... }

  public int getPrice(String productId) { ... }
}

ecc. L'idea è che ovunque qualcosa abbia bisogno di un PricingService, non deve preoccuparsi di quale sia la classe di implementazione. Supponiamo che tu abbia ricevuto una richiesta che ti dice "scegli il prezzo più basso da due diversi file CSV e il servizio REST". L'unico codice aggiuntivo che dovresti scrivere è una classe di composizione che utilizza questi altri servizi di tariffazione esistenti:

public class LowestPricingService implements PricingService {
  public void setPricingServices(List<PricingService> services) { ... }

  public int price(String productId) {
    int lowestPrice = Integer.MAX_VALUE;
    for(PricingService s : getPricingServices()) {
      int p = s.getPrice(productId);
      if(p < lowestPrice) {
        lowestPrice = p;
      }
    }
  }
}

E puoi creare un file di configurazione simile a

# FileOnePricingService.properties
class=FilePricingService
parser=CSVParser
file=/path/to/file/one.csv

# FileTwoPricingService.properties
class=FilePricingService
parser=CSVParser
file=/path/to/file/two.csv

# RESTPricingService.properties
class=RESTPricingService
endpointURL=https://example.com/price

# FilesAndRESTPricingService.properties
class=LowestPricingService
pricingServices=FileOnePricingService,FileTwoPricingService,RESTPricingService

e dire all'applicazione di utilizzare quel FilesAndRESTPricingService nella sua configurazione.

Questo sistema dipende da molti principi OO. Polimorfismo / ereditarietà, incapsulamento, ecc. Sono tutti qui in uso. Java rende tutto ciò molto più prolisso, con i getter e gli incastonatori di piatti caldi ovunque, ma questa è una mancanza della lingua, non del modello DI. (Immagina un Java con macro che ti permetta di dire {property File the File;} che creerebbe il codice getter / setter).

Il vantaggio di utilizzare un sistema DI come questo è che il framework gestisce molte configurazioni per te. Un buon sistema DI fornisce anche un modo per ispezionare la configurazione di runtime degli oggetti tramite alcune UI. In fase di esecuzione, se è necessario modificare l'elenco di PricingServices, ad esempio, l'interfaccia utente del framework dovrebbe essere in grado di mostrare tutte le implementazioni registrate e consentire di modificare le loro dipendenze al volo.

Sistemi come questo esistono ed esistono da oltre un decennio nelle impostazioni di produzione reali.

    
risposta data 19.01.2013 - 18:21
fonte

Leggi altre domande sui tag