I primitivi, come stringo int, non hanno alcun significato in un dominio aziendale. Una conseguenza diretta di ciò è che si può erroneamente utilizzare un URL quando è previsto un ID prodotto o utilizzare la quantità quando si prevede il prezzo .
Questo è anche il motivo per cui la sfida di Object Calisthenics include le primitive che si avvolgono come una delle sue regole:
Regola 3: avvolgi tutti i primitivi e le stringhe
Nel linguaggio Java, int è un oggetto primitivo, non reale, quindi obbedisce a regole diverse rispetto agli oggetti. Viene utilizzato con una sintassi non orientata agli oggetti. Ancora più importante, un int da solo è solo uno scalare, quindi non ha alcun significato. Quando un metodo accetta un int come parametro, il nome del metodo deve fare tutto il lavoro per esprimere l'intento. Se lo stesso metodo accetta un'ora come parametro, è molto più facile vedere cosa sta succedendo.
Lo stesso documento spiega che esiste un ulteriore vantaggio:
Piccoli oggetti come Hour o Money ci danno anche un posto ovvio per mettere comportamenti che altrimenti sarebbero stati disseminati attorno ad altre classi .
Infatti, quando si usano le primitive, di solito è estremamente difficile tenere traccia della posizione esatta del codice correlato a quei tipi, spesso portando a una duplicazione del codice grave . Se esiste una Price: Moneyclasse, è naturale trovare il controllo della gamma all'interno. Se, invece, viene utilizzato un int(peggio, a double) per memorizzare i prezzi dei prodotti, chi dovrebbe validare l'intervallo? Il prodotto? Lo sconto? Il carrello?
Infine, un terzo vantaggio non menzionato nel documento è la possibilità di cambiare relativamente facilmente il tipo sottostante. Se oggi il mio ProductIdha shortcome tipo sottostante e successivamente devo usare intinvece, è probabile che il codice da modificare non si estenda all'intera base di codice.
Lo svantaggio - e lo stesso argomento si applica a ogni regola dell'esercizio di Calisthenics dell'oggetto - è che se diventa rapidamente troppo travolgente per creare una classe per tutto . Se Productcontiene ProductPricequali eredita da PositivePricecui eredita da Pricecui a sua volta eredita Money, questa non è un'architettura pulita, ma piuttosto un disordine completo in cui per trovare una singola cosa, un manutentore dovrebbe aprire qualche dozzina di file ogni volta.
Un altro punto da considerare è il costo (in termini di righe di codice) della creazione di classi aggiuntive. Se i wrapper sono immutabili (come dovrebbero essere, di solito), significa che, se prendiamo C #, devi avere almeno all'interno del wrapper:
- Il getter di proprietà,
- Il suo campo di appoggio,
- Un costruttore che assegna il valore al campo di supporto,
- Un'usanza
ToString(),
- Commenti sulla documentazione XML (che compongono molte righe),
- A
Equalse a GetHashCodeoverride (anche un sacco di LOC).
ed eventualmente, quando pertinente:
- Un attributo DebuggerDisplay ,
- Una sostituzione di
==e !=operatori,
- Alla fine un sovraccarico dell'operatore di conversione implicita per convertire senza problemi da e verso il tipo incapsulato,
- Contratti di codice (incluso l'invariante, che è un metodo piuttosto lungo, con i suoi tre attributi),
- Diversi convertitori che verranno utilizzati durante la serializzazione XML, la serializzazione JSON o la memorizzazione / caricamento di un valore in / da un database.
Un centinaio di LOC per un semplice wrapper lo rende abbastanza proibitivo, motivo per cui potresti essere completamente sicuro della redditività a lungo termine di tale wrapper. La nozione di portata spiegata da Thomas Junk è particolarmente rilevante qui. Scrivere un centinaio di LOC per rappresentare un ProductIdusato in tutta la tua base di codice sembra abbastanza utile. Scrivere una classe di queste dimensioni per un pezzo di codice che rende tre righe in un singolo metodo è molto più discutibile.
Conclusione:
Avvolgi le primitive in classi che hanno un significato in un dominio aziendale dell'applicazione quando (1) aiuta a ridurre gli errori, (2) riduce il rischio di duplicazione del codice o (3) aiuta a cambiare il tipo sottostante in un secondo momento.
Non racchiudere automaticamente tutte le primitive che trovi nel tuo codice: ci sono molti casi in cui l'utilizzo stringo intva perfettamente bene.
In pratica, in public string CreateNewThing(), restituire un'istanza di ThingIdclasse invece che stringpotrebbe aiutare, ma puoi anche:
Restituisce un'istanza di Id<string>classe, ovvero un oggetto di tipo generico che indica che il tipo sottostante è una stringa. Hai il vantaggio della tua leggibilità, senza l'inconveniente di dover mantenere molti tipi.
Restituisce un'istanza di Thingclasse. Se l'utente ha solo bisogno dell'ID, questo può essere fatto facilmente con:
var thing = this.CreateNewThing();
var id = thing.Id;