gpt4 book ai didi

c# - 我如何改进此翻译器对象工厂以简化单元测试?

转载 作者:太空宇宙 更新时间:2023-11-03 20:00:26 26 4
gpt4 key购买 nike

在我的一个项目中,我有一些基于 ITranslator 接口(interface)的类,如下所示:

interface ITranslator<TSource, TDest>
{
TDest Translate(TSource toTranslate);
}

这些类将数据对象转换为新形式。为了获得翻译器的实例,我有一个 ITranslatorFactory 的方法 ITranslator<TSource, TDest> GetTranslator<TSource, TDest>() .我想不出任何方法来存储基于广泛泛型的函数集合(这里唯一的共同祖先是 Object ),所以 GetTranslator方法目前仅使用 Unity 来解决 ITranslator<TSource, TDest>匹配请求的翻译器。

这个实现感觉很别扭。我读过服务定位器 is an anti-pattern ,并且无论是否存在,此实现都会使单元测试变得更加困难,因为我必须提供一个已配置的 Unity 容器来测试任何依赖于翻译器的代码。

不幸的是,我想不出更有效的策略来获得合适的翻译器。有人对我如何将此设置重构为更优雅的解决方案有任何建议吗?

最佳答案

无论您是否同意服务定位器是反模式,将应用程序与 DI 容器解耦都有不容忽视的实际好处。在某些边缘情况下,将容器注入(inject)应用程序的一部分是有意义的,但在走这条路之前应该用尽所有其他选项。

选项1

正如 StuartLC 所指出的,您似乎是在重新发明轮子。有many 3rd party implementations已经在类型之间进行翻译。我个人会将这些备选方案视为首选,并评估哪个选项具有最佳的 DI 支持以及它是否满足您的其他要求。

选项 2

UPDATE

When I first posted this answer, I didn't take into account the difficulties involved with using .NET Generics in the interface declaration of the translator with the Strategy pattern until I tried to implement it. Since Strategy pattern is still a possible option, I am leaving this answer in place. However, the end product I came up with isn't as elegant as I had first hoped - namely the implementations of the translators themselves are a bit awkward.

Like all patterns, the Strategy pattern is not a silver bullet that works for every situation. There are 3 cases in particular where it is not a good fit.

  1. When you have classes that have no common abstract type (such as when using Generics in the interface declaration).
  2. When the number of implementations of the interface are so many that memory becomes an issue, since they are all loaded at the same time.
  3. When you must give the DI container control of the object lifetime, such as when you are dealing with expensive disposable dependencies.

Maybe there is a way to fix the generic aspect of this solution and I hope someone else sees where I went wrong with the implementation and provides a better one.

However, if you look at it entirely from a usage and testability standpoint (testability and awkwardness of use being the key problems of the OP), it is not that bad of a solution.

Strategy Pattern可以在不注入(inject) DI 容器的情况下解决这个问题。这需要一些重新管道来处理您制作的通用类型,以及一种映射转换器以与所涉及的类型一起使用的方法。

public interface ITranslator
{
Type SourceType { get; }
Type DestinationType { get; }
TDest Translate<TSource, TDest>(TSource toTranslate);
}

public static class ITranslatorExtensions
{
public static bool AppliesTo(this ITranslator translator, Type sourceType, Type destinationType)
{
return (translator.SourceType.Equals(sourceType) && translator.DestinationType.Equals(destinationType));
}
}

我们有几个要在它们之间进行转换的对象。

class Model
{
public string Property1 { get; set; }
public int Property2 { get; set; }
}

class ViewModel
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}

然后,我们有翻译器实现。

public class ModelToViewModelTranslator : ITranslator
{
public Type SourceType
{
get { return typeof(Model); }
}

public Type DestinationType
{
get { return typeof(ViewModel); }
}

public TDest Translate<TSource, TDest>(TSource toTranslate)
{
Model source = toTranslate as Model;
ViewModel destination = null;
if (source != null)
{
destination = new ViewModel()
{
Property1 = source.Property1,
Property2 = source.Property2.ToString()
};
}

return (TDest)(object)destination;
}
}

