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

前言

哇,看到题目挺长的,这个组合型的东西,到底能干啥呢?本篇文章来一起琢磨琢磨,这两天为了团队的软件赶工,我负责的那一块叫:插件管理器。我们团队的成员用的语言还是挺分散的,本人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下载

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容