Meu uso do operador de transmissão explícita é razoável ou um hack ruim?

24

Eu tenho um grande objeto:

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

e um objeto especializado, semelhante ao DTO:

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

Pessoalmente, acho um conceito de conversão explícita do BigObject no SmallObject - sabendo que é uma operação unidirecional e com perda de dados - muito intuitiva e legível:

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

É implementado usando o operador explícito:

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

Agora, eu poderia criar uma SmallObjectFactoryclasse com o FromBigObject(BigObject big)método, que faria a mesma coisa, adicioná-la à injeção de dependência e chamá-la quando necessário ... mas para mim parece ainda mais complicado e desnecessário.

PS Não tenho certeza se isso é relevante, mas haverá OtherBigObjectque também poderá ser convertido em SmallObjectconfigurações diferentes EnumType.

Gerino
fonte
4
Por que não um construtor?
Edc65 5/05
2
Ou um método estático de fábrica?
Brian Gordon
Por que você precisaria de uma classe de fábrica ou injeção de dependência? Você fez uma falsa dicotomia lá.
User253751
11
@immibis - porque de alguma forma não pensei sobre o que @Telastyn propôs: .ToSmallObject()método (ou GetSmallObject()). Um lapso momentâneo da razão - eu sabia que algo está errado com o meu pensamento, então eu pedi a vocês :)
Gerino
3
Isso soa como um caso de uso perfeito para uma interface ISmallObject que é implementada apenas pelo BigObject como um meio de fornecer acesso a um conjunto limitado de seus extensos dados / comportamento. Especialmente quando combinado com a idéia de ToSmallObjectmétodo de @ Telastyn .
Marjan Venema

Respostas:

0

Nenhuma das outras respostas está certa na minha humilde opinião. Nesta pergunta de stackoverflow, a resposta mais votada argumenta que o código de mapeamento deve ser mantido fora do domínio. Para responder sua pergunta, não - o uso do operador de elenco não é ótimo. Aconselho a criação de um serviço de mapeamento entre o DTO e o objeto do domínio, ou você pode usar o automapper para isso.

Esben Skov Pedersen
fonte
Essa é uma ótima idéia. Eu já tenho o Automapper instalado, então será uma brisa. Único problema que tenho com isso: não deveria haver algum rastro de que BigObject e SmallObject estejam de alguma forma relacionados?
Gerino
11
Não, não vejo nenhuma vantagem ao unir BigObject e SmallObject mais além do serviço de mapeamento.
Esben Skov Pedersen
7
Sério? Um automapper é sua solução para problemas de design?
Telastyn
11
O BigObject é mapeável para SmallObject, eles não são realmente relacionados um ao outro no sentido clássico de OOP, e o código reflete isso (ambos os objetos existem no domínio, a capacidade de mapeamento é definida na configuração de mapeamento, juntamente com muitos outros). Ele remove código duvidoso (minha infeliz substituição do operador), deixa os modelos limpos (sem métodos), então sim, parece ser uma solução.
Gerino
2
@EsbenSkovPedersen Esta solução é como usar um trator para cavar um buraco para instalar sua caixa de correio. Felizmente, o OP queria desenterrar o quintal de qualquer maneira, então uma escavadeira funciona nesse caso. No entanto, eu não recomendaria esta solução em geral .
Neil
81

Isso não é ótimo. Eu trabalhei com código que fez esse truque inteligente e que levou à confusão. Afinal, você seria de esperar para ser capaz de simplesmente atribuir o BigObjectem uma SmallObjectvariável, se os objetos são suficientes relacionado para lançá-los. Porém, não funciona - você obtém erros do compilador se tentar, pois, no que diz respeito ao sistema de tipos, eles não são relacionados. Também é um pouco desagradável para o operador de fundição criar novos objetos.

Eu recomendaria um .ToSmallObject()método. É mais claro sobre o que realmente está acontecendo e sobre como detalhado.

Telastyn
fonte
18
Doh ... ToSmallObject () parece ser a escolha mais óbvia. Às vezes, o mais óbvio é o mais evasivo;)
Gerino 5/05
6
mildly distastefulé um eufemismo. É lamentável que o idioma permita que esse tipo de coisa pareça uma impressão tipográfica. Ninguém imaginaria que era uma transformação real de objeto, a menos que eles mesmos escrevessem. Em uma equipe de uma pessoa, tudo bem. Se você colabora com alguém, na melhor das hipóteses, é uma perda de tempo, porque você precisa parar e descobrir se é realmente um elenco ou se é uma daquelas transformações loucas.
Kent A.
3
@Telastyn Concordou que não é o cheiro de código mais flagrante. Mas a criação oculta de um novo objeto em operação, que a maioria dos programadores entende ser uma instrução para o compilador para tratar esse mesmo objeto como um tipo diferente, é desagradável para quem precisa trabalhar com seu código depois de você. :)
Kent A.
4
+1 para .ToSmallObject(). Dificilmente você deve substituir os operadores.
Ytoledano 5/05
6
@dorus - pelo menos no .NET, Getimplica retornar uma coisa existente. A menos que você substitua as operações no objeto pequeno, duas Getchamadas retornarão objetos desiguais, causando confusão / bugs / wtfs.
Telastyn
11

Embora eu possa entender por que você precisaria de um SmallObject, eu abordaria o problema de maneira diferente. Minha abordagem para esse tipo de problema é usar uma fachada . Seu único objetivo é encapsular BigObjecte disponibilizar apenas membros específicos. Dessa maneira, é uma nova interface na mesma instância, e não uma cópia. Claro que você também pode querer fazer uma cópia, mas eu recomendaria que você o fizesse através de um método criado para esse fim em combinação com a Fachada (por exemplo return new SmallObject(instance.Clone())).

O Facade tem uma série de outras vantagens, a saber: garantir que determinadas seções do seu programa só possam fazer uso dos membros disponibilizados através da sua fachada, garantindo efetivamente que ele não possa fazer uso do que não deveria saber. Além disso, ele também tem a enorme vantagem de ter mais flexibilidade nas alterações de BigObjectmanutenção futura sem precisar se preocupar muito com a forma como ela é usada em todo o programa. Contanto que você possa emular o antigo comportamento de uma forma ou de outra, poderá fazer o SmallObjecttrabalho da mesma maneira que antes, sem precisar alterar seu programa em todos BigObjectos lugares que seriam usados.

Note, isso significa BigObjectque não depende, SmallObjectmas o contrário (como deveria ser na minha humilde opinião).

Neil
fonte
A única vantagem que você mencionou que uma fachada tem sobre a cópia de campos para uma nova classe é evitar a cópia (o que provavelmente não é um problema, a menos que os objetos tenham uma quantidade absurda de campos). Por outro lado, tem a desvantagem de que você deve modificar a classe original toda vez que precisar converter para uma nova classe, diferente do método de conversão estática.
Doval 5/05
@Doval Suponho que esse é o ponto. Você não o converteria em uma nova classe. Você criaria outra fachada, se é isso que você precisa. As alterações feitas no BigObject precisam ser aplicadas apenas à classe Facade e não em todos os lugares em que são usadas.
Neil
Uma distinção interessante entre essa abordagem e a resposta de Telastyn é se a responsabilidade pela geração SmallObjectestá com SmallObjectou BigObject. Por padrão, essa abordagem obriga SmallObjecta evitar dependências de membros privados / protegidos de BigObject. Podemos dar um passo adiante e evitar dependências de membros privados / protegidos SmallObjectusando um ToSmallObjectmétodo de extensão.
5135 Brian
@ Brian Você corre o risco de bagunçar BigObjectdessa maneira. Se você quisesse fazer algo semelhante, recomendaria criar um ToAnotherObjectmétodo de extensão dentro BigObject? Essas não devem ser as preocupações de BigObjectuma vez que, presumivelmente, ela já é grande o suficiente. Também permite que você se separe BigObjectda criação de suas dependências, o que significa que você pode usar fábricas e similares. A outra abordagem une fortemente BigObjecte SmallObject. Isso pode ser bom nesse caso específico, mas não é uma prática recomendada na minha humilde opinião.
Neil
11
@ Neil Na verdade, Brian explicou errado, mas ele está certo - os métodos de extensão se livram do acoplamento. Ele não está mais BigObjectsendo acoplado SmallObject, é apenas um método estático em algum lugar que recebe um argumento BigObjecte retorna SmallObject. Os métodos de extensão são realmente apenas açúcar sintático para chamar métodos estáticos de uma maneira mais agradável. O método de extensão não é parte de BigObject, é um método estático completamente separado. Na verdade, é um bom uso de métodos de extensão e muito útil para conversões de DTO, em particular.
Luaan
6

