多态和依赖注入

tob*_*777 3 c# oop polymorphism dependency-injection unity-container

这些天我经常面临这种情况,我正在寻找一个优雅的解决方案。我有 :

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 { }
Run Code Online (Sandbox Code Playgroud)

我的目标是在客户端的角度使事情变得透明,并像这样使用这段代码:

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);
Run Code Online (Sandbox Code Playgroud)

所以问题是,由于我不知道对象的具体类型,因此我无法向其中注入正确的依赖项。

我目前正在做的是创建一个抽象工厂,以注入正确的属性。我有两个问题,第一个是我最终会拥有很多工厂。第二个是它使多态变得无用,因为客户端实际上需要关心“管理”底层类型(我需要在工厂中注入所有可能的依赖项,并在客户端代码上实例化工厂)。

1)因此,我正在考虑使用统一的属性注入,但是在手动实例化之后,我无法确定是否可以解决对象的依赖关系。即使使用这种方法,我想我仍然会遇到同样的问题:如果存在这样的语法,我不确定 unity 是否会检查对象的实际类型并解决正确的依赖关系:

 unityContainer.Resolve<TypeA>(myDomainObject) 
Run Code Online (Sandbox Code Playgroud)

如果没有,我需要提前知道类型,然后会回到同样的问题。

2)我发现这篇文章提到EF为DI提供了一些机制,但它似乎只是为了注入框架服务(PluralizationService等......)。否则,这将是实现这一目标的好方法。

3)在这种情况下我也不能使用 DI ......从概念上看,DI 看起来不太适合多态性。不过,我对这个想法并不感到兴奋。

我很乐意为我试图实现的属性注入提供解决方案,或者我可以使用模式的想法。但是,我真的不想创建一个大型基础设施并为此目的混淆我的代码。

注意:在这种情况下,我不希望您使用域事件。

谢谢

Stu*_*tLC 5

TL;DR
用特定于实现的构造依赖参数替换IDependency多态AbstractMethod的参数,该参数由 IoC 容器注入,而不是由消费者注入。

更详细

原始类层次结构需要看起来更像这样,继承多态才能工作,因为超类virtual方法和子类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)
        // ...
    } 
}
Run Code Online (Sandbox Code Playgroud)

然而,有些事情并不符合这种设计:

  • LSP似乎被侵犯,因为尽管AbtractMethod()通告它接受基地IDependency接口,两个子类实际上依赖于一个专门的子类的依赖。
  • 这些方法的调用者建立正确的依赖关系并将其传递给方法以便正确调用它也是不寻常的,并且可以说是不方便的。

因此,如果可能的话,我会采用更传统的方法来安排依赖项,从而将依赖项传递给子类构造函数,并且在需要时可用于多态方法。这将需要为方法提供适当IDependency的内容。留给 IoC 容器做相应的依赖解析:

  • 使用构造函数注入创建正确的依赖到类TypeBTypeC
  • 如果有一个次要要求IDependency将基类上的一个暴露TypeA给消费者,那么添加一个额外的抽象属性 to TypeAof type IDependency(但这似乎有问题)
  • 根据 Ewan 的观察,存储库需要某种策略模式才能提供多态域实体(BC)。在这种情况下,将存储库耦合到工厂即可完成此操作。混凝土工厂需要绑定到容器才能进入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);

        }
    }
}
Run Code Online (Sandbox Code Playgroud)


tob*_*777 -1

非常感谢您对我的问题感兴趣,我昨天晚上想出了解决方案。目的是让事情对客户端透明,并通过诸如 baseObjectReference.AbstractMethodCall().

我终于意识到,通过利用 static 修饰符并将其用于 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;
}
Run Code Online (Sandbox Code Playgroud)

然后,在我的统一容器中添加以下代码:

public static void RegisterTypes(IUnityContainer container)
        {
            // .... registrations are here as usual 
            GetSpecializedDependencyForB.CreateSpecializedDependencyForB = CreateMyDomainService;
        }
Run Code Online (Sandbox Code Playgroud)

将此方法放在同一个统一配置类中:

private ISpecializedDependencyForB CreateMyDomainService()
    {
        return container.Value.Resolve<ISpecializedDependencyForB>();
    }
Run Code Online (Sandbox Code Playgroud)

最后,我可以像这样简单地使用我的对象:

TypeA myDomainObject = database.TypeARepository.GetById(id); 
myDomainObject.AbtractMethod();
Run Code Online (Sandbox Code Playgroud)

就是这样!

这里有四件事:

  • 第一个是我注入将创建服务实例的委托。
  • 那么它是线程安全的,因为静态成员只在应用程序开始时写入一次。所有其他访问都将被读取。此外,两个线程不会共享同一依赖项实例,因为委托始终创建一个新实例。
  • 另外一件有趣的事情是我可以依赖现有的 Unity 容器配置,不需要额外的代码。这很重要,因为我的依赖项可能需要构建其他依赖项。
  • 最后,unity 容器无论如何也是静态的,因此不存在内存泄漏。

它基本上是一本手册,可以轻松地在 Unity 旁边设置“DI 框架”。

更重要的是,它就像一个魅力!我终于对我的设计感到满意了。我只会在多态情况下使用这种方法,因为在其他情况下在方法中注入正确的依赖项很容易。然而,使用这种方法完全封装域模型可能会很有趣。