Il mio utilizzo dell'operatore di casting esplicito è ragionevole o è un trucco dannoso?


24

Ho un grande oggetto:

class BigObject{
    public int Id {get;set;}
    public string FieldA {get;set;}
    // ...
    public string FieldZ {get;set;}
}

e un oggetto specializzato, simile a DTO:

class SmallObject{
    public int Id {get;set;}
    public EnumType Type {get;set;}
    public string FieldC {get;set;}
    public string FieldN {get;set;}
}

Personalmente trovo un concetto di trasmissione esplicita di BigObject in SmallObject - sapendo che si tratta di un'operazione a senso unico, che perde dati - molto intuitiva e leggibile:

var small = (SmallObject) bigOne;
passSmallObjectToSomeone(small);

È implementato usando l'operatore esplicito:

public static explicit operator SmallObject(BigObject big){
    return new SmallObject{
        Id = big.Id,
        FieldC = big.FieldC,
        FieldN = big.FieldN,
        EnumType = MyEnum.BigObjectSpecific
    };
}

Ora, potrei creare una SmallObjectFactoryclasse con FromBigObject(BigObject big)metodo, che farebbe la stessa cosa, aggiungerla all'iniezione di dipendenza e chiamarla quando necessario ... ma a me sembra ancora più complicata e inutile.

PS Non sono sicuro che questo sia pertinente, ma ci OtherBigObjectsarà anche la possibilità di essere convertito in SmallObject, impostazione diversa EnumType.


4
Perché non un costruttore?
edc65,

2
O un metodo statico di fabbrica?
Brian Gordon,

Perché avresti bisogno di una classe di fabbrica o iniezione di dipendenza? Hai fatto una falsa dicotomia lì.
user253751

1
@immibis - perché in qualche modo non ho pensato a ciò che @Telastyn ha proposto: .ToSmallObject()metodo (o GetSmallObject()). Un momento momentaneo di ragione - sapevo che qualcosa non andava nel mio pensiero, quindi vi ho chiesto ragazzi :)
Gerino,

3
Sembra un caso d'uso perfetto per un'interfaccia ISmallObject che viene implementata da BigObject solo come mezzo per fornire accesso a un insieme limitato di dati / comportamenti estesi. Soprattutto se combinato con l'idea di @ Telastyn di un ToSmallObjectmetodo.
Marjan Venema,

Risposte:


0

Nessuna delle altre risposte ha ragione a mio modesto parere. In questa domanda stackover la risposta più votata sostiene che il codice di mappatura dovrebbe essere tenuto fuori dal dominio. Per rispondere alla tua domanda, no: il tuo utilizzo dell'operatore cast non è eccezionale. Ti consiglierei di creare un servizio di mappatura che si trova tra il tuo DTO e l'oggetto del tuo dominio, o potresti usare automapper per questo.


Questa è un'idea formidabile. Ho già installato Automapper, quindi sarà un gioco da ragazzi. L'unico problema che ho con esso: non dovrebbe esserci qualche traccia che BigObject e SmallObject siano in qualche modo correlati?
Gerino,

1
No, non vedo alcun vantaggio accoppiando BigObject e SmallObject insieme oltre al servizio di mappatura.
Esben Skov Pedersen,

7
Veramente? Un automapper è la tua soluzione per problemi di progettazione?
Telastyn,

1
Il BigObject è mappabile su SmallObject, non sono realmente correlati tra loro nel senso classico di OOP, e il codice riflette questo (entrambi gli oggetti esistono nel dominio, la capacità di mappatura è impostata nella configurazione della mappatura insieme a molti altri). Rimuove il codice discutibile (il mio sfortunato operatore override), lascia i modelli puliti (nessun metodo in essi), quindi sì, sembra essere una soluzione.
Gerino,

2
@EsbenSkovPedersen Questa soluzione è come usare un bulldozer per scavare un buco per installare la tua casella di posta. Fortunatamente l'OP voleva comunque scavare nel cortile, quindi un bulldozer lavora in questo caso. Tuttavia, non consiglierei questa soluzione in generale .
Neil

81

È ... Non eccezionale. Ho lavorato con il codice che ha fatto questo trucco intelligente e ha portato alla confusione. Dopotutto, ti aspetteresti di essere in grado di assegnare BigObjectin una SmallObjectvariabile solo se gli oggetti sono abbastanza correlati da lanciarli. Tuttavia, non funziona: si verificano errori del compilatore se si tenta dal momento che per quanto riguarda il sistema di tipi, non sono correlati. È anche leggermente sgradevole per l'operatore del casting creare nuovi oggetti.

