¿Es razonable mi uso del operador explícito de casting o es un mal truco?

24

Tengo un gran objeto:

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

y un objeto especializado tipo DTO:

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

Personalmente, encuentro un concepto de convertir explícitamente BigObject en SmallObject, sabiendo que es una operación unidireccional que pierde datos, muy intuitivo y legible:

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

Se implementa utilizando el 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
    };
}

Ahora, podría crear una SmallObjectFactoryclase con FromBigObject(BigObject big)método, que haría lo mismo, agregarlo a la inyección de dependencia y llamarlo cuando sea necesario ... pero para mí parece aún más complicado e innecesario.

PD: No estoy seguro de si esto es relevante, pero habrá OtherBigObjectque también se podrá convertir en una SmallObjectconfiguración diferente EnumType.

Gerino
fuente
44
¿Por qué no un constructor?
edc65
2
¿O un método estático de fábrica?
Brian Gordon,
¿Por qué necesitarías una clase de fábrica o inyección de dependencia? Has hecho una falsa dicotomía allí.
user253751
1
@immibis - porque de alguna manera no pensé en lo que @Telastyn propuso: .ToSmallObject()método (o GetSmallObject()). Un lapso momentáneo de la razón - Yo sabía que algo está mal con mi forma de pensar, por lo que te he pedido chicos :)
Gerino
3
Esto suena como un caso de uso perfecto para una interfaz ISmallObject que solo implementa BigObject como un medio para proporcionar acceso a un conjunto limitado de sus datos / comportamiento extensivo. Especialmente cuando se combina con la idea de @ Telastyn de un ToSmallObjectmétodo.
Marjan Venema

Respuestas:

0

Ninguna de las otras respuestas tiene razón en mi humilde opinión. En esta pregunta de stackoverflow, la respuesta más votada argumenta que el código de mapeo debe mantenerse fuera del dominio. Para responder a su pregunta, no, su uso del operador de transmisión no es excelente. Aconsejaría hacer un servicio de mapeo que se encuentre entre su DTO y su objeto de dominio, o podría usar automapper para eso.

Esben Skov Pedersen
fuente
Esta es una gran idea. Ya tengo Automapper en su lugar, por lo que será muy fácil. Único problema que tengo con él: ¿no debería haber algún rastro de que BigObject y SmallObject están de alguna manera relacionados?
Gerino
1
No, no veo ninguna ventaja al unir BigObject y SmallObject más allá del servicio de mapeo.
Esben Skov Pedersen
77
De Verdad? ¿Un automapper es su solución para problemas de diseño?
Telastyn
1
BigObject se puede asignar a SmallObject, no están realmente relacionados entre sí en el sentido clásico de OOP, y el código refleja esto (ambos objetos existen en el dominio, la capacidad de asignación se configura en la configuración de asignación junto con muchos otros). Elimina el código dudoso (mi desafortunada anulación del operador), deja los modelos limpios (sin métodos), así que sí, parece ser una solución.
Gerino
2
@EsbenSkovPedersen Esta solución es como usar una excavadora para cavar un hoyo para instalar su buzón. Afortunadamente, el OP quería desenterrar el patio de todos modos, por lo que una excavadora funciona en este caso. Sin embargo, no recomendaría esta solución en general .
Neil
81

Es ... No genial. He trabajado con código que hizo este truco inteligente y me llevó a la confusión. Después de todo, esperaría poder asignarlo BigObjecta una SmallObjectvariable si los objetos están lo suficientemente relacionados como para lanzarlos. Sin embargo, no funciona: obtienes errores del compilador si lo intentas, ya que, en lo que respecta al sistema de tipos, no están relacionados. También es un poco desagradable para el operador de fundición hacer nuevos objetos.

Recomendaría un .ToSmallObject()método en su lugar. Es más claro lo que realmente está sucediendo y lo que es detallado.

