zoukankan      html  css  js  c++  java
  • 插件框架实现

    一步步实现自己的框架系列:插件框架实现

     不好意思各位同学,本系列文章更新比较慢,因为我也要工作,况且还需要抽出时间编码验证理论,当然找借口总归是不好的,我们都是人,需要休息与娱乐嘛。

      其实.net平台已经有自己的插件框架,比如MEF,MAF这些都是.net自带的框架,前者注重灵活,后者注重物理隔离。不过这不是今天的重点,今天的重点是做我们自己的框架。

      第一步:插件模型设计

      既然是插件框架就会有插件,就会有放插件的地方,我们就需要设计插件容器,这样既可以灵活的管理插件,也使代码的层次结构更加清晰,图示紫色部分是插件与插件容器部分,外边蓝色的就是我们需要使用插件的拥有者,我发现一张图片的效果远比一堆庸俗的文字效果来的更直接,所以如果能用图表达的地方我尽量用图去表达。

      第二步:接口设计

        既然是插件我们如何标识我们的插件呢,看看MEF的导出设计,

    [Export]

    public class Part

    {

    }

    这里我们借来用下,没错,就是使用特性来标识我们导出的插件,既然这种设计这么优秀我们为什么不用呢?

    单部件特性设计

    复制代码
    namespace GL.Core
    {
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
        public class SinglePartAttribute : Attribute
        {
    
        }
    }
    复制代码

    多部件特性设计

    复制代码
    namespace GL.Core
    {
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
        public class MultiplePartAttribute : Attribute
        {
    
        }
    }
    复制代码

      为什么会设计两个特性呢?我们在使用一些功能时,比如设备管理,同一个客户端实例只需要一个设备管理的插件就够了,尽管我们可以写很多,但同一时刻我们只需要一个实例去管理就可以了,那多部件呢,比如我们客户端是一个图片效果处理程序,同一时刻,我们既可以使用图片灰度功能,也可以使用图片锐化功能,也可以使用负片功能等等,那么这种设计是合理的。
      既然是插件,那么插件的描述信息必不可少,那么怎么设计我们的插件描述信息呢——特性,这里也使用特性,

    比如这样定义我们的插件,我们既可以知道这是一个插件,又能获取插件的描述信息,简洁明了。

    复制代码
    [Single]
    [PartMetadata("Name","PartManage")]
    [PartMetadata("Author","GL")]
    public class PartManage
    {
    
    }
    复制代码

    插件元数据描述

    复制代码
    namespace GL.Core
    {
        public class PartMetadata : Attribute
        {
            public string Name { get; private set; }
    
            public object Value { get; private set; }
    
            public PartMetadata(string name, object value)
            {
                this.Name = name;
                this.Value = value;
            }
        }
    }
    复制代码

      为了方便在内存中操作我们的插件,为插件设计一个信息描述实体是必须的,当然,根据实际需要大家可以自己添加或删除一些描述信息,为方便扩展插件描述这里给出了描述的接口。

    复制代码
    namespace GL.Core
    {
        public interface IPartInformation
    {
    /// <summary> /// 插件名称 /// </summary> string PartName { get; } /// <summary> /// 插件版本 /// </summary> string PartVersion { get; } /// <summary> /// 插件描述 /// </summary> [DefaultValue("")] string PartDescription { get; } /// <summary> /// 插件类型 /// </summary> Type PartType { get; } /// <summary> /// 依赖的插件类型 /// </summary> [DefaultValue(null)] Type[] PartDependencies { get; } /// <summary> /// 插件作者 /// </summary> [DefaultValue("")] string PartAuthor { get; } /// <summary> /// 最后修改日期 /// </summary> [DefaultValue("")] string LastModifiedDate { get; } /// <summary> /// 备注 /// </summary> [DefaultValue("")] string Comment { get; } } }
    复制代码

      有时候某些插件我们不希望启用,或者多插件版本切换的时候,我又不想每次都替换那些Dll,毕竟那是些麻烦又无聊的事情,这时候我就的给我们的每一个插件添加一个配置项信息了,想启用什么或者禁用什么改改配置文件就好了(当然,这工作交给小弟就好了,或者弄个配置文件修改的工具,随便一个人就能切换了)。
    没想到还能为插件配置什么,就一个是否启用

    复制代码
    namespace GL.Core
    {
        public class IPartConfigration
        {
            public string PartType { get; set; }
            public bool IsEnable { get; set; }
    
            public IPartConfigration() { }
            
            public IPartConfigration(string partType, bool isEnable)
            {
                this.PartType = partType;
                this.IsEnable = isEnable;
            }
        }
    }
    复制代码

      跟插件相关的就差不多了,下面来设计下我们的插件容器吧,所谓容器就是能放东西的东西,我感觉上句话包括这句其实是废话,呵呵。既然是插件容器就得能放插件,没有接口设计的类,让人理解起来总是那么费劲,首先来看下我们插件容器接口的设计:

    复制代码
    namespace GL.Core
    {
        public interface IGLPartContainer
        {
            /// <summary>
            /// 单实例插件
            /// </summary>
            IList<Type> SingleParts { get; }
    
            /// <summary>
            /// 多实例插件
            /// </summary>
            IList<Type> MultipleParts { get; }
    
            /// <summary>
            /// 获取所有插件实例与配置信息
            /// </summary>
            IDictionary<object, IPartInformation> ActivePart { get; }
    
            /// <summary>
            /// 添加插件
            /// </summary>
            /// <param name="partType"></param>
            /// <param name="partConfigration"></param>
            void AddPart(Type partType, IPartConfigration partConfigration);
    
            /// <summary>
            /// 移除指定实例插件
            /// </summary>
            /// <param name="partInstance"></param>
            void RemovePart(object partInstance);
    
            /// <summary>
            /// 获取单实例插件
            /// </summary>
            /// <param name="partType"></param>
            /// <returns></returns>
            object GetSinglePart(Type partType);
    
            /// <summary>
            /// 获取多实例插件
            /// </summary>
            /// <param name="partType"></param>
            /// <returns></returns>
            IEnumerable<object> GetMultipleParts(Type partType);
    
            /// <summary>
            /// 获取插件信息
            /// </summary>
            /// <param name="partType"></param>
            /// <returns></returns>
            IPartInformation GetPartInformation(object instance);
    
            /// <summary>
            /// 获取指定类型插件是否启用
            /// </summary>
            /// <param name="partType"></param>
            /// <returns></returns>
            bool IsPartEnabled(Type partType);
    
        }
    }
    复制代码

    第三步:类的实现

      接着就是类的实现,里面会涉及到反射的应用,linq等基本应用

     View Code

    第四步:单元测试

      好了,我们插件存放的容器就设计好了,来个单元测试,接下来享受下我们的成果吧:

    复制代码
     class Program
        {
            static void Main(string[] args)
            {
                Program p = new Program();
                var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"PartConfig.config") ;
                IGLPartContainer cont = new GLPartContainer<Program>(p, configPath, new string[] { AppDomain.CurrentDomain.BaseDirectory });
                var p1 = cont.GetSinglePart(typeof(Part1));
    
                Console.WriteLine("Part1 Full Name : {0}",p1.GetType().FullName);
    
                var xxx = cont.GetPartInformation(p1);
    
                Console.WriteLine("Part1 Metadata : {0}", xxx.PartName, xxx.PartType);
    
                var p2 = cont.GetMultipleParts(typeof(Part2));
    
                Console.WriteLine("Part12 Full Name : {0}", p1.GetType().FullName);
    
                Console.ReadLine();
            }
        }
    复制代码

     运行结果:

    既然来了,何不留下您的脚印,如果您觉得本文对你有所帮助的话,请点击推荐,如果你想关注本系列文章的话,请点击关注我。

     
     
    分类: C#
  • 相关阅读:
    tensorflow 2.0 学习 (十) 拟合与过拟合问题
    tensorflow 2.0 学习 (九) tensorboard可视化功能认识
    tensorflow 2.0 学习 (八) keras模块的认识
    tensorflow 2.0 学习 (七) 反向传播代码逐步实现
    tensorflow 2.0 学习 (六) Himmelblua函数求极值
    tensorflow 2.0 学习 (五)MPG全连接网络训练与测试
    arp协议简单介绍
    Pthread spinlock自旋锁
    线程和进程状态
    内核态(内核空间)和用户态(用户空间)的区别和联系·
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3101007.html
Copyright © 2011-2022 走看看