c# - 多态性和依赖注入(inject)

标签 c# oop dependency-injection polymorphism 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 { }

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

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

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

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

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

 unityContainer.Resolve<TypeA>(myDomainObject) 

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

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

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

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

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

谢谢

最佳答案

长话短说
将多态 AbstractMethodIDependency 参数替换为特定于实现的构造依赖参数,该参数由 IoC 容器注入(inject),而不是由消费者注入(inject)。

更详细

原始类层次结构需要看起来更像这样才能使继承多态性起作用,因为父类(super class) 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 接口(interface),但这两个子类实际上依赖于一个专门的子类依赖。
  • 对于这些方法的调用者来说,建立正确的依赖关系并将其传递给方法以便正确调用它也是不寻常的,而且可以说是不方便的。

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

  • 使用构造函数注入(inject)在类 TypeBTypeC 中创建正确的依赖关系
  • 如果有向消费者公开基类 TypeA 上的 IDependency 的次要要求,则向 TypeA 添加一个额外的抽象属性属于 IDependency 类型(但这看起来不太可靠)
  • 根据 Ewan 的观察,存储库需要某种策略模式才能为多态域实体(BC)提供服务。在这种情况下,将存储库耦合到工厂来执行此操作。具体工厂需要绑定(bind)到容器才能使用 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);

        }
    }
}

关于c# - 多态性和依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29233419/

相关文章:

symfony - 在服务中注入(inject) Doctrine Entity Manager - 不好的做法?

c# - 文件夹权限 - 无法翻译部分或所有标识引用

oop - Math Parser/Lexer - Token接口(interface)设计

java - 为一般情况实现过滤器设计模式(即过滤器特定的字符串属性)

c# - 为 MVC 中的每个请求创建和销毁的 Controller 实例 - WebAPI - 要注入(inject)的依赖范围是什么?

javascript - Angularjs:[$injector:modulerr] 无法实例化模块 ui-router

c# - 连接和更新数据表

c# - 我们如何在将数据绑定(bind)到 Windows Phone 中的文本 block 时处理空值

c# - 使用 SelfHosting 时,我可以在 app.config 中自动托管所有服务吗?

C++ 初学者 - 简单的代码块崩溃,原因不明