Modelli di dominio ricco, incapsulamento e il problema della sovraiezione del costruttore

1

Sto rifattorizzando un'applicazione web di e-commerce, attualmente lavorando sulla classe UserBasket , che dovrà gestire l'aggiunta o la rimozione di articoli, la modifica della loro quantità, l'ammontare totale, il numero totale di articoli ecc. Durante la visualizzazione sono necessari anche il cestino, il nome dell'elemento, la descrizione e il nome-immagine principale.

Il UserBasket deve quindi conoscere il prezzo del Item per la quantità specificata. I diversi metodi di calcolo del prezzo corrente applicabile dovrebbero essere scambiabili. Potrei utilizzare il modello Strategy in una sorta di ItemPriceGetter con cui verrà costruito UserBasket , lasciando l'elemento inconsapevole di questo o costruendo il Item stesso con un PriceFindingStrategy .

Ho delle riserve contro un modello di dominio completamente anemico. Un articolo dovrebbe essere in grado di dirmi il suo prezzo per un determinato cliente e quantità in una certa data - dovrebbe essere in grado di dire il nome del file della sua immagine principale, la sua disponibilità ecc.

Essenzialmente, nel database, gli elementi (e altre entità di dominio) hanno più relazioni 1-to-n con logica a volte complessa e variabile per trovare quella attualmente applicabile tra loro (per prezzo, immagine, descrizione, ecc.).

Se non voglio un modello di dominio anemico, ma ci saranno diverse strategie per ottenere le informazioni "giuste", come posso evitare l'over-injection del costruttore quando ci sono più relazioni 1-to-n da un articolo in cui è necessaria una strategia per scegliere quella giusta.

Un Item -Class è lì per informarti sui dati relativi agli articoli ed eseguire la logica di business che è centrata su di loro - così da renderti capace di dirti il prezzo per un determinato cliente e quantità, la sua immagine principale -filename o alcuni di essi non sembrano una violazione del Principio di Responsabilità Unica.

Con un modello di dominio anemico, avrei la logica divisa e incapsulata in modo preciso, ma ora, per esempio, una lista articoli per scoprire disponibilità, prezzo, nome e immagine di un oggetto, non avrebbe bisogno solo di un oggetto, ma anche un ItemPriceFinder , ItemAvailabilityFinder , ItemImageFinder (o alcuni di questi) - la coesione (apparentemente) soffrirebbe e le cose che appartengono all'oggetto non sono più incapsulate lì.

Con un modello di dominio ricco, l'elemento stesso dovrà avere un ItemPriceStrategy , ItemAvailabilityStrategy , ItemImageStrategy o simili. Quindi sembra che mi sia bloccato o iniettare molte dipendenze o perdere coerenza e rendere anemico il mio modello di dominio ... e in quest'ultimo caso, avrei ancora delle classi (che per esempio hanno bisogno di molte informazioni sugli Articoli) che ho dovrebbe costruire con l'oggetto dominio stesso e la maggior parte dei servizi per ottenere dati correlati.

Qual è la migliore pratica per affrontare una situazione del genere? Nello specifico: come dovrebbero essere incapsulati i diversi modi di ottenere e filtrare le informazioni correlate per un tipo di entità aziendale basata su parametri contestuali e come dovrebbe essere integrato in modo da massimizzare la coerenza ed evitare di violare il principio di responsabilità singola?

    
posta Michael Bauer 23.03.2015 - 16:01
fonte

2 risposte

8

