前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >IOC编程

IOC编程

作者头像
小蜜蜂
发布2019-07-15 16:04:05
6400
发布2019-07-15 16:04:05
举报
文章被收录于专栏:明丰随笔

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

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

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

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

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

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

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

代码语言:javascript
复制
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对象,测试过程变得更加复杂和脆弱。

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

代码语言:javascript
复制
public class ManagementController : Controller
{
  private ITenantStore tenantStore;

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

这个代码的优点:

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

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

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

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

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

代码语言:javascript
复制
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模型的术语,依赖注入是控制反转的一种特定实现。通过依赖注入,另一个类负责在运行时将依赖项注入(推送)到高级客户端类,例如:

代码语言:javascript
复制
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

代码语言:javascript
复制
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类库的示例代码:

代码语言:javascript
复制
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应用中的使用情况:

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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();
  }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 明丰随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档