Consiglierei .ToSmallObject()invece un metodo. È più chiaro su ciò che sta realmente accadendo e su come dettagliato.


18
Doh ... ToSmallObject () sembra la scelta più ovvia. A volte il più ovvio è il più sfuggente;)
Gerino

6
mildly distastefulè un eufemismo. È un peccato che la lingua permetta a questo genere di cose di apparire come un typecast. Nessuno immaginerebbe che si trattasse di una vera trasformazione dell'oggetto a meno che non lo scrivessero da soli. In una squadra di una persona, va bene. Se collabori con qualcuno, nel migliore dei casi è una perdita di tempo perché devi fermarti e capire se è davvero un cast, o se è una di quelle trasformazioni folli.
Kent A.

3
@Telastyn Concorda sul fatto che non è l'odore di codice più eclatante. Ma la creazione nascosta di un nuovo oggetto dall'operazione che la maggior parte dei programmatori ritiene essere un'istruzione per il compilatore per trattare lo stesso oggetto come un tipo diverso, è scortese con chiunque debba lavorare con il codice dopo di te. :)
Kent A.

4
+1 per .ToSmallObject(). Quasi mai dovresti scavalcare gli operatori.
ytoledano,

6
@doro - almeno in .NET, Getimplica la restituzione di una cosa esistente. A meno che non abbiate sovrascritto le operazioni sul piccolo oggetto, due Getchiamate restituirebbero oggetti disuguali, causando confusione / bug / wtfs.
Telastyn,

11

Mentre vedo perché dovresti averne uno SmallObject, affronterei il problema in modo diverso. Il mio approccio a questo tipo di problema consiste nell'utilizzare una facciata . Il suo unico scopo è incapsulare BigObjecte rendere disponibili solo membri specifici. In questo modo, si tratta di una nuova interfaccia sulla stessa istanza e non di una copia. Naturalmente potresti anche voler eseguire una copia, ma ti consiglio di farlo attraverso un metodo creato a tale scopo in combinazione con la facciata (ad esempio return new SmallObject(instance.Clone())).

La facciata presenta una serie di altri vantaggi, vale a dire garantire che determinate sezioni del programma possano fare uso solo dei membri resi disponibili attraverso la facciata, garantendo in modo efficace che non possa fare uso di ciò che non dovrebbe sapere. Oltre a questo, ha anche l'enorme vantaggio di avere una maggiore flessibilità nel cambiare la BigObjectmanutenzione futura senza doversi preoccupare troppo di come viene utilizzato durante il programma. Finché puoi emulare il vecchio comportamento in un modo o nell'altro, puoi fare il SmallObjectlavoro come prima senza dover cambiare il tuo programma ovunque BigObjectsarebbe stato usato.

Nota, questo significa BigObjectche non dipende, SmallObjectma piuttosto il contrario (come dovrebbe essere secondo la mia modesta opinione).


L'unico vantaggio che hai menzionato che una facciata ha rispetto alla copia dei campi in una nuova classe è quella di evitare la copia (il che probabilmente non è un problema a meno che gli oggetti non abbiano una quantità assurda di campi). D'altra parte ha lo svantaggio che è necessario modificare la classe originale ogni volta che è necessario convertire in una nuova classe, a differenza di un metodo di conversione statica.
Doval,

@Doval Suppongo che sia questo il punto. Non lo convertiresti in una nuova classe. Creeresti un'altra facciata se è quello che ti serve. Le modifiche apportate a BigObject devono essere applicate solo alla classe Facade e non ovunque vengano utilizzate.
Neil,

Una distinzione interessante tra questo approccio e la risposta di Telastyn è se la responsabilità di generare SmallObjectrisieda con SmallObjecto BigObject. Per impostazione predefinita, questo approccio obbliga SmallObjecta evitare dipendenze da membri privati ​​/ protetti di BigObject. Possiamo fare un passo ulteriore ed evitare dipendenze da membri privati ​​/ protetti SmallObjectutilizzando un ToSmallObjectmetodo di estensione.
Brian,

