无论您是否同意该服务定位器是反模式的,将应用程序与 DI 容器解耦都有不可忽视的实际好处。在某些极端情况下,将容器注入应用程序的一部分是有意义的,但在采用该路线之前,应该用尽所有其他选项。
选项 1
正如 StuartLC 指出的那样,您似乎正在重新发明轮子。 many 3rd party implementations 已经在类型之间进行了转换。我个人认为这些替代方案是首选,并评估哪个选项具有最佳 DI 支持以及它是否满足您的其他要求。
选项 2
更新
当我第一次发布这个答案时,我没有考虑到在翻译器的接口声明中使用 .NET 泛型和策略模式所涉及的困难,直到我尝试实现它。由于策略模式仍然是一种可能的选择,因此我将保留此答案。然而,我想出的最终产品并不像我最初希望的那样优雅——即翻译器本身的实现有点尴尬。
与所有模式一样,策略模式并不是适用于所有情况的灵丹妙药。特别是有 3 种情况不适合。
- 当您的类没有通用抽象类型时(例如在接口声明中使用泛型时)。
- 当接口的实现数量如此之多以至于内存成为问题时,因为它们都是同时加载的。
- 当您必须让 DI 容器控制对象的生命周期时,例如当您处理昂贵的一次性依赖项时。
也许有一种方法可以修复此解决方案的通用方面,我希望其他人能看到我在实施中出错的地方并提供更好的解决方案。
但是,如果你完全从 usage 和 testability 的角度来看它(可测试性和使用的尴尬是 OP 的关键问题),那并不是不好的解决方案。
Strategy Pattern 可以用来解决这个问题,而无需注入 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;
}
}
接下来是实现策略模式的实际策略类。
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 answer 和this answer 以了解其他一些实施示例。
通过使用策略模式,您可以将应用程序与 DI 容器分离,然后可以将其与 DI 容器分开进行单元测试。
选项 3
尚不清楚您要在其间转换的对象是否具有依赖关系。如果是这样,使用您已经提出的工厂比策略模式更适合只要您将其视为组合根的一部分。这也意味着工厂应该被视为一个不可测试的类,它应该包含完成其任务所需的尽可能少的逻辑。