IOC编程

在程序设计的过程中,我们经常用到面向对象设计原理:

1. 单一责任原则: 一个【类,方法】只做一件事情。

2. 开放/封闭原则: 添加任何新行为,应该是扩展到新类中,而不应该直接修改原来运行良好的代码。

3. 李式替代原则: 类型T的对象可以用其子类型的对象替换,程序可以正常运行。

4. 接口隔离原则: 非常大的接口应该分成更小和更具体的接口,以便客户端类只需要知道它们使用的方法:不应该强制客户端类依赖于它不使用的方法。

5. 依赖倒置原则: 高级模块不应该依赖于低级模块。两者都应该取决于抽象。抽象不应该依赖于细节。细节应取决于抽象。

对于大型复杂的系统,我们早期进行有效的设计是非常有必要的,可以增加程序的:可维护性,可测性,灵活性和可扩展性,并行开发,低耦合等。这篇文章的主题是低耦合编程。低耦合的主要表现是程序依赖接口编程,但不依赖接口的具体实现。通常,应用程序越大越复杂,维护起来就越困难,因此这些技术越有可能发挥作用。我们先看一下耦合编程的示例:

public class TenantStore : ITenantStore
{
  public Tenant GetTenant(string tenant)
  {
    ...
  }
}

public class ManagementController : Controller
{
  private ITenantStore _tenantStore;

  public ManagementController()
  {
    _tenantStore = new TenantStore();
  }
}

这个代码的缺点有:

1. 所有使用TenantStore类的客户端类都需要负责实例化TenantStore对象。TenantStore类中的特定构造函数会和所有的客户端代码紧密绑定在一起,可能会导致更改TenantStore类的实现,这会让TenantStore类的维护更复杂,更容易出错,并且更耗时。

2. 对ManagementController类运行单元测试,您需要实例化TenantStore对象,测试过程变得更加复杂和脆弱。

我们可以用依赖注入来改良这段代码,让它变得低耦合,代码示例:

public class ManagementController : Controller
{
  private ITenantStore tenantStore;

  public ManagementController(ITenantStore tenantStore)
  {
    this.tenantStore = tenantStore;
  }
}

这个代码的优点:

1. ManagementController类不再负责实例化TenantStore对象。

2. 现在还清楚了控制器对其构造函数参数的依赖性,而不是隐藏在控制器方法实现中。

3. 更加容易测试客户端类(如ManagementController类)的某些行为,您现在可以提供ITenantStore接口的轻量级实现,该实现返回一些示例数据。

4. 我们删除了类之间的直接依赖关系,降低了耦合级别,并有助于提高解决方案的可维护性,可测试性,灵活性和可扩展性。

我们看到了低耦合的好处,但是如果某个类没有直接实例化其所需的其他对象,则其他一些类或组件必须承担此责任。在介绍依赖注入模式之前,我们先看一下低耦合在早期的一个实现方法:工厂模式。代码示例:

public class ManagementController : Controller
{
    private ITenantStore tenantStore;

    public ManagementController()
    {
      var tenantStoreFactory = new TenantStoreFactory();
      this.tenantStore = tenantStoreFactory.CreateTenantStore();
    }
}

简单工厂模式删除了ManagementController类对特定商店实现的直接依赖性,控制器类现在依赖于TenantStoreFactory类来代表它创建实例。但是它有一个特征:客户端通过委托第三方组件来实例化想要的对象,仍然是高级模块在依赖低级模块的表现。只是并不是直接依赖,高级模块直接依赖负责创建对象的工厂。这也意味着高级客户端类的依赖关系隐藏在那些类中,而是在单个位置指定,这使得它们更难以测试。我们称客户端的这种行为是pull模型。下图显示了简单工厂模式中的依赖关系,其中工厂代表ManagementController类实例化TenantStore对象:

依赖注入采用相反的方法,采用push模型代替pull模型。控制反转是一个经常用来描述这种push模型的术语,依赖注入是控制反转的一种特定实现。通过依赖注入,另一个类负责在运行时将依赖项注入(推送)到高级客户端类,例如:

public class ManagementController : Controller
{
  private ITenantStore tenantStore;