Telastyn
fuente
18
Doh ... ToSmallObject () parece ser la opción más obvia. A veces lo más obvio es lo más esquivo;)
Gerino
66
mildly distastefulEs un eufemismo. Es desafortunado que el lenguaje permita que este tipo de cosas se vean como una tipografía. Nadie adivinaría que fue una transformación de objeto real a menos que la hayan escrito ellos mismos. En un equipo de una persona, bien. Si colaboras con alguien, en el mejor de los casos es una pérdida de tiempo porque tienes que detenerte y descubrir si realmente es un elenco, o si es una de esas transformaciones locas.
Kent A.
3
@Telastyn estuvo de acuerdo en que no es el olor del código más atroz. Pero la creación oculta de un nuevo objeto en funcionamiento que la mayoría de los programadores entienden como una instrucción para el compilador para tratar ese mismo objeto como un tipo diferente, es desagradable para cualquiera que tenga que trabajar con su código después de usted. :)
Kent A.
44
+1 para .ToSmallObject(). Casi nunca debería anular a los operadores.
ytoledano
66
@dorus: al menos en .NET, Getimplica devolver una cosa existente. A menos que haya anulado las operaciones en el objeto pequeño, dos Getllamadas devolverían objetos desiguales, causando confusión / errores / wtfs.
Telastyn
11

Si bien puedo ver por qué necesitarías tener un SmallObject, abordaría el problema de manera diferente. Mi enfoque para este tipo de problema es usar una Fachada . Su único propósito es encapsular BigObjecty solo poner a disposición miembros específicos. De esta manera, es una nueva interfaz en la misma instancia, y no una copia. Por supuesto, es posible que también desee realizar una copia, pero le recomendaría que lo haga a través de un método creado para ese propósito en combinación con la Fachada (por ejemplo return new SmallObject(instance.Clone())).

Facade tiene una serie de otras ventajas, como asegurar que ciertas secciones de su programa solo puedan hacer uso de los miembros disponibles a través de su fachada, garantizando efectivamente que no puede hacer uso de lo que no debería saber. Además de esto, también tiene la enorme ventaja de que tiene más flexibilidad para cambiar BigObjecten el mantenimiento futuro sin tener que preocuparse demasiado por cómo se usa en todo su programa. Siempre que pueda emular el comportamiento anterior de una forma u otra, puede hacer que el SmallObjecttrabajo sea el mismo que antes sin tener que cambiar su programa en todas partes BigObject.

Tenga en cuenta que esto significa BigObjectque no depende SmallObjectsino al revés (como debería ser en mi humilde opinión).

Neil
fuente
La única ventaja que mencionó que una fachada tiene sobre la copia de campos a una nueva clase es evitar la copia (que probablemente no sea un problema a menos que los objetos tengan una cantidad absurda de campos). Por otro lado, tiene la desventaja de que debe modificar la clase original cada vez que necesita convertir a una nueva clase, a diferencia de un método de conversión estático.
Doval
@Doval, supongo que ese es el punto. No lo convertirías a una nueva clase. Crearía otra fachada si eso es lo que necesita. Los cambios realizados en BigObject solo deben aplicarse a la clase Facade y no en todas partes donde se usa.
Neil
Una distinción interesante entre este enfoque y la respuesta de Telastyn es si la responsabilidad de generar SmallObjectmentiras es SmallObjecto no BigObject. Por defecto, este enfoque obliga SmallObjecta evitar dependencias de miembros privados / protegidos de BigObject. Podemos ir un paso más allá y evitar dependencias de miembros privados / protegidos SmallObjectmediante el uso de un ToSmallObjectmétodo de extensión.
Brian
@Brian Corres el riesgo de abarrotarlo de BigObjectesa manera. Si quisieras hacer algo similar, ¿justificarías la creación de un ToAnotherObjectmétodo de extensión BigObject? Estas no deberían ser las preocupaciones de BigObject, ya que, presumiblemente, ya es lo suficientemente grande como es. También le permite separarse BigObjectde la creación de sus dependencias, lo que significa que podría usar fábricas y similares. El otro enfoque fuertemente parejas BigObjecty SmallObject. Eso puede estar bien en este caso particular, pero no es la mejor práctica en mi humilde opinión.
Neil
1
@Neil En realidad, Brian lo explicó mal, pero tiene razón: los métodos de extensión eliminan el acoplamiento. Ya no está BigObjectsiendo acoplado SmallObject, es solo un método estático en algún lugar que toma un argumento BigObjecty regresa SmallObject. Los métodos de extensión realmente son simplemente azúcar sintáctica para llamar a los métodos estáticos de una manera más agradable. El método de extensión no es parte de BigObject, es un método estático completamente separados. En realidad, es un uso bastante bueno de los métodos de extensión, y muy útil para las conversiones DTO en particular.
Luaan
6

Existe una convención muy fuerte que establece que los tipos de referencia mutables preservan la identidad. Debido a que el sistema generalmente no permite operadores de conversión definidos por el usuario en situaciones en las que un objeto del tipo de origen podría asignarse a una referencia del tipo de destino, solo hay unos pocos casos en los que las operaciones de conversión definidas por el usuario serían razonables para una referencia mutable tipos.

Sugeriría como un requisito que, dado x=(SomeType)foo;seguido en algún momento posterior y=(SomeType)foo;, con ambos lanzamientos aplicados al mismo objeto, x.Equals(y)debería ser siempre y para siempre verdadero, incluso si el objeto en cuestión fue modificado entre los dos lanzamientos. Tal situación podría aplicarse si, por ejemplo, uno tuviera un par de objetos de diferentes tipos, cada uno de los cuales tenía una referencia inmutable al otro, y lanzar cualquiera de los objetos al otro tipo devolvería su instancia emparejada. También podría aplicarse con tipos que sirven como envoltorios para objetos mutables, siempre que las identidades de los objetos que se envuelvan sean inmutables, y dos envoltorios del mismo tipo se reportarían como iguales si envolvieran la misma colección.

Su ejemplo particular usa clases mutables, pero no conserva ninguna forma de identidad; como tal, sugeriría que no es un uso apropiado de un operador de casting.

Super gato
fuente
1

Puede estar bien

Un problema con su ejemplo es que usa dichos nombres de ejemplo. Considerar:

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

Ahora, cuando tengas una buena idea de lo que significa un largo e int , entonces tanto el lanzamiento implícito de intto longcomo el lanzamiento explícito de longto intson bastante comprensibles. También es comprensible cómo se 3convierte 3y es solo otra forma de trabajar 3. Es comprensible cómo esto fallará int.MaxValue + 1en un contexto comprobado. Incluso cómo funcionará int.MaxValue + 1en un contexto no controlado para dar como resultado int.MinValueno es lo más difícil de asimilar.

Del mismo modo, cuando realiza una conversión implícita a un tipo base o explícitamente a un tipo derivado, es comprensible para cualquiera que sepa cómo funciona la herencia lo que está sucediendo y cuál será el resultado (o cómo podría fallar).

Ahora, con BigObject y SmallObject no tengo idea de cómo funciona esta relación. Si sus tipos reales eran tales que la relación de lanzamiento era obvia, entonces el lanzamiento podría ser una buena idea, aunque muchas veces, tal vez la gran mayoría, si este es el caso, entonces debería reflejarse en la jerarquía de clases y bastará con una transmisión normal basada en la herencia.

Jon Hanna
fuente
En realidad, no son mucho más de lo que se proporciona en cuestión, pero, por ejemplo, BigObjectpodrían describir un Employee {Name, Vacation Days, Bank details, Access to different building floors etc.}y SmallObjectpodrían ser un MoneyTransferRecepient {Name, Bank details}. Hay una traducción directa de Employeea MoneyTransferRecepient, y no hay razón para enviar a la aplicación bancaria más datos de los necesarios.
Gerino