设计模式(十三)抽象工厂模式

数据访问的代码?(以“新增用户”和“得到用户”为例。)

最基本的数据访问程序

用户类, 假设只有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类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。

优点:

  1. 易于交换产品系列。由于具体工程类在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
  2. 让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。

缺点:

  1. 如果要增加功能,比如我们现在要增加项目表Project,那就至少要增加三个类IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。

  2. 客户端程序类显然不会是只有一个,有很多地方都在使用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  状态模式


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值