  public ManagementController(ITenantStore tenantStore)
  {
    this.tenantStore = tenantStore;
  }
}

正如您在此示例中所看到的,ManagementController构造函数接收ITenantStore实例作为参数,由其他类注入。ManagementContoller类中唯一的依赖项是接口类型。这样做更好,因为它不了解负责实例化ITenantStore对象的类或组件。在下图中,负责实例化TenantStore对象并将其插入ManagementController类的类称为DependencyInjectionContainer类。

图1和图2之间的关键区别在于ManagementController类的依赖关系的方向。在图2中,ManagementController类唯一的依赖是ITenantStore接口。

ManagementController类定义的构造函数,该构造函数需要注入一个ITenantStore类型的对象,应用程序必须在运行时知道它应该实例化ITenantStore接口的哪个实现,然后才能继续实例化ManagementController对象。这里发生了两件事:

1. 如何实例化实现ITenantStore接口的对象。

2. 应用程序中的某些东西实例化该对象和ManagementController对象。

我们称第一项叫Regist,第二项叫Resolve。现在我们使用Unity来实现我们依赖注入,Unity支持编码和配置两种方式Regist。

配置的Regist,作为复杂的应用并不推荐使用,因为最后会形成巨大而复杂的配置文件,及其容易出错。通过编码Regist:

var container = new UnityContainer();

container.RegisterType<ITenantStore, TenantStore>();

这个Regist一个类型的api,但是每次创建一个新的接口都需要手动Regist映射关系也很不方便,所以我们更好的做法是自动注册。它可以最大限度地减少您需要编写的类型注册的数量。您可以指示Unity容器扫描一组程序集,然后根据一组规则自动注册多个映射,而不是单独指定每个类型映射。如果你有很多类型要注册,它将为你节省大量的工作。

创建IOCAutowire的独立类库:

依赖的包:Unity

public class AutowireInfo : IComparable<AutowireInfo>
{
  public AutowireInfo(Type from, Type to)
  {
    this.From = from;
    this.To = to;
    this.Lifetime = AutowireAttribute.DEFAULT_LIFETIME_SCOPE;
    this.Priority = AutowireAttribute.DEFAULT_PRIORITY;
  }
  public Type From { get; set; }
  public Type To { get; set; }
  public string Name { get; set; }
  public Lifetime Lifetime { get; set; }
  public byte Priority { get; set; }
  public int CompareTo(AutowireInfo other)
  {
    return this.Priority.CompareTo(other.Priority);
  }
  public override string ToString()
  {
    return string.Format("{0} > {1}, Prio={2}, Lifetime={3}", From.ToString(), To.ToString(), Priority, Lifetime.ToString());
  }
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class AutowireAttribute : Attribute
{
  public const Lifetime DEFAULT_LIFETIME_SCOPE = Lifetime.Singleton;
  public const byte DEFAULT_PRIORITY = 100;
  public AutowireAttribute(Type from)
  {
    if (from == null)
    {
      throw new ArgumentNullException("from");
    }

    this.From = from;
    this.Lifetime = DEFAULT_LIFETIME_SCOPE;
    this.Priority = DEFAULT_PRIORITY;
  }
  public Type From { get; private set; }
  public string Name { get; set; }
  public Lifetime Lifetime { get; set; }
  public byte Priority { get; set; }
}

public class AutowireRegister
{
  IUnityContainer _container;
  public AutowireRegister(IUnityContainer container)
  {
    _container = container;
  }
  public void AutoRegist()
  {
    var assemblies = GetAssemblies();
    this.AutowireTypes(assemblies);
  }
  private void AutowireTypes(params Assembly[] assemblies)
  {
    if (assemblies == null || assemblies.Length == 0)
    {
      return;
    }
    var autowireList = AutowireHelpers.GetAutowireInfo(assemblies);
    this.AutowireTypes(autowireList);
  }
  private Assembly[] GetAssemblies()
  {
    var results = new List<Assembly>();

    // AppDomain.CurrentDomain.GetAssemblies() does not work here because some DLLs might not be loaded into 
    // the current app domain yet, so enumerate all the DLLs in current app folder
    var assemblyFolder = AppDomain.CurrentDomain.RelativeSearchPath;
    if (string.IsNullOrWhiteSpace(assemblyFolder))
    {
      assemblyFolder = AppDomain.CurrentDomain.BaseDirectory;
    }

    var dllFiles = Directory.GetFiles(assemblyFolder, "*.dll").ToList();
    var exeFiles= Directory.GetFiles(assemblyFolder, "*.exe").ToList();
    dllFiles.AddRange(exeFiles);
    foreach (var file in dllFiles)
    {
      try
      {
        var assembly = Assembly.Load(Path.GetFileNameWithoutExtension(file));

        if (!IsSystemAssembly(assembly))
        {
          results.Add(assembly);
        }
      }
      catch
      {
        // Squash any exceptions
      }
    }
    return results.ToArray();
  }

