抽象工厂模式 与 反射

内容摘录自《大话设计模式》

 

需求:

已开发好的项目,电子商务网站,是用SQL Server作为数据库的。现在接到另一家公司的类似需求的项目,只能用Access,于是需要改造原来项目的代码。

而实际上,SQL Server和Access的命名空间,使用的函数,关键字等都存在不同之处。

 

最基本的数据访问程序

用户类,假设只有ID和Name两个字段,其余省略。

//数据表用户类
class User
{
    private int _id;
    public int ID
    {
        get { return _id; }
        set { _id = value; }
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

SqlserverUser类——用于操作User表,假设只有“新增用户”和“得到用户”方法,其余省略。

class SqlserverUser
{
    public void Insert(User user)
    {
        Debug.Log("在SQL Server中给User表增加一条记录");
    }

    public User GetUser(int id)
    {
        Debug.Log("在SQL Server中根据ID得到User表一条记录");
        return null;
    }
}

客户端代码

        User user = new User();
        SqlserverUser su = new SqlserverUser();   // 与SQL Server耦合
        su.Insert(user);
        su.GetUser(1);

 

这里不能换数据库,因为SqlserverUser su = new SqlserverUser()使得su这个对象被框死在SQL Server上了。如果这里是灵活的,专业点的说法,是多态的,那么在执行‘su.Insert(user);’和‘su.GetUser(1);’时就不用考虑是在用SQL Server还是在用Access。

下面用“工厂方法模式”来封装new SqlserverUser()所造成的变化。工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。

 

工厂方法模式的数据访问程序

Iuser接口,用于客户端访问,解除与具体数据库访问的耦合。

// 数据库操作
interface IUser
{
    void Insert(User user);
    User GetUser(int id);
}

SqlserverUser类,用于访问SQL Server的User。

class SqlserverUser : IUser
{
    public void Insert(User user)
    {
        Debug.Log("在SQL Server中给User表增加一条记录");
    }

    public User GetUser(int id)
    {
        Debug.Log("在SQL Server中根据ID得到User表一条记录");
        return null;
    }
}

AccessUser类,用于访问Access的User。

class AccessUser : IUser
{
    public void Insert(User user)
    {
        Debug.Log("在Access中给User表增加一条记录");
    }

    public User GetUser(int id)
    {
        Debug.Log("在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();
    }
}

客户端代码

        User user = new User();

        IFactory factory = new SqlServerFactory(); // 若要更改成Access数据库,只需要将本句改成IFactory factory = new AccessFactory();
        IUser iu = factory.CreateUser();

        iu.Insert(user);
        iu.GetUser(1);

 

现在如果要换数据库,只需要把new SqlServerFactory()改成new AccessFactory(),此时由于多态的关系,使得声明Iuser接口的对象iu事先根本不知道是在访问那个数据库,却可以在运行时很好的完成工作,这就是所谓的业务逻辑与数据访问的解耦。

仍然存在的问题:代码里还是有指明new SqlServerFactory();如果除了User表外,要增加其他表,需要继续增加类。

 

抽象工厂模式的数据访问程序

增加部门表(Department)。

代码结构图

部门类,假设只有ID和Name两个字段,其余省略。

class Department
{
    private int _id;
    public int ID
    {
        get { return _id; }
        set { _id = value; }
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }
}

IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合。

interface IDepartment
{
    void Insert(Department department);
    Department GetDepartment(int id);
}

SqlserverDepartment类,用于访问SQL Server的Department。

class SqlserverDepartment : IDepartment
{
    public void Insert(Department department)
    {
        Debug.Log("在SQL Server中给Department表增加一条记录");
    }

    public Department GetDepartment(int id)
    {
        Debug.Log("在SQL Server中根据ID得到Department表一条记录");
        return null;
    }
}

AccessDepartment,用于访问Access的Department。

class AccessDepartment : IDepartment
{
    public void Insert(Department department)
    {
        Debug.Log("在Access中给Department表增加一条记录");
    }

    public Department GetDepartment(int id)
    {
        Debug.Log("在Access中根据ID得到Department表一条记录");
        return null;
    }
}

IFactory接口,定义一个创建访问User表对象的抽象的工厂接口。

// 工厂类
interface IFactory
{
    IUser CreateUser();
    IDepartment CreateDepartment(); // 增加创建部门表对象的接口方法
}

SqlServerFactory,实现Ifactory接口,实例化SqlserverUser和SqlserverDepartment。

class SqlServerFactory : IFactory
{
    public IUser CreateUser()
    {
        return new SqlserverUser();
    }

    public IDepartment CreateDepartment()   // 增加了SqlserverDepartment工厂
    {
        return new SqlserverDepartment();
    }
}

AccessFactory,实现Ifactory接口,实例化AccessUser和AccessDepartment。

class AccessFactory : IFactory
{
    public IUser CreateUser()
    {
        return new AccessUser();
    }

    public IDepartment CreateDepartment()   // 增加了AccessDepartment工厂
    {
        return new AccessDepartment();
    }
}

客户端代码

        User user = new User();
        Department department = new Department();

        IFactory factory = new SqlServerFactory();
        //IFactory factory = new AccessFactory();   // 数据库访问的切换

        IUser iu = factory.CreateUser();
        iu.Insert(user);
        iu.GetUser(1);

        IDepartment iD = factory.CreateDepartment();
        iD.Insert(department);
        iD.GetDepartment(1);

 

只有一个User类和User操作类的时候,是只需要工厂方法模式的,但如果数据库有多个表,而SQL Server与Access又是两大不同的分类,解决这种涉及到多个产品系列的问题,就可以用抽象工厂模式。

 

抽象工厂模式

抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

AbstractProductA和AbstractProductB是两个抽象产品,它们都有两种不同的实现,就前面的例子来说就是User和Department,而ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体分类的实现,比如ProductA1可以理解为是SqlserverUser,而ProductB1是AccessUser。

IFactory是一个抽象工厂模式,它里面包含所有产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2就是具体的工厂了。就像SqlserverFactory和AccessFactory一样。

通常是在运行的时候再创建一个ConcreteFactory类的实例,这个具体的工厂再创建具体特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂

 

抽象工厂模式的优点与缺点

最大的好处是易于交换产品系列,由于具体工厂类,例如IFactory factory = new AccessFactory(),在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置

第二大好处是,它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口来操作实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中

 

抽象工厂模式可以很方便的切换两个数据库的访问,但如果你的需求来自增加功能,比如我们现在要增加项目表Project,就要增加三个类,Iproject、SqlserverProject、AccessProject,还需要修改IFactory、SqlserverFactory和AccessFactory才行。

假设很多地方都在使用IUser和IDepartment,在每一个类的开始都需要声明IFactory factory = new SqlserverFactory (),如果有100个类调用,那就要改100才代码,才能换成AccessFactory。编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。

 

用简单工厂来改进抽象工厂

去除IFactory、SqlserverFactory和AccessFactory三个工厂,取而代之的是DataAccess类,用一个简单工厂模式来实现。

class DataAccess
{
    //private static readonly string db = "Sqlserver";
    private static readonly string db = "Access";   // 数据库名称

    public static IUser CreateUser()
    {
        IUser iuser = null;
        switch (db)
        {
            case "Sqlserver":
                iuser = new SqlserverUser();
                break;
            case "Access":
                iuser = new AccessUser();
                break;
        }
        return iuser;
    }

    public static IDepartment CreateDepartment()
    {
        IDepartment department = null;
        switch (db)
        {
            case "Sqlserver":
                department = new SqlserverDepartment();
                break;
            case "Access":
                department = new AccessDepartment();
                break;
        }
        return department;
    }
}

客户端代码

        User user = new User();
        Department department = new Department();

        IUser iu = DataAccess.CreateUser();
        iu.Insert(user);
        iu.GetUser(1);

        IDepartment iD = DataAccess.CreateDepartment();
        iD.Insert(department);
        iD.GetDepartment(1);

 

客户端只需要DataAccess.CreateUser()和DataAccess.CreateDepartment()来生成具体的数据库访问类实例,客户端没有出现任何一个SQL Server或Access的字样,达到了解耦的目的。

但是,如果需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,现在就需要在DataAccess类中的每个方法的switch中加case了。

 

用反射+抽象工厂的数据访问数据

如果可以不在程序里写明‘如果是SqlServer就去实例化SQL Server数据库相关类,如果是Access就去实例化Access相关类这样的语句’,而是根据字符串db的值去某个地方找应该要实例化的类是哪一个。这样,就可以对switch说再见了。

 

反射:

Assembly.Load(“程序集名称”).CreateInstance(“命名空间.类名称”)

需要using System.Reflection;来引用Reflection。

 

有了反射,我们获得实例可以用下面两种写法。

// 常规的写法
IUser result = new SqlserverUser();

// 反射的写法
using System.Reflection;
IUser result = (IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.SqlserverUser");

原来的实例化是在编译时写死在代码里,需要更换为AccessUser,就需要修改代码重新编译。用了反射之后,将程序由编译时转为运行时。由于‘CreateInstance("抽象工厂模式.SqlserverUser")’中的字符串是可以写成变量的,而变量的值是可以动态改变的,所以就去除了switch判断的麻烦。

DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory。

class DataAccess
{
    private static readonly string AssemblyName = "抽象工厂模式"; // 程序集名称
    private static readonly string db = "Sqlserver";    // 数据库名称,可替换成Access。

    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);
    }
}

 

Unity中反射的使用

下面将DataAccess的函数返回实例代码改为Unity反射,假设已知类型名称。

using System.Reflection;    // 引入反射
using System;

//return (IUser)Assembly.Load(AssemblyName).CreateInstance(className);
return (IUser)Activator.CreateInstance(Type.GetType(db + "User"));  // Unity反射的实现

//return (IDepartment)Assembly.Load(AssemblyName).CreateInstance(className);
return (IDepartment)Activator.CreateInstance(Type.GetType(db + "Department"));  // Unity反射的实现

1.取得数据类型Type 
方式一:Type.GetType(“类型全名”);
适合于类型的名称已知
方式二:obj.GetType();
适合于类型名未知,类型未知,存在已有对象
方式三:typeof(类型)
适合于已知类型
方式四:Assembly.Load(“XXX”).GetType(“名字”);
适合于类型在另一个程序集中

 

如果我们增加了Oracle数据访问,增加了相关的类之后,就只需要将db改为OracleUser就能变成OracleUser的实例了。

如果要增加Project产品,只需要增加三个与Project相关的类,再修改DataAccess,增加一个CreateProject()方法就可以了。

但是有个问题,在更换数据库访问时,还是需要去改程序(修改db的值)重编译。

 

用反射+配置文件实现数据访问程序

通过读文件来给db字符串赋值,在配置文件中写明是Sqlserver还是Access,这样就连DataAccess类也不用改了。

 

所有在用简单工厂的地方,都可以考虑用反射来去除switch或if,解除分支判断带来的耦合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值