数据访问的代码?(以“新增用户”和“得到用户”为例。)
最基本的数据访问程序
用户类, 假设只有ID和Name两个字段,其余省略:
class User{
private int _id;
public int ID{
get{return _id;}
set{_id=value;}
}
private sting _name;
public string Name{
get{return _name;)
set{_name=value;}
}
}
SqlserverUser类——用于操作User表,假设只有“新增用户”和“得到用户”方法,其余方法以及具体的SQL语句省略:
class SqlserverUser{
public void Insert(User user){
Console.WriteLine("在 SQL Server 中给User表中增加一条记录");
}
public User GetUser(int id){
Console.WriteLine("在 SQL Server 中根据ID得到User表一条记录");
return null;
}
}
客户端代码:
static void Main(string[] args){
User user = new User();
SqlserverUser su = new SqlserverUser(); // 与SQL Server耦合
su.Insert(user); // 插入用户
su.GetUser(1); // 得到ID为1的用户
Console.Read();
}
这里的问题就在于SqlserverUser su = new SqlserverUser()使得su这个对象被“框死”在SQL Server上了。想想我们之前学的,工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。
用了工厂方法模式的数据访问程序
代码结构图:
IUser接口:用于客户端访问,解除与具体数据库访问的耦合:
interface IUser{
void Insert(User user);
User getUser(int id);
}
SqlserverUser类,用于访问SQL Server的User:
class SqlserverUser : IUser{
public void Insert(User user){
Console.WriteLine("在SQL Server中给User表增加一条记录");
}
public User GetUser(int id){
Console.WriteLine("在SQL Server中根据ID得到User表一条记录");
return null;
}
}
AccessUser类,用于访问Access的User:
class AccessUser : IUser{
public void Insert(User user){
Console.WriteLine("在Access中给User表增加一条记录");
}
public User GetUser(int id){
Console.WriteLine("在Access中根据ID得到User表一条记录");
return null;
}
}
IFactory接口,定义一个创建访问User表对象的抽象的工厂接口:
interface IFactory{
IUser CreateUser();
}
SqlServerFactory类,实现IFactory接口,实例化SqlserverUser:
class SqlServerFactory:IFactory{
public IUser CreateUser(){
return new SqlserverUser();
}
}
AccessFactory类,实现IFactory接口,实例化AccessUser:
class AccessFactory:IFactory{
public IUser CreateUser(){
return new AccessUser();
}
}
客户端代码:
static void Main(string[] args){
User user = new User();
// 若要更改成Access数据库,只需要将本句改成IFactorry factory = new AccessFactory();
IFactory factory = new SqlServerFactory();
IUser iu = factory.CreateUser();
iu.Insert(user);
iu.GetUser(1);
Console.Read();
}
现在如果要更改成Access数据库,只需要将 new SqlServerFactory()改成new AccessFactory();,此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问哪个数据库,却可以在运行时很好地完成工作,这就是所谓的业务逻辑与数据访问的解耦。
数据库里不会只有一个User表,现在增加部门表(Department表),该如何办呢?
class Department{
private int id;
public int ID{
get{return _id;}
set{_id=value;}
}
private string _deptName;
public string DeptName{
get{return _deptName;}
set{_deptName=value;}
}
}
用了抽象工厂模式的数据访问程序
代码结构图:
IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合:
interface IDepartment{
void Insert(Department department);
Department GetDepartment(int id);
}
SqlserverDepartment类,用于访问SQL Server的Department:
class SqlserverDepartment : IDepartment{
public void Insert(Department department){
Console.WriteLine("在SQL Server中给Department表增加一条记录");
}
public Department GetDepartment(int id){
Console.WriteLine("在SQL Server中根据ID得到Department表一条记录");
return null;
}
}
AccessDepartment类,用于访问Access的Department:
class AccessDepartment : IDepartment{
public void Insert(Department department){
Console.WriteLine("在Access中给Department表增加一条记录");
}
public Department GetDepartment(int id){
Console.WriteLine("在Access中根据ID得到Department表一条记录");
return null;
}
}
IFactory接口,定义一个创建访问Department表对象的抽象的工厂接口:
interface IFactory{
IUser CreateUser();
//增加的接口方法
IDepartment CreateDepartment();
}
SqlServerFactory类,实现IFactory接口,实例化SqlserverUser和SqlserverDepartment:
class SqlserverFactory : IFactory{
public IUser CreateUser(){
return new SqlserverUser();
}
// 增加了SqlserverDepartment工厂
public IDepartment CreateDepartment(){
return new SqlserverDepartment();
}
}
AccessFactory类,实现IFactory接口,实例化 AccessUser和 AccessDepartment:
class AccessFactory : IFactory{
public IUser CreateUser(){
return new AccessUser();
}
// 增加了AccessDepartment工厂
public IDepartment CreateDepartment(){
return new AccessDepartment();
}
}
客户端代码:
static void Main(string[] args){
User user = new User();
Department dept = new Department();
// 只需确定实例化哪一个数据库访问对象给factory
// IFactory factory = new SqlserverFactory();
IFactory factory = new AccessFactory();
// 则此时已与具体的数据库访问解除了依赖
IUser iu=factory.CreateUser();
iu.Insert(user);
iu.GetUser(1);
// 则此时已与具体的数据库访问解除了依赖
IDepartment id = factory.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
Console.Read();
}
结果显示如下:
这样,只需更改IFactory factory = new AccessFactory()为IFactory factory = new SqlserverFactory(),就实现了数据库访问的切换。
抽象工厂模式
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
AbstractProductA和AbstractProductB是两个抽象产品,之所以为抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是User和Department,而ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体分类的实现,比如ProductA1可以理解为是SqlserverUser,而ProductB1是SqlserverDepartment。
IFActory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2就是具体的工厂了。
通常是在运行时刻再创建一个ConcreteFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。
优点:
- 易于交换产品系列。由于具体工程类在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
- 让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点:
-
如果要增加功能,比如我们现在要增加项目表Project,那就至少要增加三个类IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。
-
客户端程序类显然不会是只有一个,有很多地方都在使用IUser或IDepartment,而这样的设计其实在每一个类的开始都需要声明IFactory factory = new SqlserverFactory()。如果我有100个调用数据库访问的类,岂不是要更改100次 IFactory factory = new AccessFactory()这样的代码。
须知,编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。
用简单工厂来改进抽象工厂
去除IFactory、SqlserverFactory和AccessFactory三个工厂类,取而代之的是DataAccess类,用一个简单工厂模式来实现:
class DataAccess{
// 数据库名称,可替换成Access
private static readonly string db ="Sqlserver";
// private static readonly string db ="Access";
public static IUser CreateUser(){
IUser result = null;
switch (db){
case "Sqlserver":
result = new SqlserverUser();
break;
case "Access":
result = new AccessUser();
break;
}
return result;
}
public static IDepartment CreateDepartment(){
IDepartment result = null;
switch (db){
case "Sqlserver":
result = new SqlserverDepartment();
break;
case "Access":
result = new AccessDepartment();
break;
}
return result;
}
}
客户端代码:
static void Main(string[] args){
User user = new User();
Department dept = new Department();;
// 直接得到实际的数据库访问实例,而不存在任何依赖
IUser iu = DataAccess.CreateUser();
iu.Insert(user);
iu.GetUser(1);
// 直接得到实际的数据库访问实例,而不存在任何依赖
IDepartment id = DataAccess.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
Console.Read();
}
可以看到客户端没有出现任何一个SQL Server 或Access的字样,达到了解耦的目的。
可是现在,如果我需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,现在就比较麻烦了,需要在DataAccess类中每个方法的switch中加case。
用反射+抽象工厂的数据访问程序
能不能免去switch判断的麻烦呢?
常规的写法:
IUser result = new SqlserverUser();
反射的写法:
// 先引用System.Reflection的命名空间
using System.Reflection;
IUser result = (IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.SqlserverUser");
看出差别了吗?原来的实例化是“写死”在程序里的,而现在用了反射就可以利用字符串来实例化对象,而变量是可以更换的。这样,变量的值到底是SQL Server,还是Access,完全可以由事先的那个db变量来决定。所以就去除了switch判断的麻烦。
代码结构图:
DataAccess类,用反射技术,取代IFactory、SqlserverFactoryr和AccessFactory:
// 引入反射,必须要写
using System.Reflection;
class DataAccess{
// 程序集名称
private static readonly string AssemblyName ="抽象工厂模式";
// 数据库名称,可替换成Access
private static readonly string db ="Sqlserver";
public static IUser CreateUser(){
string className = AssemblyName + "." +db + "User";
return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
}
public static IDepartment CreateDepartment(){
string className = AssemblyName + "." +db + "Department";
return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
}
}
这样的结果就是DataAccess.CreateUser()本来得到的是SqlserverUser的实例,而现在变成了OracleUser的实例了。
不过,还是有点遗憾。因为在更换数据库访问时,我们还是需要去改程序(改db这个字符串的值)重编译,如果可以不改程序,那才是真正地符合开放-封闭原则。
用反射+配置文件实现数据访问程序
可以利用配置文件来解决更改DataAccess的问题。(可以读文件来给DB字符串赋值,在配置文件中写明是Sqlserver还是Access,这样就连DataAccess类也不用更改了。
添加一个App.config文件,内容如下:
到此为止,我们成功应用了反射+抽象工厂模式解决了数据库访问时的可维护、可扩展的问题。
从这个角度上说,所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。
本章完。
本文是连载文章,此为第十三章,学习提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类的抽象工厂模式,并用反射+抽象工厂模式解决了数据库访问时的可维护、可扩展问题。
上一章:https://blog.csdn.net/qq_36770641/article/details/82833027 观察者模式
下一章:https://blog.csdn.net/qq_36770641/article/details/82886998 状态模式