My.Ioc 代码示例——谈一谈如何实现装饰器 (Decorator) 模式,兼谈如何扩展 My.Ioc

装饰器模式体现了一种“组合优于继承”的思想。当我们要动态为对象增加新功能时,装饰器模式往往是我们的好帮手。

很多后期出现的 Ioc 容器都为装饰器模式提供了支持,比如说 Autofac。在 My.Ioc 中,默认不提供装饰器支持,但我们可以自己进行扩展,以提供此项功能。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using My.Ioc;
using My.Ioc.Configuration.FluentApi;
using My.Ioc.Configuration.Injection;
using My.Ioc.Core;
using My.Ioc.Dependencies;
using My.Ioc.Injection;

namespace ImplementDecorator
{
    #region Extensions

    public class ReflectionDecoratorConstructorInjector<T> : Injector<T>
    {
        readonly ConstructorInfo _constructorInfo;

        public ReflectionDecoratorConstructorInjector(ConstructorInfo constructorInfo)
        {
            _constructorInfo = constructorInfo;
        }

        public override void Execute(InjectionContext<T> context)
        {
            var instance = _constructorInfo.Invoke(new object[] {context.Instance});
            InjectInstanceIntoContext(context, (T)instance);
        }
    }

    public class ReflectionDecoratorInjectionConfigurationInterpreter : IInjectionConfigurationInterpreter
    {
        readonly TypedInjectionConfigurationGroup _configGroup;

        public ReflectionDecoratorInjectionConfigurationInterpreter(TypedInjectionConfigurationGroup configGroup)
        {
            _configGroup = configGroup;
        }

        public InjectionConfigurationGroup InjectionConfigurationGroup
        {
            get { return _configGroup; }
        }

        public Injector<T> Parse<T>(Kernel kernel, ObjectDescription description, out List<DependencyProvider> groupDepProviders)
        {
            var ctorConfigItem = _configGroup.ConstructorInjectionConfigurationItem;
            groupDepProviders = null;
            return new ReflectionDecoratorConstructorInjector<T>(ctorConfigItem.Constructor);
        }
    }

    public static class DecoratorApi
    {
        public static ICommonConfigurationApi ApplyDecorator(this ICommonConfigurationApi typeConfig, Type decoratedType)
        {
            if (typeConfig == null)
                throw new ArgumentException();
            if (decoratedType == null)
                throw new ArgumentException();

            var configSet = typeConfig.InjectionConfigurationSet;

            var description = configSet.ObjectDescription;
            if (!description.ContractType.IsAssignableFrom(decoratedType))
                throw new ArgumentException();

            var ctor = decoratedType.GetConstructor(new Type[] { typeof(OriginalService) });
            //var parameters = new PositionalParameterSet(Parameter.Positional<Service>());
            //var option = typeConfig.Kernel.ContainerOption;
            //var ctor = option.ConstructorSelector.SelectConstructor(decoratedType, parameters);

            IConstructorInjectionConfigurationItem ctorConfigItem = new ConstructorInjectionConfigurationItem(ctor, null);
            var configGroup = new TypedInjectionConfigurationGroup(description, ctorConfigItem);
            configSet.AddCustomInjectionConfigurationGroup(configGroup);
            configGroup.InjectionConfigurationInterpreter = new ReflectionDecoratorInjectionConfigurationInterpreter(configGroup);
            return typeConfig;
        }
    }

    #endregion

    #region Test Types

    public interface IService
    {
        void Execute();
    }

    public class OriginalService : IService
    {
        public void Execute()
        {
            Console.WriteLine("Service execute...");
        }
    }

    public class DecoratedService : IService
    {
        readonly IService _innerService;

        public DecoratedService(IService innerService)
        {
            _innerService = innerService;
        }

        public void Execute()
        {
            Log();
            _innerService.Execute();
        }

        static void Log()
        {
            Console.WriteLine("DecoratedService execute...");
        }
    }

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            var container = new ObjectContainer(false);
            container.Register<IService, OriginalService>()
                .ApplyDecorator(typeof(DecoratedService))
                .In(Lifetime.Container());

            container.CommitRegistrations();

            var service = container.Resolve<IService>();
            Debug.Assert(service != null);
            Debug.Assert(service is DecoratedService);

            var service2 = container.Resolve<IService>();
            Debug.Assert(service == service2);

            Console.WriteLine("The ImplementDecorator success...");
            Console.ReadLine();
        }
    }
}

在上面的示例代码中,我们设计了两个服务类:OriginalService 和 DecoratedService,它们共同实现一个契约 IService。其中,OriginalService 就是原始服务类,而 DecoratedService 就是经过装饰之后的服务类,这从命名上也很容易看出来。接下来,我们要介绍的就是如何在 My.Ioc 中给 OriginalService 增加装饰器。

要实现这个目的,首先要了解 My.Ioc 如何构造对象。如果您对 My.Ioc 的内部实现细节不感兴趣,请直接前往这里。

My.Ioc 如何构造对象

在这一部分,我们来介绍一下 My.Ioc 的内部对象构造过程。通过下面一张图,可以帮助我们大致了解 My.Ioc 的对象构造过程:

