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 中,通过为相同契约 (Contract Type) 提供多个不同实现,并将这些实现分别注册到容器中,同时结合 My.Ioc 的条件绑定和元数据功能,我们也可以方便地构建起一棵树。请看下面的示例代码:

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);
            }
        }
    }
}
View Code

请注意,与前一种方式相比,以这种方式来构建树的思想实际上是有所不同的。从本质上来说,这是一种通过增加(派生)类型来实现复用的手段,而前一种方式是通过增加对象实例来实现复用,这一点我们前面说过了。与前一种复用相比,这种复用有一个缺点,那就是当我们将这些类型注册到容器中时,My.Ioc 会为每个派生类型生成一个注册项,而每生成一个注册项时都会附带生成并缓存一大堆中间类(比如 ObjectBuilder/Lifetime/Injector/DependencyProvider 等),这无疑会多耗用一些内存。而前一种复用方式则无此弊病,因为它只是简单增加对象实例而已,这也是我在本文开头时说“不大妥当”的原因。

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

 

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

posted @ 2014-09-09 11:15  Johnny.Liu  阅读(336)  评论(0编辑  收藏  举报