public class ViewModelToModelTranslator : ITranslator
{
public Type SourceType
{
get { return typeof(ViewModel); }
}

public Type DestinationType
{
get { return typeof(Model); }
}

public TDest Translate<TSource, TDest>(TSource toTranslate)
{
ViewModel source = toTranslate as ViewModel;
Model destination = null;
if (source != null)
{
destination = new Model()
{
Property1 = source.Property1,
Property2 = int.Parse(source.Property2)
};
}

return (TDest)(object)destination;
}
}

接下来,是实现 Strategy 模式的实际 Strategy 类。

public interface ITranslatorStrategy
{
TDest Translate<TSource, TDest>(TSource toTranslate);
}

public class TranslatorStrategy : ITranslatorStrategy
{
private readonly ITranslator[] translators;

public TranslatorStrategy(ITranslator[] translators)
{
if (translators == null)
throw new ArgumentNullException("translators");

this.translators = translators;
}

private ITranslator GetTranslator(Type sourceType, Type destinationType)
{
var translator = this.translators.FirstOrDefault(x => x.AppliesTo(sourceType, destinationType));
if (translator == null)
{
throw new Exception(string.Format(
"There is no translator for the specified type combination. Source: {0}, Destination: {1}.",
sourceType.FullName, destinationType.FullName));
}
return translator;
}

public TDest Translate<TSource, TDest>(TSource toTranslate)
{
var translator = this.GetTranslator(typeof(TSource), typeof(TDest));
return translator.Translate<TSource, TDest>(toTranslate);
}
}

用法

using System;
using System.Linq;
using Microsoft.Practices.Unity;

class Program
{
static void Main(string[] args)
{
// Begin Composition Root
var container = new UnityContainer();

// IMPORTANT: For Unity to resolve arrays, you MUST name the instances.
container.RegisterType<ITranslator, ModelToViewModelTranslator>("ModelToViewModelTranslator");
container.RegisterType<ITranslator, ViewModelToModelTranslator>("ViewModelToModelTranslator");
container.RegisterType<ITranslatorStrategy, TranslatorStrategy>();
container.RegisterType<ISomeService, SomeService>();

// Instantiate a service
var service = container.Resolve<ISomeService>();

// End Composition Root

// Do something with the service
service.DoSomething();
}
}

public interface ISomeService
{
void DoSomething();
}

public class SomeService : ISomeService
{
private readonly ITranslatorStrategy translatorStrategy;

public SomeService(ITranslatorStrategy translatorStrategy)
{
if (translatorStrategy == null)
throw new ArgumentNullException("translatorStrategy");

this.translatorStrategy = translatorStrategy;
}

public void DoSomething()
{
// Create a Model
Model model = new Model() { Property1 = "Hello", Property2 = 123 };

// Translate to ViewModel
ViewModel viewModel = this.translatorStrategy.Translate<Model, ViewModel>(model);

// Translate back to Model
Model model2 = this.translatorStrategy.Translate<ViewModel, Model>(viewModel);
}
}

请注意,如果您将上述每个代码块(从最后一个开始)复制到控制台应用程序中,它将按原样运行。

看看this answerthis answer一些额外的实现示例。

通过使用策略模式,您可以将应用程序与 DI 容器分离,然后它可以独立于 DI 容器进行单元测试。

选项3

尚不清楚您要在其间转换的对象是否具有依赖性。如果是这样,使用您已经想出的工厂比策略模式更合适只要您将其视为组合根的一部分。这也意味着工厂应该被视为一个不可测试的类,并且它应该包含尽可能少的逻辑来完成它的任务。

关于c# - 我如何改进此翻译器对象工厂以简化单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29107823/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com