欢迎来到study-hard-forever的博客

11、抽象工厂模式

抽象工厂模式:

提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。

例:我们在调用数据库时,可能会采用不同的数据库,进而其实现细节也会有所不同。如果我们在客户端实例化数据库对象,那么这个对象就完全被这个数据库限制了,若采用其他数据库时,在执行同样地操作时(比如插入数据)我们就不能利用这个对象的方法去调用Insert()实现。因此我们可以考虑工厂方法模式去改进实现,达到多态(工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化那一个类)

工厂方法模式下的数据库访问UML图:

采用IUser接口,用于客户端访问,解除了与具体数据库访问的耦合,即实现了业务逻辑与数据访问的解耦。

如果要更换数据库,只需要把new SqlServerFactory()改成new AccessFactory()即可。

但是数据表是多种多样的,我们的数据库中还有可能包含除了User表之外的其他表,例如Department(部门表):那么我们更改上述的UML图就变成了以下的抽象工厂模式:

只有一个User类和User操作类的时候,是只需要工厂方法模式的,但是随着数据表(产品)的增多,SQL Server与Access(具体工厂)又是不同的分类,这种涉及到多个产品系列的问题,就变成了专门的工厂模式——抽象工厂模式。

抽象工厂UML结构图:

这里为了更好的理解,我们可以引入产品族的概念:

产品族(Product Family):是指位于不同产品等级结构,功能相关联的产品组成的家族。

图中一共有四个产品族,分布于三个不同的产品等级结构中。只要指明一个产品所处的产品族以及它所属的等级结构,就可以唯一的确定这个产品。

(实际上我们可以简单理解:产品族为具体工厂的组合,产品等级结构为具体产品的组合)

在什么情形下使用抽象工厂模式:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节时。一个系统有多于一个的产品族(具体工厂),而系统只消费其中某一产品族(比如数据库只选一类就OK)。

AbstractFactory模式和Factory模式的区别:
AbstractFactory模式是为创建一组(有多类)相关或依赖的对象提供创建接口。
Factory模式是为一类对象提供创建接口。

从“开放——封闭”原则谈抽象工厂的优缺点:

“开放——封闭”原则要求系统对扩展开放,对修改封闭。通过扩展达到增强其功能的目的。对于涉及 到多个产品族与多个产品等级结构的系统,其功能增加包括两方面:
增加产品族(具体工厂):Abstract Factory很好的支持了"开放——封 闭"原则。
增加新产品的等级结构:需要修改所有的工厂角色,没 有很好支持“开放——封闭”原则。
综合起来,抽象工厂模式以一种倾斜的方式支持增加新的产品(具体工厂),它为新产品族的增加提供方便,而不能为新的产品等级结构的增加提供这样的方便。

抽象工厂的本质实际就是选择产品族的实现。

利用简单工厂改进抽象工厂(以选择数据库为例):

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

UML图:

	class DataAccess
    {
        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;
        }
    }
    
    //Clinet:
    class Program
    {
        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();
        }
    }

上述代码我们仍然没能摆脱switch,case进行数据库的选择,那我们接下来就以反射+配置文件实现数据库访问来替代switch和case。从这个角度讲,所有在用简单工厂的地方都可以考虑用反射技术去除switch或if,解除分支判断带来的耦合。

反射技术:

依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色需要另一个角色的协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。
但在“依赖注入”里,创建被调用者的工作不再由调用者来完成,因此称为控制反转,创建被调用者实例的工作通常由专门的Ioc容器(比如Spring.NET)来完成,然后注入调用者,因此也称为依赖入。
当然这里我们不需要那么麻烦,我们只需要了解一个简单的.NET技术,用反射来实现依赖注入就可以了。

	Using System.Reflection;  //要先引用System.Reflection命名空间
	Assembly.Load(“程序集名称”).CreateInstance(“命名空间.类名称”)  //使用

例如:

  	//反射实现:
  	using System.Reflection;
  	class DataAccess
    {
        private static readonly string AssemblyName = "抽象工厂模式";
        private static readonly string db = "Sqlserver";
        //private static readonly string db = "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);
        }
     }

但是我们需要修改数据库时仍需要进入程序进行修改数据库名称的字符串,又要重新编译执行,这样程序还并不是很完美。那么我们下面再采用反射+配置文件的方式来解决这个修改数据库名字符串的问题。

