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); } } } }
请注意,与前一种方式相比,以这种方式来构建树的思想实际上是有所不同的。从本质上来说,这是一种通过增加(派生)类型来实现复用的手段,而前一种方式是通过增加对象实例来实现复用,这一点我们前面说过了。与前一种复用相比,这种复用有一个缺点,那就是当我们将这些类型注册到容器中时,My.Ioc 会为每个派生类型生成一个注册项,而每生成一个注册项时都会附带生成并缓存一大堆中间类(比如 ObjectBuilder/Lifetime/Injector/DependencyProvider 等),这无疑会多耗用一些内存。而前一种复用方式则无此弊病,因为它只是简单增加对象实例而已,这也是我在本文开头时说“不大妥当”的原因。
尽管如此,恰如我们在文章开头提到的,我们的目的是阐述条件绑定和元数据的用法,通过这个示例,我们达到了这个目的。至于如何更好地使用条件绑定和元数据的功能,留给各位自己去发挥好了。
源码可在此处下载,压缩包中包含了 My.Ioc 框架的源码和本示例以及其他一些示例的源码。