.NET责任链模式、单例模式、模板方法模式混用

闪电
闪电
闪电
55
文章
0
评论
2020年4月27日14:48:10 评论 38

前言

 
   哇,看到题目挺长的,这个组合型的东西,到底能干啥呢?本篇文章来一起琢磨琢磨,这两天为了团队的软件赶工,我负责的那一块叫:插件管理器。我们团队的成员用的语言还是挺分散的,本人C#,队长VB.NET,还有其他成员写易语言等,系统的功能插件是我们分开写的,各自用各自的喜欢的语言写各个功能模块的插件,最后用我开发的插件管理器把所有的插件整合到一起。这让我很头疼啊,一个C#版的插件,一个VB.NET版的插件,一个易语言的插件,如果有新成员加入,又来个Python版的插件,叫我如何是好。最普通、最烂的处理方法就是:写很多版本的读取器,然后使用if来根据插件语言使用对应该版本的读取器读取信息,哇,这如果来十几种语言,岂不是坑爹。可能有人会说:引入Kernel32能解决非.NET语言的插件读取吧。确实目前使用了这种方案,能兼容易语言,.NET语言就使用.NET的插件Dll读取方式,但是还不确定Kernel32能不能解决任何语言的插件。所以为了让自己有个后路,我结合题目所说的三种设计模式,写了一个模板,下面看这个模板能干嘛,为什么要这样用。如果有地方使用得不恰当的,希望各位朋友们强拍,我会努力学习改正,如果觉得可以的,打赏一个,谢谢~
       

框架展示与说明

 
       

 
   大家看下类图,下面分别介绍各个类的职责。Plugin类是插件的数据结构类,PluginType是一个枚举,它的内容是定义了所有插件的类型,例如DotNet,Python等,扩展的时候需要修改该枚举。然后剩下的就是本章的重点了,用户代码Client使用IPluginAnalyzerable接口来读取插件信息,该接口有两个实现类,一个是PluginAnalyzer(抽象类,定义各个语言的读取器的公共部分),该类实现模板方法模式和责任链模式,让处理命令能在其子类之间互相推卸责任,其子类目前有两个,分别是DotNet,Python的具体读取器。最后一个类ComponentAnalyzer担任封装职责,将责任链初始化好,自己实现单例模式,提供给用户代码调用。所以,最后的效果是:获取ComponentAnalyzer的实例,调用其中一个方法,该方法调用具体读取器链,最后哪个读取器处理了请求,用户是不需要知道的,大概思路就是这样。

实现过程

 
       
   本实现过程只是模板的实现,具体应用到项目中还需要做出相应的改变。
事不宜迟,我们先实现Plugin类和PlugType枚举:

##Plugin:
class Plugin
{
    public Plugin ( PluginType type , String pluginPath ) {
        this.BelongType = type;
        this.PluginPath  = pluginPath;
    }
    public PluginType BelongType   { set; get;  }
    public String PluginPath     { set; get;  }
}
##PlugType:
enum PluginType
{
      DotNet = 0,
      Python = 1,
}

       
   实现完两个基本的类型以后,然后就来看下本篇的重头戏,我会边贴代码边附加上帮助理解的说明。首先从最底层的IPluginAnalyzerable开始:

interfaceI  PluginAnalyzerable
{
    voidAnalyze(Plugin plugin);
}

       
   用户代码就是使用该接口的Analyze方法来处理传入的插件的,再下一层有两个类,一个是抽象类PluginAnalyzer,一个是ComponentAnalyzer。

    abstract  class  PluginAnalyzer:IPluginAnalyzerable
    {
        public PluginAnalyzer ( PluginType type ) { 
            this.analyzerType =type; 
        }

        protected  PluginType analyzerType;

        public  void  Analyze ( Plugin plugin ) {
            if  ( plugin.BelongType ==  this.analyzerType ){
                String author = GetAuthor( plugin );
                String version = GetVersion( plugin );
                Console.WriteLine(String.Format("\\r\\n分析者:{0},插件类型:{1} \\r\\n{2}\\r\\n{3}",this.GetType().Name,plugin.BelongType,author, version));
            }
            else  {
                if ( nextAnalyzer != null ) {
                    nextAnalyzer.Analyze(plugin); 
                }
            }
        }

        private  PluginAnalyzer nextAnalyzer;
        public  PluginAnalyzer NextAnalyzer  {  
            set  {  this.nextAnalyzer =  value;  }
            get  {  return  this.nextAnalyzer;  }
        }

        protected  abstract  String GetAuthor ( Plugin plugin );
        protected  abstractString GetVersion ( Plugin plugin );
  }

解读:

每一个继承本类的具体类都有两个字段

  1. 所属类型(PluginType枚举),变量名为analyzerType。
  2. 下一个分析者(PluginAnalyzer),也就是兄弟类(同样继承PluginAnalyzer)。
    每一个继承本类的具体类都需要重写两个方法GetAuthor和GetVersion,这两个方法将会在模板方法Analyzer内部被使用。
    模板方法Analyzer首先判断传进来的插件类型是否和自身可以处理的类型相同,如果相同则调用自身的方法处理,如果不同则把处理权推给自己的下一位分析者。这样就完成了具体架构的搭建了。
    然后就是具体读取器类了,各自有各自的处理相同任务的方式。都继承PluginAnalyzer
//DotNetPluginAnalyzer(该类是处理.NET插件的读取器)
class DotNetPluginAnalyzer : PluginAnalyzer
{
        public DotNetPluginAnalyzer ( PluginType type ) : base( type ){}
        public DotNetPluginAnalyzer():base(PluginType.DotNet){ }
        protected  override  string  GetAuthor ( Plugin plugin ){
            return"DotNet的插件,作者名为:Jarvin";
        }
        protected override string GetVersion ( Plugin plugin ){
            return"DotNet的插件,版本号为:!!!V2014!!";
        }
}
//PythonPluginAnalyzer(该类是处理Python插件的读取器)
class PythonPluginAnalyzer : PluginAnalyzer
{
        public PythonPluginAnalyzer ( PluginType type ):base(type){ }
        public PythonPluginAnalyzer ( ):base(PluginType.Python){ }
        protected override string GetAuthor ( Plugin plugin ){
          return"Python的插件,作者名为:Joker" ;
        }
        protected override string GetVersion ( Plugin plugin ) {
            return"Python的插件,版本号为:V---很奇怪----";
        }
}

       
   好了,如何使用?我将新建一个类,把这些读取器包装起来,并且形成一条链,提供一个统一的接口给用户代码调用,下面看我如何包装的。

    private ComponentAnalyzer(){
            rootAnalyzer = new DotNetPluginAnalyzer ( );
            PythonPluginAnalyzer pythonAnalyzer = new PythonPluginAnalyzer ( );
            rootAnalyzer.NextAnalyzer = pythonAnalyzer;
        }
        #region 单例模式实现
     private ComponentAnalyzer ( ) {

     }
        public static ComponentAnalyzer GetInstance ( ) {
            return SingleHelper.GetInstance ( );
        }
        private class SingleHelper
        {
            private static ComponentAnalyzer me = new ComponentAnalyzer ( );
            public static ComponentAnalyzer GetInstance ( ) {
                return me;
            }
        }
        #endregion

        PluginAnalyzer rootAnalyzer;

        public void Analyze ( Plugin plugin ) {
            rootAnalyzer.Analyze ( plugin );
        }
    }

       
    很简单的一个类,我们先看构造函数:把所有语言的读取器连接起来,然后链头是rootAnalyzer,我们每次调用Analyze方法都会调用rootAnalyzer对应的方法,让其在内部传递。弄到这里,差不多完成了,大家可以在最下面直接下载源码运行看结果,下面给出客户端测试类。

class Programe
    {
        public static void Main ( string[] args ){
            IPluginAnalyzerable dotnetAnalyzer = ComponentAnalyzer.GetInstance ( );
            Console.WriteLine ( "输入Q退出" );
            while ( true ) {
                Plugin plugin = RandomPlugin ( );
                dotnetAnalyzer.Analyze ( plugin );
                if ( Console.ReadLine ( ).ToUpper ( ) == "Q" ) {
                    break;
                }
            }
        }
        private static Plugin RandomPlugin ( ){
            Random random = new Random ( );
            PluginType type = ( PluginType )random.Next ( 0, 2 );
            String plaginPath = Path.GetRandomFileName ( ) ;
            Plugin result = new Plugin ( type, plaginPath );
            return result;
        }
    }

测试结果:


看,以一致的方式执行,但是会得到不一样的效果,责任被推到合适的地方做出相应的处理。

到这里有人会说,那如何证明该模型的扩展性?? 好,下面我扩展一种语言读取器,看我改了多少,对系统影响了多少?

扩展性测试

我以Ruby为例。

  1. 在PluginType中添加一个枚举内容Ruby:

2.添加一个Ruby读取器:

3.在ComponentAnalyzer(前面说的包装器)中,把该读取器添加到链条上!

       
   为了测试,在客户端代码中修改Random随机生成数,使其能生成3,大功告成!(这是测试相关,我们没有修改实际客户端任何代码)
顺利扩展!!!我们修改的只是上层的代码,对于底层,也提供了扩展点,符合对修改封闭,对扩展开放。
       

总结

 
       
   完成了,大家也累了,希望有不对的地方大家大力拍,面向组合编程,面向接口编程,不要面向具体实现类编程,这是我学习设计模式感受最深的一句话。谢谢大家观看。下面提供完整源码。

完整Demo下载

继续阅读
weinxin
我的微信
微信扫一扫
闪电
  • 本文由 发表于 2020年4月27日14:48:10
  • 除非特殊声明,本站文章均为原创,转载请务必保留本文链接
CleanAOP--简介 .NET

CleanAOP--简介

AOP为Aspect Oriented Programming的缩写。 意为:面向切面编程。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,将它...
WPF如何用TreeView制作好友列表、播放列表 .NET

WPF如何用TreeView制作好友列表、播放列表

前言   TreeView这个控件对于我来说是用得比较多的,以前做的小聊天软件(好友列表)、音乐播放器(播放列表)、类库展示器(树形类结构)等都用的是TreeView,下面以一个好友列表为例,说明一下...
WPF多线程UI更新——两种方法 .NET

WPF多线程UI更新——两种方法

前言     在WPF中,在使用多线程在后台进行计算限制的异步操作的时候,如果在后台线程中对UI进行了修改,则会出现一个错误:(调用线程无法访问此对象,因为另一个线程拥有该对象。)这是很常见的一个错误...
WPF 蒙罩层 LoadingPage .NET

WPF 蒙罩层 LoadingPage

前言   无论是在PC客户端,移动端,网站,在遇到长时间处理的时候都会需要用到蒙罩层,让用户有更好的体现。今天上网逛了一下各位前辈网友的蒙罩层的实现方式,觉得有很多都搞复杂了(利用前台代码+后台代码+...
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: