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

本文的目的在于通过创建一棵插件树来演示条件绑定和元数据的用法。

说“插件树”也许是不大妥当的,因为在一般观念中,谈到插件树,我们很容易会想到 Winform/Wpf 中的菜单。举例来说,如果要在 Winform 中创建一个菜单,我们使用类似如下代码:

// Create File menu
var newMenu = new ToolStripMenuItem();

var localProjectMenu = new ToolStripMenuItem();
var remoteProjectMenu = new ToolStripMenuItem();
var openMenu = new ToolStripMenuItem();
openMenu.DropDownItems.AddRange(new ToolStripItem[] { localProjectMenu, remoteProjectMenu});

var fileMenu = new ToolStripMenuItem();
fileMenu.DropDownItems.AddRange(new ToolStripItem[] { openMenu, newMenu });
fileMenu.Size = new System.Drawing.Size(39, 21);
fileMenu.Text = "&File";

// Create Edit menu
var undoMenu = new ToolStripMenuItem();
var redoMenu = new ToolStripMenuItem();

var editMenu = new ToolStripMenuItem();
editMenu.DropDownItems.AddRange(new ToolStripItem[] { undoMenu, redoMenu});

// Create MenuStrip
var menuStrip = new MenuStrip();
menuStrip.Items.AddRange(new ToolStripItem[] { fileMenu, editMenu});

这样创建出来,就是一个类似如下结构的菜单树:

在上述示例中,我们通过分别创建各个菜单(并为各个菜单指定不同的属性,例如 Text/Size),然后将这些菜单插入对应的上级菜单或 MenuStrip 中,最后组装成一棵菜单树。换句话说,在这里我们复用的是 ToolStripMenuItem/ToolStripItem 等等这些类。

使用 My.Ioc 的条件绑定功能,我们也可以方便地构建起一棵树。但在 My.Ioc 中,这是通过注册项 (Registration) 的复用而非类的复用来实现的。请看下面的示例代码:

using System;
using System.Collections.Generic;
using My.Ioc;

namespace TreeBuilder
{
    #region Tree/Node Types

    public interface INode
    {
        string ParentName { get; set; }
        string Name { get; }
        IEnumerable<INode> ChildNodes { get; }
    }

    public abstract class Node : INode
    {
        string _name;

        public string ParentName { get; set; }
        public string Name
        {
            get
            {
                _name = _name ?? GetType().Name;
                return _name;
            }
        }

        public virtual IEnumerable<INode> ChildNodes
        {
            get { return null; }
        }
    }

    #region Level 1

    public class Tree
    {
        readonly string _name;
        readonly IEnumerable<INode> _childNodes;

        public Tree(IEnumerable<INode> childNodes)
        {
            if (childNodes == null)
                throw new ArgumentException();
            _name = GetType().Name;
            foreach (var childNode in childNodes)
                childNode.ParentName = _name;
            _childNodes = childNodes;
        }

        public string Name
        {
            get { return _name; }
        }

        public IEnumerable<INode> ChildNodes
        {
            get { return _childNodes; }
        }
    }

    #endregion

    #region Level 2

    public abstract class CompositeNode : Node
    {
        readonly IEnumerable<INode> _childNodes;

        protected CompositeNode(IEnumerable<INode> childNodes)
        {
            if (childNodes == null)
                throw new ArgumentException();
            foreach (var childNode in childNodes)
                childNode.ParentName = Name;
            _childNodes = childNodes;
        }

        public override IEnumerable<INode> ChildNodes
        {
            get { return _childNodes; }
        }
    }

    public class ListNode : CompositeNode
    {
        public ListNode(List<INode> childNodes)
            : base(childNodes)
        {
        }
    }

    public class ArrayNode : CompositeNode
    {
        public ArrayNode(INode[] childNodes)
            : base(childNodes)
        {
        }
    }

    #endregion

    #region Level 3

    public class ChildNode1 : Node
    {
    }

    public class ChildNode2 : Node
    {
    }

    public class ChildNode3 : Node
    {
    }

    public class ChildNode4 : Node
    {
    }

    public class ChildNode5 : Node
    {
    }

    public class ChildNode6 : Node
    {
    }

    public class ChildNode7 : Node
    {
    }

    public class ChildNode8 : Node
    {
    }

    #endregion

    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            IObjectContainer container = new ObjectContainer(false);
            Register(container);

            // Try to get an observer
            IObjectObserver<Tree> treeObserver;
            if (!container.TryGetObserver(out treeObserver))
                throw new InvalidOperationException();

            // Resolve the tree using the observer
            var tree = container.Resolve(treeObserver);
            //var tree = container.Resolve<Tree>();
            PrintTreeMembers(tree, 1);

            // Add more nodes at runtime
            container.Register(typeof(INode), typeof(ChildNode8))
                .WhenParentMetadata((mata) => string.Equals("ArrayNode", mata));
            container.Register<INode, ChildNode7>()
                .WhenParentTypeIs<ListNode>();

            // Commit the registrations to the registry as usual.
            container.CommitRegistrations();

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine();

            // Resolve the tree again
            tree = container.Resolve(treeObserver);
            //tree = container.Resolve<Tree>();
            PrintTreeMembers(tree, 2);

            Console.ReadLine();
        }

        static void Register(IObjectContainer container)
        {
            container.Register<Tree>();

            container.Register<INode, ArrayNode>()
                .WhenParentTypeIs<Tree>()
                .Set("ArrayNode");
            container.Register<INode, ListNode>()
                .WhenParentTypeIs<Tree>()
                .Set("ListNode");

            #region Inject into ArrayNode

            container.Register(typeof(INode), typeof(ChildNode1))
                .WhenParentMetadata((mata) => string.Equals("ArrayNode", mata));

            container.Register<INode, ChildNode2>()
                .WhenParentTypeIs<ArrayNode>();

            container.Register<INode, ChildNode3>()
                .WhenParentTypeIs<ArrayNode>();

            container.Register<INode, ChildNode4>()
                .WhenParentTypeIs<ArrayNode>();

            #endregion

            #region Inject into ListNode

            container.Register<INode, ChildNode5>()
                .WhenParentTypeIs<ListNode>();

            container.Register<INode, ChildNode6>()
                .WhenParentTypeIs<ListNode>();

            #endregion

            // Commit the registrations to the registry.
            container.CommitRegistrations();
        }

        static void PrintTreeMembers(Tree tree, int time)
        {
            if (tree == null)
                throw new ArgumentException();

            Console.WriteLine(time);
            Console.WriteLine("=================================================");

            Console.WriteLine(tree.Name);

            // Breadth first traversal
            var allNodes = new List<INode>();
            if (tree.ChildNodes == null)
                return;
            allNodes.AddRange(tree.ChildNodes);
            for (int i = 0; i < allNodes.Count; i++)
            {
                var node = allNodes[i];
                Console.WriteLine(node.ParentName + "/" + node.Name);
                if (node.ChildNodes != null)
                    allNodes.AddRange(node.ChildNodes);
            }
        }
    }
}

与复用类相比,注册项的复用有一个缺点,那就是 My.Ioc 每生成一个注册项时都会生成并缓存一大堆中间类(比如 ObjectBuilder/Lifetime/Injector/DependencyProvider 等),而通过类复用则无此弊病,这也是我在本文开头时说“不大妥当”的原因。

但不管怎么说,恰如我们在文章开头提到的,我们的目的是阐述条件绑定和元数据的用法,通过这个示例,我们很好地实现了这个目的。至于如何更好地使用条件绑定和元数据的功能,留给各位自己去发挥好了。

源码可在此处下载,压缩包中包含了 My.Ioc 框架的源码和本示例以及其他一些示例的源码。

时间: 2024-11-05 20:48:55

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 这两个事件来实现.但是,使用这两个事件也有一些不足.首先,它们只能针对当前注册/注销的服务发出通知,而对于依赖当前服务的上层服务的激活/停用事件(由于当前服务的注册/注销而引起的),它们则无能为力:其次,这两者都是针对所有注册项的广播事件.也就是说,只要发生注册/注销,无论注册/注销的是什么服务,容器都会向所有订阅了这两个事

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 代码示例——谈一谈如何实现装饰器 (Decorator) 模式,兼谈如何扩展 My.Ioc

装饰器模式体现了一种“组合优于继承”的思想.当我们要动态为对象增加新功能时,装饰器模式往往是我们的好帮手. 很多后期出现的 Ioc 容器都为装饰器模式提供了支持,比如说 Autofac.在 My.Ioc 中,默认不提供装饰器支持,但我们可以自己进行扩展,以提供此项功能. using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using My.Ioc; u

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

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

代码示例:一些简单技巧优化JavaScript编译器工作详解,让你写出高性能运行的更快JavaScript代码

告诉你一些简单的技巧来优化JavaScript编译器工作,从而让你的JavaScript代码运行的更快.尤其是在你游戏中发现帧率下降或是当垃圾回收器有大量的工作要完成的时候. 单一同态: 当你定义了一个两个参数的函数,编译器会接受你的定义,如果函数参数的类型.个数或者返回值的类型改变编译器的工作会变得艰难.通常情况下,单一同态的数据结构和个数相同的参数会让你的程序会更好的工作. function example(a, b) { // 期望a,b都为数值类型 console.log(++a * +

集算器实现外键功能的代码示例

如果字段或字段组合k是表M的主键,而k也同时存在于表B,则k就是B的外键.外键维系着表之间的关联关系,是结构化数据计算最重要的概念之一.集算器可以通过对象引用方便地实现外键功能,下面用几个例子来说明:    例1 关联主子表1:    订单表(order)是子表,员工表(emp)是主表,请将emp关联到order中,显示emp中的Name.Gender.Salary字段以及order中的OrderID,Amount字段. 说明:本例只用到emp和order,之后的例子还要用到部门表(dep),三