在C#中我们需要对App.config配置文件进行修改:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<appSettings>
		<add key="DB" value="Sqlserver"/>
	</appSettings>
</configuration>
   	//反射+配置文件实现:
  	using System.Reflection;
   	class DataAccess
    {
        private static readonly string AssemblyName = "抽象工厂模式";
        private static readonly string db = ConfigurationManager.AppSettings["DB"];
        
        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);
        }
    }

以上是C#实现反射的方法,我们再来看一下Java反射:

Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息。
Java反射相关的API在包java.lang.reflect中。

下面我们来看一个完整实例(Java):

例(反射+配置文件+简单工厂改造后的抽象工厂实现数据库访问):

UML图:

具体代码实现:

建立配置文件:sql.properties,内容如下:

db=Access
#db=SqlServer
package test;
//User:

public class User{
	
	private long id;
    private String name;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }  
    
}

//interface IUser:

public interface IUser {
	//对数据库操作采取插入和查询(通过ID)
    void insert(User user);
    void getUser(int id);
}

//AccessUser implements IUser:

public class AccessUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在Access数据库插入user记录");
    }

    @Override
    public void getUser(int id) {
        System.out.println("在Access数据库根据id获取user记录");
    }

}

//SqlServerUser implements IUser:

public class SqlServerUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在SqlServer数据库插入user记录");
    }

    @Override
    public void getUser(int id) {
        System.out.println("在SqlServer数据库根据id获取user记录");
    }

}

//Department:

public class Department {
    private long id;
    private String deptName;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    
}

//interface IDepartment:

public interface IDepartment {
    void insert(Department dept);
    void getDept(int id);
}

//AccessDepartment implements IDepartment:

public class AccessDepartment implements IDepartment {

    @Override
    public void insert(Department dept) {
        System.out.println("在Access数据库插入department记录");
    }

    @Override
    public void getDept(int id) {
        System.out.println("在Access数据库根据id获取department记录");
    }

}

//SqlServerDepartment implements IDepartment:

public class SqlServerDepartment implements IDepartment {

    @Override
    public void insert(Department dept) {
        System.out.println("在SqlServerDepartment数据库插入department记录");
    }

    @Override
    public void getDept(int id) {
        System.out.println("在SqlServerDepartment数据库根据id获取department记录");
    }

}

//ReflectSqlFactory(重点在于该类):

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Properties;

public class ReflectSqlFactory {
    public String getDb() {
        Properties prop = new Properties();
        String db = "";
        try {
            InputStream in = new BufferedInputStream(new FileInputStream(
                    "sql.properties"));
            prop.load(in); // /加载属性列表
            Iterator<String> it = prop.stringPropertyNames().iterator();
            while (it.hasNext()) {
                String key = it.next();
                if("db".endsWith(key)){
                    db = prop.getProperty("db");
                    break;
                }
            }
            in.close();
        } catch (Exception e) {
            System.out.println(e);
        }
        return db;
    }
    public Class<?> getsqlFactory(String type) throws ClassNotFoundException{
        String className= "test."+getDb()+type;
        //System.out.println("传参连接得到的className"+className);
        Class<?> c1 = Class.forName(className);
        //System.out.println("调用函数getName()得到的"+c1.getName());
        return c1;
    }
    public IUser createUser() throws InstantiationException, IllegalAccessException, ClassNotFoundException{
        Class<?> c = getsqlFactory("User");
        IUser iu = (IUser)c.newInstance();
        return iu;
    };
    public IDepartment createDept() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class<?> c = getsqlFactory("Department");
        IDepartment id = (IDepartment)c.newInstance();
        return id;
    };
}

//main:

public class TestMain {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    	
    	ReflectSqlFactory reflectSqlFactory = new ReflectSqlFactory();  //首先先建立工厂
    	
    	//User
        User user = new User();
        IUser iuDao = reflectSqlFactory.createUser();
        iuDao.insert(user);
        iuDao.getUser(1);
        
        //Department
        Department dept = new Department();
        IDepartment idepatDao = reflectSqlFactory.createDept();
        idepatDao.insert(dept);
        idepatDao.getDept(1);
    }
}

有关反射的推荐文章(有很多,这里推荐一篇沉默王二的文章作参考):

如果有人再问你 Java 的反射,把这篇文章扔给他:https://qingmiaogu.blog.csdn.net/article/details/86684626

posted @ 2020-06-19 22:08  study-hard-forever  阅读(243)  评论(0编辑  收藏  举报