@Brian Rischi di ingombrare in BigObjectquel modo. Se volessi fare qualcosa di simile, vorresti creare un ToAnotherObjectmetodo di estensione all'interno BigObject? Questi non dovrebbero essere i problemi di BigObject, poiché, presumibilmente, è già abbastanza grande così com'è. Ti permette anche di separarti BigObjectdalla creazione delle sue dipendenze, il che significa che potresti usare fabbriche e simili. L'altro approccio fortemente coppie BigObjecte SmallObject. Questo può andare bene in questo caso particolare, ma non è la migliore pratica secondo la mia modesta opinione.
Neil,

1
@Neil In realtà, Brian spiegato male, ma lui è di destra - i metodi di estensione non sbarazzarsi del giunto. Non è più BigObjectaccoppiato a SmallObject, è solo un metodo statico da qualche parte che prende argomento BigObjecte restituisce SmallObject. I metodi di estensione sono in realtà solo zucchero sintattico per chiamare metodi statici in un modo migliore. Il metodo di estensione non fa parte di BigObject, è un metodo statico completamente separato. In realtà è un ottimo uso dei metodi di estensione e molto utile in particolare per le conversioni DTO.
Luaan,

6

Esiste una convenzione molto forte che lancia su tipi di riferimento mutabili che preservano l'identità. Poiché il sistema generalmente non consente agli operatori di casting definiti dall'utente in situazioni in cui un oggetto del tipo di origine potrebbe essere assegnato a un riferimento del tipo di destinazione, ci sono solo pochi casi in cui le operazioni di casting definite dall'utente sarebbero ragionevoli per riferimento mutabile tipi.

Vorrei suggerire come requisito che, dato x=(SomeType)foo;seguito qualche tempo dopo y=(SomeType)foo;, con entrambi i cast applicati allo stesso oggetto, x.Equals(y)dovrebbe essere sempre e per sempre vero, anche se l'oggetto in questione è stato modificato tra i due cast. Una situazione del genere potrebbe applicarsi se, ad esempio, uno avesse una coppia di oggetti di tipi diversi, ognuno dei quali conteneva un riferimento immutabile all'altro, e il lancio di uno o l'altro oggetto sull'altro tipo restituirebbe la sua istanza accoppiata. Potrebbe anche applicarsi con tipi che fungono da involucri per oggetti mutabili, a condizione che le identità degli oggetti da avvolgere fossero immutabili e che due involucri dello stesso tipo si dichiarassero uguali se avessero avvolto la stessa raccolta.

Il tuo esempio particolare usa classi mutabili, ma non conserva alcuna forma di identità; come tale, suggerirei che non è un uso appropriato di un operatore di casting.


1

Potrebbe andare bene.

Un problema con il tuo esempio è che usi questi nomi di esempio. Ritenere:

SomeMethod(long longNum)
{
  int num = (int)longNum;
  /* ... */

Ora, quando hai una buona idea di cosa significhi un lungo e un int , allora sia il cast implicito di intto longche il cast esplicito da longto intsono abbastanza comprensibili. È anche comprensibile come 3diventa 3ed è solo un altro modo di lavorare 3. È comprensibile come ciò fallirà int.MaxValue + 1in un contesto controllato. Anche il modo in cui funzionerà int.MaxValue + 1in un contesto non controllato per ottenere int.MinValuenon è la cosa più difficile da capire.

Allo stesso modo, quando si esegue il cast implicitamente su un tipo di base o esplicitamente su un tipo derivato, è comprensibile a chiunque sappia come l'ereditarietà funzioni ciò che sta accadendo e quale sarà il risultato (o come potrebbe non riuscire).

Ora, con BigObject e SmallObject non ho idea di come funzioni questa relazione. Se i tuoi tipi reali erano tali che la relazione sul casting fosse ovvia, allora il casting potrebbe davvero essere una buona idea, anche se il più delle volte, forse la stragrande maggioranza, se questo è il caso, allora dovrebbe riflettersi nella gerarchia di classi e nel sarà sufficiente la normale fusione basata sull'eredità.


In realtà non sono molto più di ciò che viene fornito in questione - ma per esempio BigObjectpotrebbe descrivere un Employee {Name, Vacation Days, Bank details, Access to different building floors etc.}e SmallObjectpotrebbe essere un MoneyTransferRecepient {Name, Bank details}. Esiste una traduzione diretta da Employeea MoneyTransferRecepient, e non vi è motivo di inviare all'applicazione bancaria più dati del necessario.
Gerino,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.