多态性和依赖注入
Polymorphism and Dependency injection
这些天我经常遇到这种情况,我正在寻找一个优雅的解决方案。我有:
public abstract class TypeA
{
public abstract void AbtractMethod(IDependency dependency);
}
public class TypeB : TypeA
{
public override void AbtractMethod(ISpecializedDependencyForB dependency) { }
}
public class TypeC : TypeA
{
public override void AbtractMethod(ISpecializedDependencyForC dependency) { }
}
public interface IDependency { }
public interface ISpecializedDependencyForB : IDependency { }
public interface ISpecializedDependencyForC : IDependency { }
我的 objective 是让事情在客户端的角度来看是透明的,并像这样使用这段代码:
TypeA myDomainObject = database.TypeARepository.GetById(id); // The important point here is that I don't know if the object is of TypeB or TypeC when I consume it.
IDependency dependency = ? // How do I get the right dependency
myDomainObject.AbtractMethod(dependency);
所以问题是因为我不知道对象的具体类型,所以我无法将正确的依赖项注入其中。
我目前正在做的是创建一个抽象工厂,以注入正确的属性。我有两个问题,第一个是我最终会拥有很多工厂。第二个是它使多态性变得无用,因为客户端实际上需要关心 "managing" 底层类型(我需要在工厂中注入所有可能的依赖项,并在客户端代码上实例化工厂)。
1) 因此,我正在考虑使用 属性 统一注入,但我无法确定是否可以在手动实例化对象后解决对象的依赖关系。即使使用这种方法,我认为我仍然会遇到同样的问题:如果存在这样的语法,我不确定 unity 是否会检查对象的实际类型并解决正确的依赖关系:
unityContainer.Resolve<TypeA>(myDomainObject)
如果不是,我需要提前知道类型,然后会回到同样的问题。
2) 我发现这篇文章提到 EF 为 DI 提供了一些机制,但它似乎只是为了注入框架服务(PluralizationService 等)。否则,这将是实现这一目标的好方法。
3) 在这种情况下我也不能使用 DI...从概念上看,DI 似乎不太适合多态性。不过我对这个想法并不感兴趣。
我很乐意为我试图实现的 属性 注入提供解决方案,或者我可以使用的模式想法。但是,我真的不想为此目的创建一个大型基础架构并混淆我的代码。
注意:我不希望您在这种情况下使用领域事件。
谢谢
无论是什么知道依赖关系,都可以存在于具有函数
的接口 IDependencyProvider 后面吗
IDependency GetDependency(Type type).
这甚至可以只是 return 一个对象,而实现接口的 class 需要知道所有子类型及其关联的依赖项。
AbstractMethod 则更改为:
void AbstractMethod(IDependencyProvider provider);
在您的子 classes 中,您然后覆盖它并调用
var dependency = provider.GetDependency(this.GetType());
您的中间层对子类型或子依赖项一无所知。
这是一个有趣的问题,我在想的是您的存储库知道并创建了 TypeB 和 TypeC 类,因此您可以在那个时候添加正确的依赖项
public class TypeARepository
{
private ISpecializedDependencyForB depB;
private ISpecializedDependencyForC depC;
public TypeARepository(ISpecializedDependencyForB depB, ISpecializedDependencyForC depC)
{
this.depB = depB;
this.depC = depC;
}
public TypeA GetById(string id)
{
if (id == "B")
{
return new TypeB(depB);
}
else
{
return new TypeC(depC);
}
}
}
然后 TypeB 和 TypeC 将使用它们对依赖项的私有引用来实现它们的抽象方法,而不是在方法中传入它。
我自己不时遇到各种形式的这个问题,在我看来,如果类型之间存在那么困难 link 只是通过注入配置或类似方式设置它是错误的。因为它允许安装程序设置错误的配置
这种方法还允许您使用 unity
注入依赖项
TL;DR
将多态 AbstractMethod
的 IDependency
参数替换为特定于实现的构造依赖参数,该参数由 IoC 容器注入,而不是由消费者注入。
更详细
原始的 class 层次结构需要看起来更像这样才能使继承多态性起作用,因为 superclass virtual
方法和 subclass override
方法必须匹配签名:
public abstract class TypeA // superclass
{
public abstract void AbtractMethod(IDependency dependency);
}
public class TypeB : TypeA // subclass 1
{
public override void AbtractMethod(IDependency dependency)
{
Contract.Requires(dependency is ISpecializedDependencyForB);
// ...
}
}
public class TypeC : TypeA // subclass 2
{
public override void AbtractMethod(IDependency dependency)
{
Contract.Requires(dependency is ISpecializedDependencyForC)
// ...
}
}
但是,有些事情并不符合此设计:
- LSP 似乎被违反了,因为虽然
AbtractMethod()
宣称它接受基础 IDependency
接口,但两个子 class 实际上依赖于一个专门的 subclassed 依赖。
- 这些方法的调用者建立正确的依赖关系并将其传递给方法以便正确调用它也是不寻常的,而且可以说是不方便的。
所以,如果可能的话,我会采用更传统的方法来安排依赖关系,即依赖关系被传递给 subclass 构造函数,并在需要时可用于多态方法。这消除了向方法提供适当 IDependency
的需要。留给 IoC 容器去做适当的依赖解析:
- 使用构造函数注入在 类
TypeB
和 TypeC
中创建正确的依赖关系
- 如果有次要要求向消费者公开基础 class
TypeA
上的 IDependency
,则向 [=22= 添加额外的摘要 属性 ] 类型 IDependency
(但这似乎有问题)
- 根据 Ewan 的观察,存储库需要某种策略模式才能提供多态域实体(
B
或 C
)。在这种情况下,将存储库耦合到工厂来执行此操作。混凝土工厂需要绑定到容器才能进入 Resolve()
.
所以把这些放在一起,你可能会得到这样的结果:
using System;
using System.Diagnostics;
using Microsoft.Practices.Unity;
namespace SO29233419
{
public interface IDependency { }
public interface ISpecializedDependencyForB : IDependency { }
public interface ISpecializedDependencyForC : IDependency { }
public class ConcreteDependencyForB : ISpecializedDependencyForB {};
public class ConcreteDependencyForC : ISpecializedDependencyForC { };
public abstract class TypeA
{
// Your polymorphic method
public abstract void AbtractMethod();
// Only exposing this for the purpose of demonstration
public abstract IDependency Dependency { get; }
}
public class TypeB : TypeA
{
private readonly ISpecializedDependencyForB _dependency;
public TypeB(ISpecializedDependencyForB dependency)
{
_dependency = dependency;
}
public override void AbtractMethod()
{
// Do stuff with ISpecializedDependencyForB without leaking the dependency to the caller
}
// You hopefully won't need this prop
public override IDependency Dependency
{
get { return _dependency; }
}
}
public class TypeC : TypeA
{
private readonly ISpecializedDependencyForC _dependency;
public TypeC(ISpecializedDependencyForC dependency)
{
_dependency = dependency;
}
public override void AbtractMethod()
{
// Do stuff with ISpecializedDependencyForC without leaking the dependency to the caller
}
public override IDependency Dependency
{
get { return _dependency; }
}
}
public interface ITypeAFactory
{
TypeA CreateInstance(Type typeOfA);
}
public class ConcreteTypeAFactory : ITypeAFactory
{
private readonly IUnityContainer _container;
public ConcreteTypeAFactory(IUnityContainer container)
{
_container = container;
}
public TypeA CreateInstance(Type typeOfA)
{
return _container.Resolve(typeOfA) as TypeA;
}
}
public class TypeARepository
{
private readonly ITypeAFactory _factory;
public TypeARepository(ITypeAFactory factory)
{
_factory = factory;
}
public TypeA GetById(int id)
{
// As per Ewan, some kind of Strategy Pattern.
// e.g. fetching a record from a database and use a discriminating column etc.
return (id%2 == 0)
? _factory.CreateInstance(typeof (TypeB))
: _factory.CreateInstance(typeof (TypeC));
// Set the properties of the TypeA from the database after creation?
}
}
class Program
{
static void Main(string[] args)
{
// Unity Bootstrapping
var myContainer = new UnityContainer();
myContainer.RegisterType<ISpecializedDependencyForB, ConcreteDependencyForB>();
myContainer.RegisterType<ISpecializedDependencyForC, ConcreteDependencyForC>();
myContainer.RegisterType(typeof(TypeB));
myContainer.RegisterType(typeof(TypeC));
var factory = new ConcreteTypeAFactory(myContainer);
myContainer.RegisterInstance(factory);
myContainer.RegisterType<TypeARepository>(new InjectionFactory(c => new TypeARepository(factory)));
// And finally, your client code.
// Obviously your actual client would use Dependency Injection, not Service Location
var repository = myContainer.Resolve<TypeARepository>();
var evenNumberIsB = repository.GetById(100);
Debug.Assert(evenNumberIsB is TypeB);
Debug.Assert(evenNumberIsB.Dependency is ISpecializedDependencyForB);
var oddNumberIsC = repository.GetById(101);
Debug.Assert(oddNumberIsC is TypeC);
Debug.Assert(oddNumberIsC.Dependency is ISpecializedDependencyForC);
}
}
}
非常感谢您对我的问题的关注,我昨天晚上想出了一个解决方案。 objective 是为了让事情对客户端透明,并通过 baseObjectReference.AbstractMethodCall()
.
等语法充分利用多态性
我终于意识到我能够通过利用静态修饰符并将其用于 DI 目的来实现我所追求的目标。所以我有那个:
public abstract class TypeA
{
public abstract void AbtractMethod();
}
public class TypeB : TypeA
{
private ISpecializedDependencyForB SpecializedDependencyForB
{
get
{
return GetSpecializedDependencyForB.CreateSpecializedDependencyForB();
}
}
public override void AbtractMethod() { // do stuff with dependency }
}
public static class GetSpecializedDependencyForB
{
public static ISpecializedDependencyForB DependencyForB
{
return CreateSpecializedDependencyForB();
}
public delegate ISpecializedDependencyForB CreateSpecializedDependencyForBDelegate();
public static CreateSpecializedDependencyForBDelegate CreateSpecializedDependencyForB;
}
然后,在我的统一容器中添加以下代码:
public static void RegisterTypes(IUnityContainer container)
{
// .... registrations are here as usual
GetSpecializedDependencyForB.CreateSpecializedDependencyForB = CreateMyDomainService;
}
在相同的统一配置中使用此方法class :
private ISpecializedDependencyForB CreateMyDomainService()
{
return container.Value.Resolve<ISpecializedDependencyForB>();
}
最后,我可以像这样简单地使用我的对象:
TypeA myDomainObject = database.TypeARepository.GetById(id);
myDomainObject.AbtractMethod();
就是这样!
这里有四件事:
- 第一个是我注入将创建服务实例的委托。
- 然后它是线程安全的,因为静态成员只在应用程序开始时写入一次。将读取所有其他访问。此外,两个线程不会共享相同的依赖实例,因为委托总是创建一个新的实例。
- 还有一件有趣的事情是,我可以依赖现有的统一容器配置,不需要额外的代码。这很重要,因为我的依赖可能需要构建其他依赖。
- 最后统一容器也是静态的,所以没有内存泄漏。
它基本上是一本手册,易于设置 "DI framework" 坐在 Unity 旁边。
更重要的是,它就像一个魅力!我终于对我的设计感到满意了。我只会将这种方法用于多态情况,因为在其他情况下,在方法中注入正确的依赖项很容易。然而,使用这种方法完全封装领域模型可能会很有趣。
这些天我经常遇到这种情况,我正在寻找一个优雅的解决方案。我有:
public abstract class TypeA
{
public abstract void AbtractMethod(IDependency dependency);
}
public class TypeB : TypeA
{
public override void AbtractMethod(ISpecializedDependencyForB dependency) { }
}
public class TypeC : TypeA
{
public override void AbtractMethod(ISpecializedDependencyForC dependency) { }
}
public interface IDependency { }
public interface ISpecializedDependencyForB : IDependency { }
public interface ISpecializedDependencyForC : IDependency { }
我的 objective 是让事情在客户端的角度来看是透明的,并像这样使用这段代码:
TypeA myDomainObject = database.TypeARepository.GetById(id); // The important point here is that I don't know if the object is of TypeB or TypeC when I consume it.
IDependency dependency = ? // How do I get the right dependency
myDomainObject.AbtractMethod(dependency);
所以问题是因为我不知道对象的具体类型,所以我无法将正确的依赖项注入其中。
我目前正在做的是创建一个抽象工厂,以注入正确的属性。我有两个问题,第一个是我最终会拥有很多工厂。第二个是它使多态性变得无用,因为客户端实际上需要关心 "managing" 底层类型(我需要在工厂中注入所有可能的依赖项,并在客户端代码上实例化工厂)。
1) 因此,我正在考虑使用 属性 统一注入,但我无法确定是否可以在手动实例化对象后解决对象的依赖关系。即使使用这种方法,我认为我仍然会遇到同样的问题:如果存在这样的语法,我不确定 unity 是否会检查对象的实际类型并解决正确的依赖关系:
unityContainer.Resolve<TypeA>(myDomainObject)
如果不是,我需要提前知道类型,然后会回到同样的问题。
2) 我发现这篇文章提到 EF 为 DI 提供了一些机制,但它似乎只是为了注入框架服务(PluralizationService 等)。否则,这将是实现这一目标的好方法。
3) 在这种情况下我也不能使用 DI...从概念上看,DI 似乎不太适合多态性。不过我对这个想法并不感兴趣。
我很乐意为我试图实现的 属性 注入提供解决方案,或者我可以使用的模式想法。但是,我真的不想为此目的创建一个大型基础架构并混淆我的代码。
注意:我不希望您在这种情况下使用领域事件。
谢谢
无论是什么知道依赖关系,都可以存在于具有函数
的接口 IDependencyProvider 后面吗IDependency GetDependency(Type type).
这甚至可以只是 return 一个对象,而实现接口的 class 需要知道所有子类型及其关联的依赖项。
AbstractMethod 则更改为:
void AbstractMethod(IDependencyProvider provider);
在您的子 classes 中,您然后覆盖它并调用
var dependency = provider.GetDependency(this.GetType());
您的中间层对子类型或子依赖项一无所知。
这是一个有趣的问题,我在想的是您的存储库知道并创建了 TypeB 和 TypeC 类,因此您可以在那个时候添加正确的依赖项
public class TypeARepository
{
private ISpecializedDependencyForB depB;
private ISpecializedDependencyForC depC;
public TypeARepository(ISpecializedDependencyForB depB, ISpecializedDependencyForC depC)
{
this.depB = depB;
this.depC = depC;
}
public TypeA GetById(string id)
{
if (id == "B")
{
return new TypeB(depB);
}
else
{
return new TypeC(depC);
}
}
}
然后 TypeB 和 TypeC 将使用它们对依赖项的私有引用来实现它们的抽象方法,而不是在方法中传入它。
我自己不时遇到各种形式的这个问题,在我看来,如果类型之间存在那么困难 link 只是通过注入配置或类似方式设置它是错误的。因为它允许安装程序设置错误的配置
这种方法还允许您使用 unity
注入依赖项TL;DR
将多态 AbstractMethod
的 IDependency
参数替换为特定于实现的构造依赖参数,该参数由 IoC 容器注入,而不是由消费者注入。
更详细
原始的 class 层次结构需要看起来更像这样才能使继承多态性起作用,因为 superclass virtual
方法和 subclass override
方法必须匹配签名:
public abstract class TypeA // superclass
{
public abstract void AbtractMethod(IDependency dependency);
}
public class TypeB : TypeA // subclass 1
{
public override void AbtractMethod(IDependency dependency)
{
Contract.Requires(dependency is ISpecializedDependencyForB);
// ...
}
}
public class TypeC : TypeA // subclass 2
{
public override void AbtractMethod(IDependency dependency)
{
Contract.Requires(dependency is ISpecializedDependencyForC)
// ...
}
}
但是,有些事情并不符合此设计:
- LSP 似乎被违反了,因为虽然
AbtractMethod()
宣称它接受基础IDependency
接口,但两个子 class 实际上依赖于一个专门的 subclassed 依赖。 - 这些方法的调用者建立正确的依赖关系并将其传递给方法以便正确调用它也是不寻常的,而且可以说是不方便的。
所以,如果可能的话,我会采用更传统的方法来安排依赖关系,即依赖关系被传递给 subclass 构造函数,并在需要时可用于多态方法。这消除了向方法提供适当 IDependency
的需要。留给 IoC 容器去做适当的依赖解析:
- 使用构造函数注入在 类
TypeB
和TypeC
中创建正确的依赖关系
- 如果有次要要求向消费者公开基础 class
TypeA
上的IDependency
,则向 [=22= 添加额外的摘要 属性 ] 类型IDependency
(但这似乎有问题) - 根据 Ewan 的观察,存储库需要某种策略模式才能提供多态域实体(
B
或C
)。在这种情况下,将存储库耦合到工厂来执行此操作。混凝土工厂需要绑定到容器才能进入Resolve()
.
所以把这些放在一起,你可能会得到这样的结果:
using System;
using System.Diagnostics;
using Microsoft.Practices.Unity;
namespace SO29233419
{
public interface IDependency { }
public interface ISpecializedDependencyForB : IDependency { }
public interface ISpecializedDependencyForC : IDependency { }
public class ConcreteDependencyForB : ISpecializedDependencyForB {};
public class ConcreteDependencyForC : ISpecializedDependencyForC { };
public abstract class TypeA
{
// Your polymorphic method
public abstract void AbtractMethod();
// Only exposing this for the purpose of demonstration
public abstract IDependency Dependency { get; }
}
public class TypeB : TypeA
{
private readonly ISpecializedDependencyForB _dependency;
public TypeB(ISpecializedDependencyForB dependency)
{
_dependency = dependency;
}
public override void AbtractMethod()
{
// Do stuff with ISpecializedDependencyForB without leaking the dependency to the caller
}
// You hopefully won't need this prop
public override IDependency Dependency
{
get { return _dependency; }
}
}
public class TypeC : TypeA
{
private readonly ISpecializedDependencyForC _dependency;
public TypeC(ISpecializedDependencyForC dependency)
{
_dependency = dependency;
}
public override void AbtractMethod()
{
// Do stuff with ISpecializedDependencyForC without leaking the dependency to the caller
}
public override IDependency Dependency
{
get { return _dependency; }
}
}
public interface ITypeAFactory
{
TypeA CreateInstance(Type typeOfA);
}
public class ConcreteTypeAFactory : ITypeAFactory
{
private readonly IUnityContainer _container;
public ConcreteTypeAFactory(IUnityContainer container)
{
_container = container;
}
public TypeA CreateInstance(Type typeOfA)
{
return _container.Resolve(typeOfA) as TypeA;
}
}
public class TypeARepository
{
private readonly ITypeAFactory _factory;
public TypeARepository(ITypeAFactory factory)
{
_factory = factory;
}
public TypeA GetById(int id)
{
// As per Ewan, some kind of Strategy Pattern.
// e.g. fetching a record from a database and use a discriminating column etc.
return (id%2 == 0)
? _factory.CreateInstance(typeof (TypeB))
: _factory.CreateInstance(typeof (TypeC));
// Set the properties of the TypeA from the database after creation?
}
}
class Program
{
static void Main(string[] args)
{
// Unity Bootstrapping
var myContainer = new UnityContainer();
myContainer.RegisterType<ISpecializedDependencyForB, ConcreteDependencyForB>();
myContainer.RegisterType<ISpecializedDependencyForC, ConcreteDependencyForC>();
myContainer.RegisterType(typeof(TypeB));
myContainer.RegisterType(typeof(TypeC));
var factory = new ConcreteTypeAFactory(myContainer);
myContainer.RegisterInstance(factory);
myContainer.RegisterType<TypeARepository>(new InjectionFactory(c => new TypeARepository(factory)));
// And finally, your client code.
// Obviously your actual client would use Dependency Injection, not Service Location
var repository = myContainer.Resolve<TypeARepository>();
var evenNumberIsB = repository.GetById(100);
Debug.Assert(evenNumberIsB is TypeB);
Debug.Assert(evenNumberIsB.Dependency is ISpecializedDependencyForB);
var oddNumberIsC = repository.GetById(101);
Debug.Assert(oddNumberIsC is TypeC);
Debug.Assert(oddNumberIsC.Dependency is ISpecializedDependencyForC);
}
}
}
非常感谢您对我的问题的关注,我昨天晚上想出了一个解决方案。 objective 是为了让事情对客户端透明,并通过 baseObjectReference.AbstractMethodCall()
.
我终于意识到我能够通过利用静态修饰符并将其用于 DI 目的来实现我所追求的目标。所以我有那个:
public abstract class TypeA
{
public abstract void AbtractMethod();
}
public class TypeB : TypeA
{
private ISpecializedDependencyForB SpecializedDependencyForB
{
get
{
return GetSpecializedDependencyForB.CreateSpecializedDependencyForB();
}
}
public override void AbtractMethod() { // do stuff with dependency }
}
public static class GetSpecializedDependencyForB
{
public static ISpecializedDependencyForB DependencyForB
{
return CreateSpecializedDependencyForB();
}
public delegate ISpecializedDependencyForB CreateSpecializedDependencyForBDelegate();
public static CreateSpecializedDependencyForBDelegate CreateSpecializedDependencyForB;
}
然后,在我的统一容器中添加以下代码:
public static void RegisterTypes(IUnityContainer container)
{
// .... registrations are here as usual
GetSpecializedDependencyForB.CreateSpecializedDependencyForB = CreateMyDomainService;
}
在相同的统一配置中使用此方法class :
private ISpecializedDependencyForB CreateMyDomainService()
{
return container.Value.Resolve<ISpecializedDependencyForB>();
}
最后,我可以像这样简单地使用我的对象:
TypeA myDomainObject = database.TypeARepository.GetById(id);
myDomainObject.AbtractMethod();
就是这样!
这里有四件事:
- 第一个是我注入将创建服务实例的委托。
- 然后它是线程安全的,因为静态成员只在应用程序开始时写入一次。将读取所有其他访问。此外,两个线程不会共享相同的依赖实例,因为委托总是创建一个新的实例。
- 还有一件有趣的事情是,我可以依赖现有的统一容器配置,不需要额外的代码。这很重要,因为我的依赖可能需要构建其他依赖。
- 最后统一容器也是静态的,所以没有内存泄漏。
它基本上是一本手册,易于设置 "DI framework" 坐在 Unity 旁边。
更重要的是,它就像一个魅力!我终于对我的设计感到满意了。我只会将这种方法用于多态情况,因为在其他情况下,在方法中注入正确的依赖项很容易。然而,使用这种方法完全封装领域模型可能会很有趣。