在 My.Ioc 中,当我们调用 container.Resolve 时,容器将为我们完成一系列子对象构造。看到这里,有人可能要问了,为什么是构造一系列子对象而不是构造一个对象呢?这是因为我们预留了这样的可能性(即可扩展性):当我们向容器请求某个服务时,容器返回给我们的不一定是最初注册的对象,而可能是经过某种修饰或增强的、实现了相同契约的对象。

以上面的示例来说,我们最初注册到容器中的服务是 OriginalService,但之后我们为其增加了一个装饰器 DecoratedService,因此容器最后返回给我们的应该是一个 DecoratedService 对象,而不是最初注册的 OriginalService 对象。如此一来,这里要创建的子对象便有两个了:首先需要创建一个 OriginalService 对象,然后以该对象为参数创建一个 DecoratedService 对象。

请注意,我们将容器在最终返回结果之前所创建的一系列中间对象称为子对象,也就是上图中的对象1、对象2、对象3...等等。

在创建每一个子对象时,我们还考虑到用户可能需要为其公共属性赋予一个属性值(即属性注入),或者调用其公共方法(即方法注入)。因此,容器除了调用子对象构造函数(即构造函数注入)以创建子对象实例之外,可能还要在创建好子对象实例之后调用其成员方法,或者调用其公共属性的 set 方法以便给公共属性赋值。

经过这样分析,上面这张图也不难理解了。当我们在容器中注册某个服务时,如果我们没有指定在创建好对象之后接着调用对象的任何公共方法(方法注入)或属性 set 方法(属性注入),也没有为该服务指定其他装饰或增强类型,那么当我们向容器请求该服务时(调用 container.Resolve),容器将直接调用该服务的构造函数以创建一个服务对象,并将其返回给我们。如果我们在注册时指定了要注入的属性或方法,那么容器除了创建服务对象之外,还会调用指定的公共方法或属性 set 方法。如果我们在注册时,还指定了某些装饰或增强类型,那么容器不但会创建最初的服务对象本身,还会创建指定装饰或增强类型的实例,并且最终将后者返回给我们。

在设计上,我们采用 [配置 + 解释] 的方式来表示 My.Ioc 的对象创建过程。所谓“配置”是指我们将每个子对象的构造(包括构造函数注入、属性注入、方法注入等)分解成一个个配置项,通过组合所有这些配置项来组成一个子对象创建过程。所谓“解释”是指当我们获得每个子对象的所有配置项之后,我们用一个解释程序将这些配置转换成实际的子对象创建器(在 My.Ioc 中对应的对象是 Injector)。最后,当我们拥有一个个子对象创建器之后,我们再将其组装成一个子对象链创建过程。

在实现上,我们采用管道 (Pipeline) 模式来表示整个配置过程。管道中每一个节点对应一个子对象创建配置(在 My.Ioc 框架中,我们称为注入配置组,用 InjectionConfigurationGroup 来表示),所有节点组合到一起就是一个子对象链创建配置(称为注入配置集,用 InjectionConfigurationSet 来表示)。每一个节点(即 InjectionConfigurationGroup)又分为两个子节点:子对象构造函数配置项(对应 IConstructorInjectionConfigurationItem)+ 子对象成员方法和属性 set 方法配置项(对应 IMemberInjectionConfigurationItem)。其中,第一个节点的第一个子节点是必需的,其他部分都是可选的。

有了上面这些知识作为铺垫,接下来要回答如何为 My.Ioc 添加装饰器支持,甚至如何扩展 My.Ioc 的问题,也就不难了。

如何实现装饰器模式

要为服务添加一个装饰器,首先我们必须在服务的配置集 (InjectionConfigurationSet) 中增加一个配置组 (InjectionConfigurationGroup),并为该配置组提供一个构造函数配置项 (IConstructorInjectionConfigurationItem)。此外,我们还需要为该配置组提供一个解释程序(示例代码中的 ReflectionDecoratorInjectionConfigurationInterpreter)。在这个解释程序中,我们需要定义如何将这个配置组转换成一个子对象创建器(在 Parse 方法中定义)。然后,我们便可以将该配置节点添加到服务的配置集中。这样,当我们调用 container.Resolve 方法时,容器便会向我们返回所期望的结果。

添加扩展方法

当实现过程了然之后,我们可以为这个过程编写一个扩展方法,以方便后续使用,如示例代码中所示。

本文示例代码以及 My.Ioc 框架源码可在此处获取。

时间: 2024-10-10 14:16:29

My.Ioc 代码示例——谈一谈如何实现装饰器 (Decorator) 模式,兼谈如何扩展 My.Ioc的相关文章

My.Ioc 代码示例——使用默认构造参数和覆盖参数

在 Ioc 世界中,有些框架(例如 Autofac/NInject/Unity)支持传递默认参数,有些框架(例如 SimpleInjector/LightInjector 等)则不支持.作为 My.Ioc 来说,我们支持默认参数. 当我们在 My.Ioc 中注册对象时,有些对象类型 (System.Type) 要求我们必须提供默认参数,而有些则不是必要的.在 My.Ioc 中,默认参数有两个作用:1. 容器根据默认参数来选择用于构建对象的构造函数.而一旦选定构造函数之后,今后容器便会一直使用该构

My.Ioc 代码示例——使用 Observer 机制捕获注册项 (Registration) 状态的变化

在 My.Ioc 中,要想在服务注销/注册时获得通知,可以通过订阅 ObjectBuilderRegistered 和 ObjectBuilderUnregistering 这两个事件来实现.但是,使用这两个事件也有一些不足.首先,它们只能针对当前注册/注销的服务发出通知,而对于依赖当前服务的上层服务的激活/停用事件(由于当前服务的注册/注销而引起的),它们则无能为力:其次,这两者都是针对所有注册项的广播事件.也就是说,只要发生注册/注销,无论注册/注销的是什么服务,容器都会向所有订阅了这两个事

就谈个py 的装饰器 decorator

很早很早就知道有这么个 装饰器的东西,叫的非常神秘. 包括c#  和 java 中都有这个东西, c#中叫做attribut 特性,java中叫做Annotation 注解,在偷偷学习c#教程的时候,我承认我 看了盗版的视频,上边的讲师一带而过,说过这个东西,所以我觉得这个东西很是高达上,这里不说这两个语言,就单说python python中实现这个 很简单,当然如果支持这种语法的话,方式是一样的. 很多程序员就喜欢把简单的东西弄复杂了,例如我,说了 这么一大堆,还没到正题,只是我要讲起来这个东

My.Ioc 代码示例——使用条件绑定和元数据(可选)构建插件树

本文的目的在于通过创建一棵插件树来演示条件绑定和元数据的用法. 说“插件树”也许是不大妥当的,因为在一般观念中,谈到插件树,我们很容易会想到 Winform/Wpf 中的菜单.举例来说,如果要在 Winform 中创建一个菜单,我们使用类似如下代码: // Create File menu var newMenu = new ToolStripMenuItem(); var localProjectMenu = new ToolStripMenuItem(); var remoteProject

My.Ioc 代码示例——利用 ObjectBuilderRequested 事件实现延迟注册

在使用 Ioc 框架时,一般我们建议集中在一个称为 Composition Root(其含义请参见下面的小注)的位置来注册 (Register) 和解析 (Resolve) 服务.该做法的目的在于通过限制 Ioc 的使用场合,尽量减少应用程序本身对于 Ioc 框架的依赖. 这种模式固然能够很好地解耦应用程序和 Ioc 框架,使我们能够在需要的时候方便地更换 Ioc 框架,但它同时也带来了一个问题:难道我们一定要在程序启动时注册所有服务吗?有些服务并不一定会马上用到,有一些服务甚至可能不会用到.在

My.Ioc 代码示例——Lifetime 和 ILifetimeScope

很多 Ioc 框架在创建对象的过程中,都会采取某种方式来缓存/复用/释放已构建的对象.在 My.Ioc 中,这个目的是通过 Lifetime/ILifetimeScope 来实现的.其中,Lifetime 实现了缓存/复用对象的功能,ILifetimeScope 则实现了复用/释放对象的功能. My.Ioc 默认提供了三种 Lifetime:ContainerLifetime.TransientLifetime 和 ScopeLifetime.这里简单解释一下它们的含义:ContainerLif

My.Ioc 代码示例——避免循环依赖

本文的目的在于通过一些示例,向用户说明 My.Ioc 支持哪些类型的依赖关系.也就是说,如何设计对象不会导致循环依赖. 在 Ioc 世界中,循环依赖是一个顽敌.这不仅因为它会导致 Ioc 容器抛出异常,而且还因为它是不可预知的,尽管通过仔细的配置是可以尽量避免这个问题的. 当用户在 Ioc 容器中注册对象时,他们事先并不知道该对象与其他对象之间的依赖关系,因为依赖关系是由 Ioc 容器管理的.这种依赖关系要等到用户首次调用 container.Resolve(contractType) 时才能确

My.Ioc 代码示例——注册项 (Registration) 的注销和更新

当您需要从 Ioc 容器中注销/删除一个注册项的时候,您会怎么做呢? 有人曾经在 stackoverflow 上提问“如何从 Unity 中注销一个注册项”,对于这个问题,有人的回答是“有趣.你为什么要这样做?”,也有人提出了一些变通的解决办法,例如通过自定义 LifetimeManager 来实现等等.这些其实都不是根本的解决办法.因为服务的注册/注销本身是一个容器级过程,当中一定会涉及到一些中间对象的创建/清理 (Dispose) 以及不同对象之间的协调,本应由容器来提供支持才对. 遗憾的是

浅谈Python装饰器

一.概念 装饰器是Python语言中的高级语法.主要的功能是对一个函数.方法.或者类进行加工,作用是为已经存在的对象添加额外的功能,提升代码的可读性.装饰器是设计模式的一种,被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.在实际应用中也经常会用到装饰器.这篇文章来简单谈一下装饰器的实现方式. 二.闭包 因为装饰器是基于闭包来实现的,这里简单说下Python的闭包.看下面的代码: def outer(): var = 3 def inner(): print(var) retu