  private bool IsSystemAssembly(Assembly assembly)
  {
    if (assembly == null
      || assembly.FullName.StartsWith("mscorlib.")
      || assembly.FullName.StartsWith("System.")
      || assembly.FullName.StartsWith("Microsoft."))
    {
      return true;
    }
    return false;
  }

  private void AutowireTypes(IList<AutowireInfo> autowireList)
  {
    if (autowireList == null || autowireList.Count == 0)
    {
      return;
    }
    // Order the registrations descending to allow a lower priority number (50) to overwrite a higher priority number (100)
    var ordered = autowireList.OrderByDescending(a => a.Priority);
    foreach (var info in ordered)
    {
      this._container.RegisterType(
        info.From,
        info.To,
        info.Name,
        CreateLifetimeManager(info.Lifetime)
        );
    }
  }

  private ITypeLifetimeManager CreateLifetimeManager(Lifetime lifetime)
  {
    switch (lifetime)
    {
      case Lifetime.Transient:
        return this._container.Resolve<TransientLifetimeManager>();

      case Lifetime.PerThread:
        return this._container.Resolve<PerThreadLifetimeManager>();

      default:
        // Singleton and default
        return this._container.Resolve<ContainerControlledLifetimeManager>();
    }
  }
}

public enum Lifetime
{
  Transient = 0,
  Singleton = 1,
  PerThread = 2
}

public static class AutowireHelpers
{
  public static IList<AutowireInfo> GetAutowireInfo(params Assembly[] assemblies)
  {
    var results = new List<AutowireInfo>();
    if (assemblies != null)
    {
      foreach (var a in assemblies)
      {
        GetAutowireInfo(a, results);
      }
    }

    return results;
  }

  private static void GetAutowireInfo(Assembly assembly, IList<AutowireInfo> results)
  {
    try
    {
      var attrType = typeof(AutowireAttribute);
      foreach (var type in assembly.GetTypes())
      {
        var attrs = type.GetCustomAttributes(attrType, inherit: false);

        if (attrs.Length > 0)
        {
          foreach (AutowireAttribute attr in attrs)
          {
            results.Add(new AutowireInfo(attr.From, type)
            {
              Name = attr.Name,
              Lifetime = attr.Lifetime,
              Priority = attr.Priority
            });
          }
        }
      }
    }
    catch (ReflectionTypeLoadException ex)
    {
      // Ignore exceptions if assembly types could not be loaded
    }
  }
}

public static class UnityContainerFactory
{
  public static IUnityContainer CreateContainer()
  {
    UnityContainer container = new UnityContainer();
    var autowireRegister = container.Resolve<AutowireRegister>();
    autowireRegister.AutoRegist();
    return container;
  }
}

创建引用IOCAutowire类库的示例代码:

public interface ILogger
{
  void Write(string message);
}

public interface ITest
{
}
public interface ITest2
{
}

[Autowire(typeof(ILogger), Name = "Text")]
public class TextLogger : ILogger
{
  public void Write(string message)
  {
    Console.WriteLine("Text Logger:" + message);
  }
}

[Autowire(typeof(ILogger), Name = "Data", Lifetime = Lifetime.Transient)]
public class DataLogger : ILogger
{
  ITest _test;
  string _name;

  public DataLogger(ITest test, string name = "123")
  {
    _test = test;
    _name = name;
  }

