设想一个场景:
我们做了一个项目,最初,只支持MySQL数据库,所以我们一般直接在项目内部定义一个类,并定义其方法
#MySqlHelper.cs public class MySqlHelper { public void Query() { } }
使用上,当然更简单了
//项目最初,只支持MySQL MySqlHelper mySqlHelper = new MySqlHelper(); mySqlHelper.Query();
隔了1个月,产品说版本升级,支持一下SQLServer吧,然后,搞开发的同学,就开始想了,你这么搞,难道我再加一个类不成,也不是不行,就是太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啊,一般来说,不同的客户采用不同的数据库,甚至可能采用多个数据库,那么,我们自然不能在代码实现中,将这部分代码写死,如果客户中间要换数据库呢?那不是还得改程序吗?既然我们做产品,当然要做成通用的嘛!像这种情况,配置文件就该派上用场了,我们可以在app.config中指定数据库种类,比如这里,我通过AppSettings节点,添加了一个key值为IDBHelperConfig的项,项内分别保存了类所在的dll和类的名称。
<?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="ReflectionDemo,ReflectionDemo.MySqlHelper"/> </appSettings> <connectionStrings> <!--数据库连接字符串--> <add name="Customers" connectionString="Data Source=ElevenPC; Database=Customers; User ID=sa; Password=Password; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>
我们就可以通过反射动态加载dll,然后实现动态可配置加载方式,到这里,小心思又要来了,反射每次要写那么多代码,如果每次调用都写那么多,那将DRY原则置于何地呀,所以,就把它封装成一个简单工厂吧,这样就看起来完美了,
我们可以把上图内容封装到类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 type1 = assembly.GetType(typeName); object o = Activator.CreateInstance(type1); IDBHelper dBHelper = o as IDBHelper; return dBHelper; } }
这样,操作就简单了,我们只需在调用时,调用工厂类方法,创建对象,然后即可调用相应的Query方法了
IDBHelper dBHelper = SimpleFactory.CreateInstance(); dBHelper.Query();
看上去清爽多了,就跟直接new 类是一样的。
那可能有小伙伴要说了,其实我没必要用反射啊,我在配置文件里定义了要使用的数据库类型,那我可以直接在工厂类里将这个值作为参数传入,然后根据这个值来生成相应的类啊,反正最后返回的都是IDBHelper对象。是的,这样的设计确实也没问题,那么,我们这里为什么要如此设计呢?这样做有什么好处呢?其实,是为了实现程序的可扩展,此话怎讲呢?
再来想象一下这个场景,如果,我们现在又要支持Oracle数据库了,按照刚刚提到的这种方式的话,那我们就需要修改源程序,在SimpleFactory类里添加对应的Oracle对象的生成逻辑,使用时在修改配置文件。
而如果是采用的上述方式的话,你发现没,我们只需要将Oracle对象对应的类库文件放到项目路径下,然后通过修改配置文件,即可完成程序的扩展,而基本无需改动源程序的任何代码。那么,你觉得哪种方式更优呢?
其实,说到这里,我们能看出来,之前将MySqlHelper.cs和SqlServerHelper.cs放在项目框架下是不合理的,因为,作为单独的数据库操作类,我们应该将其设计成单独类库,这样不仅代码结构更加合理,而且,能够实现代码的复用和扩展,这是我最终的项目架构图。
这样的话,我们就实现了一个通用的动态加载的可配置可扩展的框架示例。其实应该是挺简单的,主要就是借助反射来实现动态加载和可扩展,借助配置文件来做到可配置。
源码我放到了GitHub,有需要的请自取
https://github.com/IronMarmot/Samples/tree/master/MyReflection/ReflectionDemo
最新版本讲解,请关注下方公众号。