本文的目的在于通过创建一棵插件树来演示条件绑定和元数据的用法。
说“插件树”也许是不大妥当的,因为在一般观念中,谈到插件树,我们很容易会想到 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