zoukankan      html  css  js  c++  java
  • 简单工厂模式——实现多数据库链接

    我们的工作是支持SqlServer数据库,Access数据库,MySql数据库. 这样的话,每一种对数据库的操作都会对应三种不同的实现.
    dnt_forums(论坛版块信息表)为例. 已有数据 (这个表被小菜简化了,因为这样更能说明问题)

    fid : 版块编号 name:版块名称
    1 版块1
    2 版块2

    现在要求提供操作. 1.fid访问数据库取出name ==> string GetForumName(int fid) 等等数据库相关操作...... 先来看看代码的组织吧.

    很直观.接下来就来看代码吧. 数据访问接口

    using System;
    
    
    
    namespace Discuz.Data
    
    {
    
        public interface IForumManage
    
        {
    
            string GetForumName(int fid);
    
        }
    
    }

    SqlServer数据库实现

    using System;
    
    using Discuz.Data;
    
    
    
    namespace Discuz.Data.SqlServer
    
    {
    
        public class ForumManage : IForumManage
    
        {
    
            public string GetForumName(int fid)
    
            {
    
                //SqlServer数据库实现,具体实现略
    
                return "SqlServer版块名称";
    
            }
    
        }
    
    }

    Access数据库实现

    using System;
    
    using Discuz.Data;
    
    
    
    namespace Discuz.Data.Access
    
    {
    
        public class ForumManage : IForumManage
    
        {
    
            public string GetForumName(int fid)
    
            {
    
                //Access数据库实现,具体实现略
    
                return "Access版块名称";
    
            }
    
        }
    
    }

    MySql数据库实现

    using System;
    
    using Discuz.Data;
    
    
    
    namespace Discuz.Data.MySql
    
    {
    
        public class ForumManage : IForumManage
    
        {
    
            public string GetForumName(int fid)
    
            {
    
                //MySql数据库实现,具体实现略
    
                return "MySql版块名称";
    
            }
    
        }
    
    }

    这样的话,那么我们的客户程序便可以方便的使用了.
    Default.aspx页面调用

    using System;
    
    using Discuz.Data;
    
    
    
    public partial class _Default : System.Web.UI.Page 
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            IForumManage forumManage = null;
    
            string dbType = "SqlServer"; //如果要使用其它数据库改这里   
    
             switch (dbType)
    
            {
    
                case "SqlServer":
    
                    forumManage = new Discuz.Data.SqlServer.ForumManage();
    
                    break;
    
                case "Access":
    
                    forumManage = new Discuz.Data.Access.ForumManage();
    
                    break;
    
                case "MySql":
    
                    forumManage = new Discuz.Data.MySql.ForumManage();
    
                    break;
    
                default:
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql");
    
            }
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
        }
    
    }

    如果小菜想在页面Forum.aspx也使用ForumManage,怎么办呢?这还不简单.

    using System;
    
    using Discuz.Data;
    
    
    
    public partial class Forum : System.Web.UI.Page
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            IForumManage forumManage = null;
    
            string dbType = "SqlServer"; //如果要使用其它数据库改这里   
    
             switch (dbType)
    
            {
    
                case "SqlServer":
    
                    forumManage = new Discuz.Data.SqlServer.ForumManage();
    
                    break;
    
                case "Access":
    
                    forumManage = new Discuz.Data.Access.ForumManage();
    
                    break;
    
                case "MySql":
    
                    forumManage = new Discuz.Data.MySql.ForumManage();
    
                    break;
    
                default:
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql");
    
            }
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
            //或者使用forumManage的其它方法
    
        }
    
    }

    苍天啊,大地啊,怎么会这样啊,一模一样的代码竟然出现.(代码中有坏味道,看来我们得重构一下它才行)
    怎么办呢? 把创建具体ForumManage的逻辑独立出来,放入某个类中,就设为ForumFactory吧.
    这样我们就引入ForumFactory

    using System;
    
    using Discuz.Data;
    
    
    
    public class ForumFactory
    
    {
    
        public static IForumManage Create(string dbType)
    
        {
    
            IForumManage forumManage = null;
    
            switch (dbType)
    
            {
    
                case "SqlServer":
    
                    forumManage = new Discuz.Data.SqlServer.ForumManage();
    
                    break;
    
                case "Access":
    
                    forumManage = new Discuz.Data.Access.ForumManage();
    
                    break;
    
                case "MySql":
    
                    forumManage = new Discuz.Data.MySql.ForumManage();
    
                    break;
    
                default:
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql");
    
            }
    
            return forumManage;
    
        }
    
    }

    那么,我们在Default.aspxForum.aspx等地方使用ForumManage将方便许多,不信就接着往下看.
    Default.aspx页面调用

    using System;
    
    using Discuz.Data;
    
    
    
    public partial class _Default : System.Web.UI.Page 
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            IForumManage forumManage = null;
    
            forumManage = ForumFactory.Create("SqlServer");//如果要使用其它数据库改这里
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
        }
    
    }

    Forum.aspx页面调用

    using System;
    
    using Discuz.Data;
    
    
    
    public partial class Forum : System.Web.UI.Page
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            IForumManage forumManage = null;
    
            forumManage = ForumFactory.Create("SqlServer");//如果要使用其它数据库改这里
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
            //或者使用forumManage的其它方法
    
        }
    
    }

    有一天,老板觉的应该把SqlServer数据库换成MySql数据库怎么办呢?
    偶早就想到有这一天了,打开VS2005点击->编辑->查找与替换 将所有ForumFactory.Create("SqlServer")替换为ForumFactory.Create("MySql").然后重新编译.

    ,瞧瞧,糟糕透了.相信你也不想这样干吧.
    看来情况不妙,有什么解决低招呢? 把当前使用的数据库类型放入配置文件Web.config.(也称作依赖注入)

    <appSettings>   
       
    <add key="DbType" value="SqlServer"/>
    </appSettings>

    那么修改我们的ForumFactory代码吧

    using System;
    
    using System.Configuration;
    
    using Discuz.Data;
    
    
    
    public class ForumFactory
    
    {
    
        public static IForumManage Create()
    
        {
    
            IForumManage forumManage = null;
    
            string dbType = ConfigurationManager.AppSettings["DbType"];
    
    
    
            switch (dbType)
    
            {
    
                case "SqlServer":
    
                    forumManage = new Discuz.Data.SqlServer.ForumManage();
    
                    break;
    
                case "Access":
    
                    forumManage = new Discuz.Data.Access.ForumManage();
    
                    break;
    
                case "MySql":
    
                    forumManage = new Discuz.Data.MySql.ForumManage();
    
                    break;
    
                default:
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql");
    
            }
    
            return forumManage;
    
        }
    
    }

    还能不能在简洁些呢? 如果有一天老板觉得要使用Oracle数据库才算跟的上潮流.那我们该怎么办呢?
    第一步:定义一个Discuz.Data.Oracle.ForumManage,实现Discuz.Data.IForumManage接口. (对扩展开放)
    第二步:修改ForumFactory中的switch代码,添加代码. (对修改开放)

    switch (dbType)
    
    {  
    
        //其它同上,略
    
        case "Oracle" :
    
            forumManage = new Discuz.Data.Oracle.ForumManage();
    
        default:
    
            throw new Exception("暂时只支持SqlServer,Access,MySql,Oracle");
    
    }

    第三步:修改Web.config中的<add key="DbType" value="Oracle" />
    看来我们做的不够好,我们的设计应该对扩展开放对修改关闭,而不是对修改开放
    不知道聪明的你想到什么解决方法呢?
    .Net中那么好的反射功能不用岂不太可惜了.

    using System;
    
    using System.Reflection;
    
    using System.Configuration;
    
    
    
    namespace Discuz.Data
    
    {
    
        public class ForumFactory
    
        {
    
            public static IForumManage Create()
    
            {
    
                IForumManage forumManage = null;
    
                string dbType = ConfigurationManager.AppSettings["DbType"];
    
    
    
                string assemblyName = string.Format("Discuz.Data.{0}", dbType);
    
                string fullName = string.Format("Discuz.Data.{0}.ForumManage", dbType);
    
                try
    
                {
    
                    forumManage = 
    
    (IForumManage)Assembly.Load(assemblyName).CreateInstance(fullName);
    
                }
    
                catch
    
                {
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql,Oracle");
    
                }
    
                return forumManage;
    
            }
    
        }
    
    }

    现在不仅代码简洁了,我们也成功实现了对扩展开放对修改关闭.
    因为就算有一天老板要求我们把数据库换成DB2
    第一步:定义一个Discuz.Data.DB2.ForumManage,实现Discuz.Data.IForumManage接口.
    第二步:修改Web.config中的<add key="DbType" value="DB2" />
    我们不再需要往ForumFactory中的switch中添加代码,实现了对修改关闭

    switch (dbType)
    
    {    
    
        //其它同上,略
    
        case "DB2" :
    
            forumManage = new Discuz.Data.DB2.ForumManage();
    
        default:
    
            throw new Exception("暂时只支持SqlServer,Access,MySql,Oracle,DB2");
    
    }

    也由于使用了反射,我们可以把ForumFactory移入Discuz.Data类库中. 之前不行吗?不行,因为Discuz.Data无法引用Discuz.Data.SqlServer或者Discuz.Data.Access等空间. 会出现循环引用. 接下来就来看看简单工厂模式的类图吧.

    看来不错,但每次调用Create()都要实例化一次forumManage看来并不是太聪明,能不能使用static提升一下性能呢?

    using System;
    
    using System.Reflection;
    
    using System.Configuration;
    
    using Discuz.Data;
    
    
    
    public class ForumFactory
    
    {
    
        private static IForumManage forumManage;
    
        public static IForumManage Create()
    
        {
    
            if (forumManage == null)
    
            {
    
                string dbType = ConfigurationManager.AppSettings["DbType"];
    
    
    
                string assemblyName = string.Format("Discuz.Data.{0}", dbType);
    
                string fullName = string.Format("Discuz.Data.{0}.ForumManage", dbType);
    
                try
    
                {
    
                    forumManage = 
    
    (IForumManage)Assembly.Load(assemblyName).CreateInstance(fullName);
    
                }
    
                catch
    
                {
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql,Oracle");
    
                }
    
            }
    
            return forumManage;
    
        }
    
    }

    看起来好象不错,使用了static,下一次调用ForumFactory.Create(),就不用实例化forumManage,直接返回

    但你发现问题了没? 1.Web.config中的配置项是<add key="DbType" value="Access" /> 2.Default.aspx使用ForumManage调用GetForumName方法

       IForumManage forumManage = ForumFactory.Create() ==> 实例化的将是 new Discuz.Data.Access.ForumManage();    输出: Access版块 3.Web.config中的配置修改为<add key="DbType" value="SqlServer" />也就是换数据库    刷新Default.aspx页面,分析一下将输出什么呢?    执行IForumManage forumManage = ForumFactory.Create()    调用ForumFactory.Create(),因为其中的forumManage在第2步中已经被实例化过,不等于null,直接返回forumManage

       所以返回的还是new Discuz.Data.Access.ForumManage()
       那么应该输出:Access版块,也就是换数据库不成功    ,分析的很好,不过输出却是:SqlServer版块,说明换数据库成功.    为什么呢?分析错了吗? 不是, 因为修改Web.config会导致应用程序重启.(一切重新来过) 这时你会说了,那不是很好,关我什么事! 但我们经常把会改变的配置项独立出来,放在某个配置文件中,这样也避免了修改配置文件导致应用程序重启导致的性能损耗. Discuz!NT2.0,就将数据库类型放在DNT.config配置文件中.

    <?xml version="1.0" encoding="utf-8"?>
    <BaseConfigInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     
    <DbType>Access</DbType>
    </BaseConfigInfo>

    这样的话,我们要取配置项,就需要反序化它.
    在这里,我们只需要知道,我们可以通过BaseConfigFileManager.GetDbType就能得到最新的配置文件中的DbType结点的值

    string dbType = ConfigurationManager.AppSettings["DbType"];
    我们修改为
    string dbType = BaseConfigFileManager.GetDbType;

    1.DNT.config中的配置是<add key="DbType" value="Access" />
    2.Default.aspx使用ForumManage调用GetForumName方法
       IForumManage forumManage = ForumFactory.Create() ==> 实例化的将是 new Discuz.Data.Access.ForumManage();
       输出: Access版块
    3.DNT.config中的配置修改为<add key="DbType" value="SqlServer" />也就是换数据库
       刷新Default.aspx页面,输出Access版块.

       我们应该如何解决这个问题呢?
    1.我们可以为ForumFactory提供一个Reset()方法,forumManage重置为null   

    using System.Xml.Serialization;
    
    using System.Reflection;
    
    using System.Configuration;
    
    
    
    namespace Discuz.Data
    
    {
    
        public static class ForumFactory
    
        {
    
            private static IForumManage forumManage;
    
    
    
            public static IForumManage Create()
    
            {
    
                if (forumManage == null)
    
                {
    
                    //省略
    
                  }
    
                return forumManage;
    
            }
    
    
    
            public static void Reset()
    
            {
    
                forumManage = null;
    
            }
    
        }
    
    }

    ForumFactory.Reset()将在什么时候调用呢?
    2.避免通过打开DNT.config来修改DbType配置项
       因为不方便,不直观,而且可能出现输入错误的情况
       我们可以通过定制相应的页面,比如下拉菜单来更改数据库类型,间接修改DNT.config配置文件,如果数据库类型更改,
       则调用ForumFactory.Reset()

    顺便来复习下单件模式吧.通常我们可以把简单工厂改造成单件模式.Discuz!NT2.0也这么做了.
    不过小菜认为这里有点不太合适,因为反而会把代码设计的更复杂,简洁就是美,而且达到功能的要求.
    不信你就看看下面的代码与上面的代码对比一下.

    using System;
    
    using System.Reflection;
    
    using Discuz.Config
    
    
    
    namespace Discuz.Data
    
    {
    
        public class ForumFactory
    
        {
    
            private static IForumManage _instance;
    
            private static object lockHelper = new object();
    
    
    
            private ForumFactory()
    
            {}
    
    
    
            static ForumFactory()
    
            {
    
                GetProvider();
    
            }
    
    
    
            public static void GetProvider()
    
            {
    
                string dbType = BaseConfigFileManager.GetDbType;
    
    
    
                string assemblyName = string.Format("Discuz.Data.{0}", dbType);
    
                string fullName = string.Format("Discuz.Data.{0}.ForumManage", dbType);
    
                try
    
                {
    
                    _instance = 
    
    (IForumManage)Assembly.Load(assemblyName).CreateInstance(fullName);
    
    
                }
    
                catch
    
                {
    
                    throw new Exception("暂时只支持SqlServer,Access,MySql");
    
                }
    
            }
    
    
    
            public static IForumManage Instance
    
            {
    
                get
    
                {
    
                    if (_instance == null)
    
                    {
    
                        lock (lockHelper)
    
                        {
    
                            if (_instance == null)
    
                            {
    
                                GetProvider();
    
                            }
    
                        }
    
                    }
    
                    return _instance;
    
                }
    
            }
    
    
    
            public static void ResetProvider()
    
            {
    
                _instance = null;
    
            }
    
        }
    
    }

    如果你看了小菜的前一篇 <.Net Framework框架源码学习单件模式> 的话 你就会发现这个单件模式很特别.
    它把小菜单件模式的第一种和第四种都用上了,杂合体.
    为什么呢?
    想想看把Instance属性修改成如下,也就是仅使用第一种.

    publicstatic IForumManage Instance
    {
       
    get{ return _instance;}
    }

    有什么问题呢?初看没问题,但关键出在ResetProvider()方法上,如果它被调用,_instance为空.Instance的返回也将是null
    那下次调用ForumManage.Create()将返回什么呢?当然还是null.因为静态构造函数只会被调用一次,所以你可别指望它.
    这也就是为什么在Instance属性中使用了double-check双检查,重新加载_instance的原因.即第四种的原因.
    建议有点乱的朋友看一下小菜的前一篇 <.Net Framework框架源码学习单件模式>
    所以大家也应该清楚,并不是用模式就是好的.

    也许看到该篇简单工厂模式,很多人会想到工厂模式.
    现在就来对比一下,针对这个应用使用工厂模式合不合适.
    ,今天不是讲简单工厂吗?怎么又跑到工厂模式.是不是跑题了?
    模式之间的碰撞才能出现火花不是吗.

    很直观吧,来看看代码吧

    using System;
    
    using Discuz.Data;
    
    
    
    namespace Discuz.DALFactory
    
    {
    
        public abstract class ForumFactory
    
        {
    
            public abstract IForumManage Create(); 
    
        }
    
    
    
        public class SqlServerForumFactory : ForumFactory
    
        {
    
            public override IForumManage Create()
    
            {
    
                return new Discuz.Data.SqlServer.ForumManage();
    
            }
    
        }
    
    
    
        public class AccessForumFactory : ForumFactory
    
        {
    
            public override IForumManage Create()
    
            {
    
                return new Discuz.Data.Access.ForumManage();
    
    
    
            }
    
        }
    
    
    
        public class MySqlForumFactory : ForumFactory
    
        {
    
            public override IForumManage Create()
    
            {
    
                return new Discuz.Data.MySql.ForumManage();
    
    
    
            }
    
        }
    
    }

    那我们如何使用呢?
    还是在Default.aspx页面调用

    using System;
    
    using Discuz.Data;
    
    using Discuz.DALFactory;
    
    
    
    public partial class _Default : System.Web.UI.Page 
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            ForumFactory factory = new SqlServerForumFactory();
    
            //如果要使用其它数据库改这里   
    
             IForumManage forumManage = null;
    
            forumManage = factory.Create();
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
        }
    
    }

    ,天呐,我既然看到了new SqlServerForumFactory();这和之前的ForumFactory.Create("SqlServer");看来是不相上下,只能说我们走入了歧途,但是有办法走出来吗?
    那我们就在Web.config中动动手脚

    <appSettings>
       
    <add key="DbType" value="Access"/>
       
    <add key="Access" value="Discuz.DALFactory.AccessForumFactory"/>
       
    <add key="SqlServer" value="Discuz.DALFactory.SqlServerForumFactory"/>
       
    <add key="MySql" value="Discuz.DALFactory.MySqlForumFactory"/>
    </appSettings>

    那我们在Default.aspx页面调用来看看

    using System;
    
    using System.Reflection;
    
    using System.Configuration;
    
    using Discuz.Data;
    
    using Discuz.DALFactory;
    
    
    
    public partial class _Default : System.Web.UI.Page 
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            string dbType = ConfigurationManager.AppSettings["DbType"];
    
            string fullName = ConfigurationManager.AppSettings[dbType];
    
            string assemblyName = fullName.Substring(0, fullName.LastIndexOf("."));
    
    
    
            ForumFactory  factory = 
    
    (ForumFactory)Assembly.Load(assemblyName).CreateInstance(fullName);
    
    
    
            IForumManage forumManage = null;
    
            forumManage = factory.Create();
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
        }
    
    }

    那我们在Forum.aspx页面调用看看

    using System;
    
    using System.Reflection;
    
    using System.Configuration;
    
    using Discuz.Data;
    
    using Discuz.DALFactory;
    
    
    
    public partial class Forum : System.Web.UI.Page
    
    {
    
        protected void Page_Load(object sender, EventArgs e)
    
        {
    
            string dbType = ConfigurationManager.AppSettings["DbType"];
    
            string fullName = ConfigurationManager.AppSettings[dbType];
    
            string assemblyName = fullName.Substring(0, fullName.LastIndexOf("."));
    
    
    
            ForumFactory  factory  = 
    
    (ForumFactory)Assembly.Load(assemblyName).CreateInstance(fullName);
    
    
    
            IForumManage forumManage = null;
    
            forumManage = factory.Create();
    
    
    
            Response.Write("版块名称:" + forumManage.GetForumName(1));
    
        }
    
    }

    ..代码基本上一模一样,重复的代码太多,这样不是又要把相同的代码独立出来,那不是又和简单工厂类似. 看来工厂模式用在这里是此地无银三百两的作法.

    所以不管是从本篇的单件模式的运用,还是工厂模式与简单工厂的碰撞值的大家思考.

  • 相关阅读:
    趁热打铁(如何改bug)
    element el-input的autofocus失效问题解决
    为什么要将图片转为base64格式
    学习jdk1.8的Lambda和Stream
    (隐式参数)java8的方法引用之重新认识java的this关键字
    记一次惊奇面试,希望能为广大求职中的javaer提供一点经验。
    单机版ZooKeeper的安装教程
    两个对象值转换的方法(BeanUtils.copyProperties与JSONObject.parseObject对比)
    SpringBoot监控管理之Admin监管使用
    安利一个十分实用的IDEA插件--RestfulToolkit
  • 原文地址:https://www.cnblogs.com/dean-Wei/p/3628865.html
Copyright © 2011-2022 走看看