【问题标题】:Polymorphism and Dependency injection多态性和依赖注入
【发布时间】:2015-05-27 19:27:25
【问题描述】:

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

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 { }

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

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);

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

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

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

 unityContainer.Resolve<TypeA>(myDomainObject) 

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

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

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

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

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

谢谢

【问题讨论】:

  • 如果方法签名不同(并且基本方法必须是虚拟的),您将无法覆盖。同时,如果子类依赖是针对不同接口的,那么在容器中配置这些应该没有问题吧?
  • 哦,是的,您是对的,我在编写示例时忘记了这一点。但实际上没关系,它肯定不会编译,但它指向我想要完成的行为。有一个问题,因为我需要根据类类型手动传递正确的参数,它破坏了我试图通过多态实现的抽象
  • 也许我遗漏了一些东西,但你为什么不能使用“is”运算符检查 myDomainObject 的类型?
  • 我同意我签入了 vs 并且它不喜欢你的覆盖。但是我想我明白你的意思。你看过命名的依赖吗?
  • 好的,依赖对象在哪里?必须对他们有所了解。我想我基本上同意史蒂文的观点——我们需要更多的背景信息,因为这可能不是一个通用的场景

标签: c# oop dependency-injection polymorphism unity-container


【解决方案1】:

TL;DR
将多态AbstractMethodIDependency 参数替换为特定于实现的构造依赖参数,该参数由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)
        // ...
    } 
}

但是,此设计中有些事情并非如此:

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

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

  • 使用构造函数注入在TypeBTypeC 类中创建正确的依赖关系
  • 如果有次要要求将基类TypeA 上的IDependency 公开给消费者,则向TypeA 类型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);

        }
    }
}

【讨论】:

  • 非常感谢您的宝贵时间和全面的解释。我终于想出了另一个解决这个问题的方法。这个想法本质上与你和 Erwan 的建议相似,但我实现 DI 的方式不同。我会尽快发布!
【解决方案2】:

知道依赖关系的任何东西都可以存在于具有函数的接口 IDependencyProvider 后面吗

IDependency GetDependency(Type type).  

这甚至可以只返回一个对象,实现接口的类需要知道所有子类型及其相关的依赖关系。

然后将AbstractMethod改为:

void AbstractMethod(IDependencyProvider provider);

然后在您的子类中覆盖它并调用

var dependency = provider.GetDependency(this.GetType());

然后,您的中间层对子类型或子依赖项一无所知。

【讨论】:

    【解决方案3】:

    这是一个有趣的问题,我的想法是您的存储库知道并创建了 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 然后将使用它们对依赖项的私有引用来实现它们的抽象方法,而不是在方法中传递它。

    我自己不时遇到各种形式的这个问题,在我看来,如果类型之间存在硬链接,只是通过注入配置等进行设置是错误的。因为它允许安装程序可能设置错误的配置

    这种方法还允许您以统一方式注入依赖项

    【讨论】:

    • nb。我同意@Steven 关于一般不注入实体类型对象的评论。但是在这种类型之间存在硬链接的情况下,我看不到一种反转并将依赖项作为服务的好方法
    【解决方案4】:

    非常感谢您对我的问题感兴趣,我昨天晚上想出了一个解决方案。目标是保持对客户端透明,并通过 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;
            }
    

    在同一个统一配置类中使用此方法:

    private ISpecializedDependencyForB CreateMyDomainService()
        {
            return container.Value.Resolve<ISpecializedDependencyForB>();
        }
    

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

    TypeA myDomainObject = database.TypeARepository.GetById(id); 
    myDomainObject.AbtractMethod();
    

    就是这样!

    这里有四件事:

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

    它基本上是一个手动且易于设置的“DI 框架”,位于 Unity 旁边。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多