我已经写了细微差别和详细信息here,但这里是我正在努力解决的问题的主旨:
我有一个第三方组件,它返回基本上看起来相同的各种POCO对象中的任何一个。看起来一样,我的意思是如果你浏览对象的JSON,你会认为它们是完全相同的类。但他们不是同一类。尽管具有相同的形状,但它们不实现接口或从基类继承。像这样:
// Order V1
public class ThirdPartyOrderV1
{
public decimal Amount { get; set; }
public ThirdPartyOrderItemV1[] LineItems { get; set; }
public ThirdPartyOrderAddressV1 BillingAddress { get; set; }
public ThirdPartyOrderAddressV1 ShippingAddress { get; set; }
public string SomeV1ThingIDontCareAbout { get; set; }
public decimal AnotherV1ThingIDontCareAbout { get; set; }
// ...
}
public class ThirdPartyOrderItemV1
{
public int ItemID { get; set; }
public int Quantity { get; set; }
// ...
}
public class ThirdPartyOrderAddressV1
{
public string Name { get; set; }
public string Street { get; set; }
// ...
}
// Order V2
public class ThirdPartyOrderV2
{
public decimal Amount { get; set; }
public ThirdPartyOrderItemV2[] LineItems { get; set; }
public ThirdPartyOrderAddressV2 BillingAddress { get; set; }
public ThirdPartyOrderAddressV2 ShippingAddress { get; set; }
public object SomeV2ThingIDontCareAbout { get; set; }
// ...
}
public class ThirdPartyOrderItemV2
{
public int ItemID { get; set; }
public int Quantity { get; set; }
// ...
}
public class ThirdPartyOrderAddressV2
{
public string Name { get; set; }
public string Street { get; set; }
// ...
}
该库有3个或4个这些版本。现在当我从库中获取对象时,我会对它们进行类型检查并将它们传递给方法以处理特定类型:
public static void Main()
{
var parser = new ThirdPartyOrderParser();
var order = parser.ParseOrder("text");
if (order is ThirdPartyOrderV1)
{
IngestOrderV1(order as ThirdPartyOrderV1);
}
else if (order is ThirdPartyOrderV2)
{
IngestOrderV2(order as ThirdPartyOrderV2);
}
// ...
}
这并不理想,因为我最终为每种类型复制了我的方法。我希望有一个方法可以处理任何看起来像订单(或行项目或地址等)的任意对象。
我知道我不可能是唯一一个遇到过这类问题的人。我有一些想法,比如将它转换为dynamic
对象并以这种方式引用属性。或者将其序列化为JSON并遍历[JObject表示](https://www.newtonsoft.com/json/help/html/QueryingLINQtoJSON.htm(或XML或YAML或其他)
但是这些解决方案仍然感觉很脆弱,只是错误。有没有更好的方法来处理这个?也许甚至一些可以生成类的工具代表类的共性并在实际类和这个“常见”类之间来回转换?
谢谢!
答案 0 :(得分:1)
使用dynamic
反对强类型工作是一个不错的主意。不幸的是,你必须处理所有这些类型,但至少它们是定义的,所以你知道它们各自代表什么。
我首先要定义自己的类,以满足您的需求。它们可能与此第三方库中的类相似也可能不相似。如果您收到任何其他形式的输入,请立即将其转换为您自己的模型。这样你就可以避免与其他人的课程紧密联系。如果它们发生了变化,或者您想要与其他库集成,那么您只需要将模型中的映射添加到模型中。
假设这些是外部模型:
public class ThirdPartyOrderV1
{
public decimal Amount { get; set; }
public ThirdPartyOrderItemV1[] LineItems { get; set; }
}
public class ThirdPartyOrderItemV1
{
public int ItemID { get; set; }
public int Quantity { get; set; }
}
这些是您的模型,它允许您处理来自多个来源的项目:
public class MyOrder
{
public Money Amount { get; set; }
public MyOrderItem Lines { get; set; }
}
public class MyOrderItem
{
public ItemId ItemId { get; set; }
public int Quantity { get; set; }
}
public class ItemId
{
public OrderItemType ItemType { get; set; }
public string Value { get; set; }
}
public enum OrderItemType
{
Internal,
Acme,
BobsThirdPartyItems
}
您可以使用Automapper,或者甚至只编写自己的方法来转换它们。如果不是真的很明显1:1映射我宁愿写自己的。你必须以同样的方式编写相同的逻辑。
public static class BobsThirdPartyItemExtensions
{
public static MyOrder ToMyOrder(this ThirdPartyOrderV1 source)
{
return new MyOrder
{
Amount = new Money(source.Amount, Currency.USD),
Lines = source.LineItems.Select(item => item.ToMyOrderItem()).ToArray()
};
}
public static MyOrderItem ToMyOrderItem(this ThirdPartyOrderItemV1 source)
{
return new MyOrderItem
{
ItemId = new ItemId
{
ItemType = OrderItemType.BobsThirdPartyItems,
Value = source.ItemID.ToString("0")
},
Quantity = source.Quantity
};
}
}
var myOrder = someThirdPartyV1order.ToMyOrder();
到目前为止,这可以保护您的应用程序免受不必要的外部复杂性的影响,确保在内部您只需要处理由您为应用程序定义的一组类。这仍然留下了处理所有冗余V1
和V2
类型的细节。你可以为那些编写额外的扩展方法,这些方法是相同的。这不是真正的代码重复。它们都是需要映射的不同类型。我可能会倾向于那个。
如果它们真的相同,那么您可以使用Automapper将一种外部类型转换为另一种外部类型。
假设你有
public class ThirdPartyOrderV2
{
public decimal Amount { get; set; }
public ThirdPartyOrderItemV2[] LineItems { get; set; }
public string DontNeedThis { get; set; }
}
public class ThirdPartyOrderItemV2
{
public int ItemID { get; set; }
public int Quantity { get; set; }
}
您可以像这样配置自动映射:
Mapper.Initialize(config =>
{
config.CreateMap<ThirdPartyOrderItemV2, ThirdPartyOrderItemV1>();
config.CreateMap<ThirdPartyOrderV2, ThirdPartyOrderV1>()
.ForSourceMember(source => source.DontNeedThis, option => option.Ignore());
});
这基本上表示将所有相同的属性从V2映射到V1,并忽略我们不关心的属性。
现在,给定V2
的实例,您可以这样做:
var v2 = new ThirdPartyOrderV2
{
Amount = 5.00M,
DontNeedThis = "Who cares",
LineItems = new ThirdPartyOrderItemV2[]
{
new ThirdPartyOrderItemV2 {ItemID = 5, Quantity = 200},
new ThirdPartyOrderItemV2 {ItemID = 4, Quantity = 50}
}
};
var v1 = Mapper.Map<ThirdPartyOrderV1>(v2);
var myOrder = v1.ToMyOrder();
你无法绕过做一些映射,但是我会保持强类型并且避免对dynamic
做任何事情,除非你别无选择。
你的评论提到这些类很大,所以创建自己的类将需要很多额外的工作来维护。无论如何,我会考虑这样做。如果维护意味着某些事情正在发生变化,那么您将不得不在某处更改某些代码来解决这个问题。我宁愿在我的应用程序边界处理这种变化,在那里我收到输入而不是内部逻辑断开并且必须在那里进行维护。当您将外部输入映射到您已定义并控制的内容时,至少可以完成维护。
如果您在强类型类和属性之间进行映射,那么即使它稍微繁琐,对于必须维护它的下一个人来说也是可以理解的。它也说明了这样一个现实:虽然今天的课程看起来很相似,但你却无法控制它。 (如果第三方图书馆做出了理性的选择,那么也许他们不会复制那么多的课程。你能相信他们做明天有意义的吗?)如果你偏离那个那么那么既费时又难以弄清楚什么是继续