  public void Write(string message)
  {
    Console.WriteLine("Data Logger, name is " + _name + "; message is " + message);
  }
}

[Autowire(typeof(ITest))]
public class Test : ITest
{
  public Test(ITest2 test2)
  {
  }
}

[Autowire(typeof(ITest2))]
public class Test2 : ITest2
{
}

在Console应用中的使用情况:

static void Main(string[] args)
{
    var container = UnityContainerFactory.CreateContainer();
    
    ITest test = container.Resolve<ITest>();

    ILogger logger = container.Resolve<ILogger>("Text");
    logger.Write("hello Unity");

    logger = container.Resolve<ILogger>("Data");
    logger.Write("hello Unity");

    logger = container.Resolve<ILogger>("Data", new ParameterOverride("name", "456"));
    logger.Write("hello Unity");

    //查看容器中所有的类型注册信息
    foreach (IContainerRegistration item in container.Registrations)
    {
      Console.WriteLine(item.RegisteredType.ToString() 
        + ":" + item.MappedToType.ToString());
    }

    Console.ReadKey();
}

在MVC应用中的使用情况:

依赖的包:Unity,Unity.Mvc

public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
    AreaRegistration.RegisterAllAreas();
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    DependencyResolver.SetResolver(new UnityDependencyResolver(UnityConfig.Container));
  }

  protected void Application_End()
  {
    UnityConfig.Container.Dispose();
  }
}

public static class UnityConfig
{
  private static Lazy<IUnityContainer> container =
    new Lazy<IUnityContainer>(() =>
    {
      var container = UnityContainerFactory.CreateContainer();
      return container;
    });
    
  public static IUnityContainer Container => container.Value;
}

public class HomeController : Controller
{
  private ITest _test;

  public HomeController(ITest test)
  {
    _test = test;
  }
}

在WebApi应用中的使用情况:

依赖的包:Unity,Unity.AspNet.WebApi

protected void Application_Start()
{
  GlobalConfiguration.Configure(WebApiConfig.Register);
  FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
}

public static class UnityConfig
{
  private static Lazy<IUnityContainer> container =
    new Lazy<IUnityContainer>(() =>
    {
      var container = UnityContainerFactory.CreateContainer();
      return container;
    });

  public static IUnityContainer Container => container.Value;
}

public static class UnityWebApiActivator
{
  public static void Start()
  {
    var resolver = new UnityDependencyResolver(UnityConfig.Container);
    GlobalConfiguration.Configuration.DependencyResolver = resolver;
  }

  public static void Shutdown()
  {
    UnityConfig.Container.Dispose();
  }
}

本文分享自微信公众号 - 明丰随笔(liumingfengwx2),作者:刘明丰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浅谈.Net反射 11

    浅谈.Net反射系列基本来到了尾声,本文主要从.Net Framework的源码角度去分析:

    小蜜蜂
  • 浅谈.Net反射 4

    System.Reflection命名空间下的Assembly类型,代表了一个程序集,并包含了关于程序集的信息。

    小蜜蜂
  • AOP编程

    Aspect Oriented Programming(AOP),面向切面编程。AOP主要解决的问题是针对业务处理过程中对一些逻辑进行切面提取,它可以分散在处理...

    小蜜蜂
  • 转载 Java设计模式

    设计模式; 一个程序员对设计模式的理解: “不懂”为什么要把很简单的东西搞得那么复杂。后来随着软件开发经验的增加才开始明白我所看到的“复杂”恰恰就是设计模式的精...

    用户1518699
  • 23种设计模式详解(三)

    模板方法模式就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类...

    南风
  • Java 接口

    但如果三角形类不需要draw()方法,只能去掉图形类中的draw()或把draw()变成图形类私有

    用户2965768
  • Java内功心法,行为型设计模式

    使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。 将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

    李红
  • 23种设计模式详解(三)

    南风
  • Java内功心法,行为型设计模式

    使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。 将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

    李红
  • 温故而知新:设计模式之抽象工厂(AbstractFactory)

    抽象工厂主要用来解决多个系列的对象实例问题。 问题的应用场景(来源于李建忠老师的webcast讲座): 如果有一款游戏,里面有"道路,房屋,隧道,丛林"这四类基...

    菩提树下的杨过

扫码关注云+社区

领取腾讯云代金券