Vorrei andare con il modello di dominio anemico qui. Considera l'esempio del tuo ItemImageFinder . Prendi in considerazione alcuni scenari:

  1. Decidi di estendere il tuo sistema per includere anche Image miniature, o includendo immagini alternative.
    • Ora devi modificare il tuo comportamento Item . Chiaramente, questo non è l'unico motivo per cui Item deve essere modificato, quindi stai violando SRP.
    • Devi creare un metodo di Behemoth o più metodi diversi per il comportamento delle immagini.
    • Con un ItemImageFinder , tutte queste modifiche sarebbero in una classe relativa a Images , non all'intero articolo.
  2. Devi creare uno strumento da riga di comando.
    • Ora i tuoi articoli devono caricare tutti questi dati immagine, che non è nemmeno in grado di eseguire il rendering.
  3. Decidi di avere più implementazioni dell'interfaccia Item .
    • Forse una delle implementazioni non ha bisogno di cercare articoli. Ora hai tutto questo comportamento di cui non hai bisogno.

Inoltre, non sono sicuro del motivo per cui vuoi che la classe ImageFinder sia ItemImageFinder . Potrebbe essere solo ImageFinder , con un metodo getImage(Item) . Potrebbe quindi essere esteso per avere un metodo getThumbnail(Item) o un metodo getImage(SomeOtherObject) .

Hai detto tu stesso "la coesione (apparentemente) soffrirebbe". Innanzitutto, non sono d'accordo che lo faccia. Ma dimentica per un secondo la "saggezza accettata": anche se ciò accadesse, quali sarebbero esattamente gli inconvenienti di una simile rottura della coesione? Non riesco a pensare a nessuno.

    
risposta data 23.03.2015 - 16:27
fonte
3

An Item-Class is there to tell you about data relating to items, and perform business logic that is centered around them - so making it able to tell you its price for a given customer and quantity, its main-image-filename or some such doesn't seem like a violation of the Single Responsibility Principle.

Sì, si tratta di una violazione dell'SRP. Va bene se l'articolo conosce il suo nome-immagine-principale e il suo prezzo base, ma non è corretto se l'articolo ha conoscenza dei clienti in modo che possa dirti il prezzo finale per un determinato cliente.

Come regola generale, ("un principio con un'ampia applicazione che non intende essere rigorosamente accurato o affidabile per ogni situazione"), la stessa classe di articolo dovrebbe essere valida in un'applicazione di produzione invece di un'applicazione di vendita, dove non ci sarebbe alcuna idea di un cliente. Tale classe potrebbe forse contenere alcune informazioni aggiuntive che non sarebbero necessarie in un'altra applicazione (come i materiali necessari per la produzione o il tempo di produzione), ma almeno non fallirebbe con un errore class not found: "Customer" .

Per pensarlo da un altro punto di vista, perché la classe oggetto dovrebbe essere in grado di dirti quanto costerebbe per un determinato cliente, invece del contrario: una classe cliente è in grado di dirti quanto un certo articolo sarebbe costato per questo. Ogni volta che ti imbatti in dilemmi come questo, sai che nessuna delle due classi è il posto giusto per la logica.

Come altra regola empirica, le classi dovrebbero contenere solo la logica che tratta le classi che sono immediatamente correlate. Quindi, la classe cliente avrà presumibilmente ordini, quindi ovviamente va bene se la classe cliente contiene una logica che può dirti, ad esempio, qual è la somma totale di tutti gli ordini mai piazzati. Questo rende la classe cliente non-anemica. Ma la classe articolo probabilmente non avrebbe relazioni immediate (in entrambe le direzioni) con la classe cliente, quindi questo probabilmente significa che non ci dovrebbe essere logica in nessuna delle due classi che coinvolge l'altra. Se questo significa che l'oggetto dovrà essere anemico, così sia.

Nella mia esperienza, quando hai un pezzo di logica che vuoi mettere da qualche parte, ma non sembra esserci un'entità modello che sia l'entità giusta per contenere questa logica, è meglio spostare questa logica in " modello "oggetto, che per definizione conosce tutte le entità e le relazioni tra di loro. Questo tende a determinare una classe di modello principale abbastanza grassa, che è l'opposto dell'anemico, se questa è una qualsiasi consolazione.

    
risposta data 23.03.2015 - 16:47
fonte