Há uma convenção muito forte que se baseia em tipos de referência mutáveis ​​que preservam a identidade. Como o sistema geralmente não permite operadores de conversão definidos pelo usuário em situações em que um objeto do tipo de origem pode ser atribuído a uma referência do tipo de destino, existem apenas alguns casos em que as operações de conversão definidas pelo usuário seriam razoáveis ​​para referência mutável tipos.

Eu sugeriria como requisito que, dado x=(SomeType)foo;seguido algum tempo depois y=(SomeType)foo;, com ambos os elencos aplicados ao mesmo objeto, x.Equals(y)fosse sempre e para sempre verdadeiro, mesmo que o objeto em questão fosse modificado entre os dois elencos. Essa situação poderia ser aplicada se, por exemplo, um tivesse um par de objetos de tipos diferentes, cada um com uma referência imutável ao outro, e converter um objeto para o outro tipo retornaria sua instância emparelhada. Também poderia se aplicar a tipos que servem como invólucros para objetos mutáveis, desde que as identidades dos objetos que estão sendo quebrados sejam imutáveis, e dois invólucros do mesmo tipo se reportariam iguais se envoltassem a mesma coleção.

Seu exemplo específico usa classes mutáveis, mas não preserva nenhuma forma de identidade; como tal, eu sugeriria que não é um uso apropriado de um operador de fundição.

supercat
fonte
1

Pode estar tudo bem.

Um problema com o seu exemplo é que você usa esses nomes de exemplo. Considerar:

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

Agora, quando você tem uma boa idéia do significado de long e int , tanto o elenco implícito de intpara longquanto o elenco explícito de longpara intsão bastante compreensíveis. Também é compreensível como 3se torna 3e é apenas outra maneira de trabalhar 3. É compreensível como isso falhará int.MaxValue + 1em um contexto verificado. Mesmo como ele funcionará int.MaxValue + 1em um contexto desmarcado para resultar int.MinValuenão é a coisa mais difícil de entender.

Da mesma forma, quando você lança implicitamente para um tipo de base ou explicitamente para um tipo derivado, é compreensível para quem sabe como a herança funciona o que está acontecendo e qual será o resultado (ou como poderá falhar).

Agora, com BigObject e SmallObject , não tenho noção de como esse relacionamento funciona. Se o seu tipo real é tal que a relação de elenco é óbvia, o elenco pode realmente ser uma boa idéia, embora muitas vezes, talvez a grande maioria, se esse for o caso, isso deve refletir-se na hierarquia de classes e na classe. fundição normal baseada em herança será suficiente.

Jon Hanna
fonte
Na verdade, eles não são muito mais do que o fornecido em questão - mas, por exemplo, BigObjectpode descrever um Employee {Name, Vacation Days, Bank details, Access to different building floors etc.}e SmallObjectpode ser um MoneyTransferRecepient {Name, Bank details}. Há uma tradução direta de Employeepara MoneyTransferRecepiente não há motivo para enviar ao aplicativo bancário mais dados do que o necessário.
Gerino