最近猿A开发了一个项目,最初只支持MySQL数据库,所以猿A直接在项目内部定义一个类,并定义其具体实现
#MySqlHelper.cs public class MySqlHelper { public void Query() { } }
使用上,当然更简单了。
//项目最初,只支持MySQL MySqlHelper mySqlHelper = new MySqlHelper(); mySqlHelper.Query();
隔了1个月,产品说版本升级,支持一下SqlServer吧,然后,猿A就开始想了,你这么搞,难道我再加一个类不成,也不是不行,就是太low了,不过既然都是和数据库操作相关的类,干脆我就写个接口,定义数据库操作相关方法,然后定义和数据库相关的类来实现这个接口吧,毕竟也是要成为架构师的人嘛,怎能没有这点小想法。
于是,猿A写了一个接口,定义数据库操作方法
#IDBHelper.cs public interface IDBHelper { void Query(); //void Insert(); //void Update(); //void Delete(); }
接着,写了2个类MySQLHelper和SQLServerHelper且都继承该接口
#MySqlHelper.cs public class MySqlHelper:IDBHelper { public void Query() { } } ======= #SqlServerHelper.cs public class SqlServerHelper : IDBHelper { public void Query() { //throw new NotImplementedException(); } }
这样,客户是哪个数据库,我就生成哪种方式的代码吧。
猿B看到了猿A的写法,大叫一声,“瞎搞”,然后语重心长的说,今天我就再教你一招吧...
小A啊,一般来说,不同的客户采用不同的数据库,甚至可能采用多个不同数据库,那么,我们自然不能在代码实现中,将数据库连接和操作等写成固定的。为啥呢?因为这样的话,那不是的针对每个数据库都得写一套固定的代码吗?如果客户中间要换数据库呢?那不是还得改程序吗?
既然我们做的是产品,当然要做成通用的嘛!首先要保证产品的普适性。当然,像这种情况其实也是很容易处理的,我们只需按如下步骤进行就可以轻松搞定了。
1.借助配置文件,我们可以在配置文件中指定数据库种类,比如这里,我在app.config中通过AppSettings节点,添加了一个key值为IDBHelperConfig的项,项内保存了使用的数据库种类。这样可用于在实例化数据库操作类时用于判断。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" /> </startup> <appSettings> <add key="IDBHelperConfig" value="MySql"/> </appSettings> <connectionStrings> <!--数据库连接字符串--> <add name="Customers" connectionString="Data Source=ElevenPC; Database=Customers; User ID=sa; Password=Password; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>
2.由于所有的数据库操作类都继承了IDBHelper接口,那么你可以定义一个工厂类,其接受一个参数(即配置文件里配置的值),并生成对应的数据库操作类。
这样的话,其实我们就通过配置文件实现了程序的可配置。
3.但是,小A你想啊,这次增加一个SQLServer,万一下次还要增加Oracle,增加redis呢?你是不是还得修改代码,那这样的话,我们有没有一种方式能够做到一劳永逸呢,这样的话才能算的上是一个比较好的系统架构,对不对?
猿A说:要增加功能还能不修改代码,有这么神奇的方式吗?
猿B说:你啊,还是太年轻,要增加功能,修改代码肯定是必须的,但是,修改的可能只是业务代码,而跟架构没关系。还记得上次教你的反射吧。反射可以通过程序集名称和类名称来动态的创建对象。这样,如果我们的配置文件配置了数据库操作类及类所在的程序集,那么,我们就可以在工厂方法中通过反射的方式来动态的创建对象。如
<add key="IDBHelperConfig" value="ReflectionDemo,ReflectionDemo.MySqlHelper"/>
SimpleFactory的反射实现如下
public static class SimpleFactory { //通过配置文件,来决定使用哪种数据库 static string dllName = ConfigurationManager.AppSettings["IDBHelperConfig"].Split(',')[0]; static string typeName = ConfigurationManager.AppSettings["IDBHelperConfig"].Split(',')[1]; public static IDBHelper CreateInstance() { Assembly assembly = Assembly.Load(dllName); Type type = assembly.GetType(typeName); object o = Activator.CreateInstance(type); IDBHelper dBHelper = o as IDBHelper; return dBHelper; } }
这样,操作就简单了,我们只需在调用时,调用工厂类方法,创建对象,然后即可调用相应的Query方法了,看上去清爽多了,就跟直接new出的对象是一样的调用方式。
IDBHelper dBHelper = SimpleFactory.CreateInstance(); dBHelper.Query();
即使我们下次需要增加Oracle支持,那么我们也只需将Oracle的全部操作都封装在一个单独的类库里,然后向应用程序提供该类库就可以让原来的程序支持Oracle,这就是程序的可扩展性。你明白了吗?
猿A:哇,真的耶,大佬就是厉害,感谢指点,那这样的话,我赶紧去修改我的代码,我要将接口定义以及对MySQL、SqlServer、Oracle的支持都封装到单独的类库,并且用反射的方式来实现工厂类的方法,然后在通过配置文件来指导工厂方法生产对象。
过了一小会儿...
猿A:大佬,来看一下我这个架构还有什么问题吗?
猿B:嗯,这样的实现就对啦。这其实就是一个小型通用的可配置可扩展的架构的实例。简单来说,就是利用配置文件来实现可配置,利用反射的动态加载来实现可扩展。学的不错,继续加油。
就这样,猿A在迷茫的道路上又前进了一步。
源码我放到了GitHub,有需要的请自取
https://github.com/IronMarmot/Samples/tree/master/MyReflection/Reflect