有关类型映射和扩展方法的最佳实践


15

我想问一些有关映射类型和在C#中使用扩展方法的最佳实践的问题。我知道这个主题在过去几年中已被讨论过多次,但是我读了很多帖子,但仍然有疑问。

我遇到的问题是扩展具有“转换”功能的类。假设我有“ Person”类,该类表示将由某些逻辑使用的对象。我还有一个“客户”类,代表来自外部API的响应(实际上会有多个API,因此我需要将每个API的响应映射到常见类型:Person)。我可以访问两个类的源代码,并且理论上可以在那里实现我自己的方法。我需要将客户转换为人,以便可以将其保存到数据库。该项目不使用任何自动映射器。

我有4种可能的解决方案:

  1. Consumer类中的.ToPerson()方法。这很简单,但是对我来说似乎是在打破“单一责任”模式,尤其是Consumer类也映射到其他类(某些其他外部API要求),因此它需要包含多种映射方法。

  2. Person类中的构造函数,以Consumer为参数。也很容易,似乎也打破了单一责任模式。我需要有多个映射构造函数(因为将有另一个API的类,提供与Consumer相同的数据,但格式略有不同)

  3. 具有扩展方法的Converters类。这样,我可以为Consumer类编写.ToPerson()方法,并且当另一个API与其自身的NewConsumer类一起引入时,我可以编写另一个扩展方法并将其全部保存在同一文件中。我听过一种意见,即扩展方法通常是邪恶的,仅在绝对必要时才应使用扩展方法,这就是使我受挫的原因。否则我喜欢这个解决方案

  4. 转换器/映射器类。我创建了单独的类来处理转换并实现将源类实例作为参数并返回目标类实例的方法。

综上所述,我的问题可以简化为问题的数量(全部与我上面所述的内容相关):

  1. 是否将转换方法放入(POCO?)对象中(例如Consumer类中的.ToPerson()方法)是否被视为打破单一责任模式?

  2. 在(DTO类)类中使用转换构造函数是否被视为打破单一责任模式?尤其是如果可以从多种源类型转换此类,那么是否需要多个转换构造函数?

  3. 在访问原始类源代码的同时使用扩展方法是否被视为不良做法?这样的行为可以用作分隔逻辑的可行模式吗?还是反模式?


Person课程是DTO吗?它包含任何行为吗?
Yacoub Massad 2015年

据我所知,它在技术上是DTO。它包含一些“注入”的逻辑作为扩展方法,但是此逻辑仅限于“ ConvertToThatClass”类型的方法。这些都是遗留方法。我的工作是使用这些类中的一些来实现新功能,但是有人告诉我,如果为了保持一致而不好的话,我就不要坚持目前的方法。因此,我想知道这种使用扩展方法进行转换的现有方法是否是一种好的方法,是否应该坚持使用或尝试其他方法
emsi

Answers:


11

是否将转换方法放入(POCO?)对象中(例如Consumer类中的.ToPerson()方法)是否被视为打破单一责任模式?

是的,因为转换是另一责任。

在(DTO类)类中使用转换构造函数是否被视为打破单一责任模式?尤其是如果可以从多种源类型转换此类,那么是否需要多个转换构造函数?

是的,转换是另一责任。如果通过构造函数或转换方法(例如ToPerson)进行操作,则没有任何区别。

在访问原始类源代码的同时使用扩展方法是否被视为不良做法?

不必要。即使您具有要扩展的类的源代码,也可以创建扩展方法。我认为是否创建扩展方法应由这种方法的性质决定。例如,它包含很多逻辑吗?它是否还依赖于对象本身成员的其他条件?我要说的是,您不应该具有需要依赖项才能工作或包含复杂逻辑的扩展方法。扩展方法中仅应包含最简单的逻辑。

这样的行为可以用作分隔逻辑的可行模式吗?还是反模式?

如果逻辑很复杂,那么我认为您不应该使用扩展方法。如前所述,您仅应将扩展方法用于最简单的事情。我不会认为转换很简单。

我建议您创建转换服务。您可以为它提供一个通用接口,如下所示:

public interface IConverter<TSource,TDestination>
{
    TDestination Convert(TSource source_object);
}

您可以拥有这样的转换器:

public class PersonToCustomerConverter : IConverter<Person,Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

而且,您可以使用“ 依赖注入”将转换器(例如IConverter<Person,Customer>)注入到需要在Person和之间进行转换的任何类Customer


如果我没记错的话,IConverter框架中已经有一个,只是在等待实现。
RubberDuck

@RubberDuck,它在哪里存在?
Yacoub Massad 2015年

我在想IConvertable,这不是我们在这里寻找的。我的错。
RubberDuck 2015年

啊哈!我发现我在想@YacoubMassad。ConverterList调用时使用ConvertAllmsdn.microsoft.com/zh-CN/library/kt456a2y(v=vs.110).aspx我不知道这对OP有多大用处。
RubberDuck

也相关。有人采用了您在此处提出的方法。codereview.stackexchange.com/q/51889/41243
RubberDuck 2015年

5

是否将转换方法放入(POCO?)对象中(如.ToPerson()Consumer类中的方法)被视为破坏单一责任模式?

是。一个Consumer类负责保存与使用者有关的数据(并可能执行某些操作),而不应负责将其自身转换为另一个不相关的类型。

在(DTO类)类中使用转换构造函数是否被视为打破单一责任模式?尤其是如果可以从多种源类型转换此类,那么是否需要多个转换构造函数?

大概。我通常在域和DTO对象之外都具有方法来进行转换。我经常使用一个接收域对象并将其(反)序列化为数据库,内存,文件等的存储库。如果我将这种逻辑放在域类中,那么现在我已经将它们绑定到一种特殊的序列化形式,这对测试(和其他事情)不利。如果我将逻辑放在我的DTO类中,那么我已经将它们绑定到我的域中,再次限制了测试。

在访问原始类源代码的同时使用扩展方法是否被视为不良做法?

虽然它们可能会被过度使用,但一般而言并非如此。使用扩展方法,可以创建使代码更易于阅读的可选扩展。它们确实有缺点-容易产生编译器必须解决的歧义(有时是无声的),并且由于扩展方法的来源并不十分明显,因此会使代码更难以调试。

在您的情况下,转换器或映射器类似乎是最简单,最直接的方法,尽管我认为使用扩展方法没有做错任何事情。


2

如何使用AutoMapper或自定义映射器

MyMapper
   .CreateMap<Person>()
   .To<PersonViewModel>()
   .Map(p => p.Name, vm => vm.FirstName)
   .To<SomeDTO>()
   .Map(...);

来自域

 db.Persons
   .ToListAsync()
   .Map<PersonViewModel>();

在后台,您可以抽象AutoMapper或滚动自己的映射器


2

我知道这很老,但仍然很现实。这将使您可以双向转换。在使用实体框架和创建视图模型(DTO)时,我发现这很有用。

public interface IConverter<TSource, TDestination>
{
    TDestination Convert(TSource source_object);
    TSource Convert(TDestination source_object);
}

public class PersonCustomerConverter : IConverter<Person, Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
    public Person Convert(Customer source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

我发现AutoMapper对于执行真正简单的映射操作来说有点过多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.