一步步實現自己的框架系列(二):插件框架實現


  不好意思各位同學,本系列文章更新比較慢,因為我也要工作,況且還需要抽出時間編碼驗證理論,當然找借口總歸是不好的,我們都是人,需要休息與娛樂嘛。

  其實.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等基本應用

namespace GL.Core
{
    public sealed class GLPartContainer<TOwner> : IGLPartContainer
        where TOwner : class
    {

        private static readonly ILog _logger = LogManager.GetLogger("GL.Core.GLPartContainer");

        /// <summary>
        /// 插件實例注冊
        /// </summary>
        private IDictionary<object, IPartInformation> _PartInstanceRegistry = new Dictionary<object, IPartInformation>();

        /// <summary>
        /// 插件配置
        /// </summary>
        private IDictionary<string, IPartConfigration> _PartConfig = new Dictionary<string, IPartConfigration>();
        

        public IList<Type> SingleParts { get; private set; }

        public IList<Type> MultipleParts { get; private set; }

        public IDictionary<object, IPartInformation> ActivePart 
        {
            get { return _PartInstanceRegistry; }
        }

        /// <summary>
        /// 容器擁有者
        /// </summary>
        public TOwner Parent { get; private set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="owner">容器擁有者</param>
        /// <param name="partConfigFile">插件配置文件目錄</param>
        /// <param name="partDirectories">插件目錄(絕對路徑)</param>
        public GLPartContainer(TOwner owner, string partConfigFile, string[] partDirectories)
        {
            this.Parent = owner;

            if (File.Exists(partConfigFile))
            {
                var doc = XDocument.Load(partConfigFile);
                _PartConfig = doc.Descendants("part").ToDictionary(
                        e => e.Attribute("type").Value,
                        e => new IPartConfigration(e.Attribute("type").Value, bool.Parse(e.Attribute("isEnabled").Value)));
            }

            foreach (var path in partDirectories)
            {
                if (!Directory.Exists(path))
                {
                    throw new GLException(GLErrorCodes.PartDirectoryNotExists, string.Format("Part Directory Not Exist:{0}", path));
                }
                string[] dllPaths = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories);
                try
                {

                    var typeList = from dll in dllPaths
                                   from type in Assembly.LoadFile(dll).GetTypes()
                                   select type;

                    SingleParts = (from type in typeList
                                  from attribute in type.GetCustomAttributes(false)
                                  where attribute.GetType().Equals(typeof(SinglePartAttribute))
                                  select type).ToList();

                    MultipleParts = (from type in typeList
                                    from attribute in type.GetCustomAttributes(false)
                                    where attribute.GetType().Equals(typeof(MultiplePartAttribute))
                                    select type).ToList();
                }
                catch (Exception e)
                {
                    throw new GLException(GLErrorCodes.PartLoadError, "Platform Load Type Error:{0}", e.Message, e);
                }
            }

            if (SingleParts == null)
            {
                SingleParts = new List<Type>();
            }
            if (MultipleParts == null)
            {
                MultipleParts = new List<Type>();
            }

            foreach (var part in SingleParts)
            {
                var instance = Activator.CreateInstance(part);
                _PartInstanceRegistry.Add(instance, GetPartInformation(part));
            }

            foreach (var part in MultipleParts)
            {
                var instance = Activator.CreateInstance(part);
                _PartInstanceRegistry.Add(instance, GetPartInformation(part));
            }
        }

        public void AddPart(Type partType, IPartConfigration partConfigration)
        {
            var singlePart = from attribute in partType.GetCustomAttributes(false)
                          where attribute.GetType().Equals(typeof(SinglePartAttribute))
                          select partType;

            if (singlePart != null)
            {
                var instance = Activator.CreateInstance(partType);
                _PartInstanceRegistry.Add(instance, GetPartInformation(partType));
                SingleParts.Add(partType);
            }
            else
            {
                var multiplePart = from attribute in partType.GetCustomAttributes(false)
                                   where attribute.GetType().Equals(typeof(MultiplePartAttribute))
                                   select partType;
                if (multiplePart != null)
                {
                    var instance = Activator.CreateInstance(partType);
                    _PartInstanceRegistry.Add(instance, GetPartInformation(partType));
                    MultipleParts.Add(partType);
                }
                else
                {
                    throw new GLException(GLErrorCodes.PartNotFound, "指定類型中未發現插件");
                }
            }
        }

        public IPartInformation GetPartInformation(object instance)
        {
            IPartInformation partInformation = null;
            _PartInstanceRegistry.TryGetValue(instance, out partInformation);
            return partInformation;
        }

        private IPartInformation GetPartInformation(Type type)
        {
            var partMetadatas = type.GetCustomAttributes(false).Where(e => e.GetType().Equals(typeof(PartMetadataAttribute)));
            var partInformation = new PartInformation();
            foreach (var metadata in partMetadatas)
            {
                var partMetadata = metadata as PartMetadataAttribute;
                partInformation.SetProperty(partMetadata.Name, partMetadata.Value);
            }

            return partInformation;
        }

        public bool IsPartEnabled(Type partType)
        {
            return _PartConfig.ContainsKey(partType.FullName) ? _PartConfig[partType.FullName].IsEnable : false;
        }


        public void RemovePart(object partInstance)
        {
            if (_PartInstanceRegistry.ContainsKey(partInstance))
            {
                _PartInstanceRegistry.Remove(partInstance);
            }
            else
            {
                throw new GLException(GLErrorCodes.PartNotFound, string.Format("instance of {0} not found", partInstance.GetType().FullName));
            }
        }

        public object GetSinglePart(Type partType)
        {
            var instanceCount = _PartInstanceRegistry.Keys.Count(p => partType.IsAssignableFrom(p.GetType()));
            if (instanceCount > 1)
            {
                throw new GLException(GLErrorCodes.PartMultipleInstances, string.Format("{0} has multiple instances", partType.FullName));
            }
            return _PartInstanceRegistry.Keys.SingleOrDefault(p => partType.IsAssignableFrom(p.GetType()));
        }


        public IEnumerable<object> GetMultipleParts(Type partType)
        {
            return _PartInstanceRegistry.Keys.Where(p => partType.IsAssignableFrom(p.GetType()));
        }

    }
}
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();
        }
    }

 

 運行結果:

既然來了,何不留下您的腳印,如果您覺得本文對你有所幫助的話,請點擊推薦,如果你想關注本系列文章的話,請點擊關注我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM