第15章 就不能换DB吗?—抽象工厂模式

由于抽象工厂在我们编程当中经常使用和常见,所有本篇文章对《大话设计模式》中的15章做了很详细的比较。通过一个Dao层可以更换访问任意数据库的例子来学习抽象工厂模式。例如:Dao层可以访问Sqlserver数据库,也可以访问Access数据库,当程序新增访问Oracle数据库时,无需修改现有代码,只需要添加访问Oracle相关的类就可以,实现了开闭原则。本篇文章的例子中每种数据库上都有User和Department表,我们Dao层对这两个表进行查询和插入操作。

最基本数据库访问

一下是访问Sqlserver数据库的代码。

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

	public void insert(User user) {
		System.out.println("SqlserverUser insert user");
	}

	public void getUser(int id) {
		System.out.println("SqlserverUser get user");
	}

}
public class ClientTest {
	public static void main(String [] args){
		SqlserverUser su = new SqlserverUser();
		su.getUser(1);
		su.insert(new User());
	}
}

在上面的代码中,如果我们现在要更换Access数据库,那简直是灾难性的改动。主要是因为new Sqlserver()被硬编码了,访问数据库User表的类Sqlserver框死在了su变量上,也就说客户端依赖了具体的SqlserverUser类。如果灵活一点的那就是多态,在实际运行时决定使用SqlserverUser,还是AccessUser。这里我们使用工厂方法来解决这问题,让工厂方法模式来封装new Sqlserver()所造成的变化。

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

我们可以把访问User表的类看做是抽象产品,让客户端依赖于抽象,而不是依赖具体的user表访问类。访问Sqlserver数据库User表的SqlserverUser类和访问Access数据库User表的AccessUser类看做是具体产品。有一个抽象工厂和一个生产SqlserverUser具体产品,一个用于生产AccessUser具体产品。

uml图

代码实现

IUser接口,抽象产品,用于客户端和具体的数据库访问解耦。

public interface IUser {
	void insert(User user);
	void getUser(int id);
}

SqlserverUser实现了IUser接口的具体产品。访问Sqlserver数据库user表的类。

public class SqlserverUser implements IUser {

	@Override
	public void insert(User user) {
		System.out.println("SqlserverUser insert user");
	}

	@Override
	public void getUser(int id) {
		System.out.println("SqlserverUser get user");
	}

}

AccessUser实现了IUser接口的具体产品。访问Access数据库user表的类。

public class AccessUser implements IUser {

	@Override
	public void insert(User user) {
		System.out.println("AccessUser insert user");
	}

	@Override
	public void getUser(int id) {
		System.out.println("AccessUser get user");
	}

}

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

public interface IFactory {
	IUser createUser();
}

SqlserverUser实现了IFactory接口的具体工厂,用于生产访问Sqlserver数据库表的相关类。

public class SqlserverFacotry implements IFactory {

	@Override
	public IUser createUser() {
		// TODO Auto-generated method stub
		return new SqlserverUser();
	}

}

Access实现了IFactory接口的具体产品,用于生产访问Access数据库表的相关类。

public class AccessFactory implements IFactory {

	@Override
	public IUser createUser() {
		// TODO Auto-generated method stub
		return new AccessUser();
	}

}

客户端

public class ClientTest {
	public static void main(String [] args){

		IFactory factory = new SqlserverFacotry();
		//IFactory factory = new AccessFactory();
		IUser iu = factory.createUser();
		iu.insert(new User());
		iu.getUser(1);

	}
}

现在用工厂方法来实现,如果我们从访问Sqlserver数据库切换到Access数据库,只需要把new SqlserverFactory()换成new AccessFactory()即可,由于多态的关系IUser iu实现根本不知道是在访问那个具体的数据库,只有在运行时才知道,这就是所谓的业务逻辑与数据库访问的解耦。在访问数据库变更的情况下,我们用相对较小的改动实现这种变更。

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

我们提出一个问题:Sqlserver和Access数据库中不可能只有User一个表,可能还有其它的表,比如增加部门表(Department),那么我们应该怎么办呢?

uml图

我们看出我们需改动的地方如下:

1,在IFacotry接口中增加creatDepartment()方法,SqlserverFactory和AccessFactory分别实现createDepartemnt()方法。

2,增加操作部门表的抽象,IDepartment接口。

3,增加SqlserverDepparment和AccessDepartment类,分别实现IDepartment接口。

如果是增加一个对Oracle数据库的访问,改动如下:

1,增加OracleFactory类,用于生产操作oracle表的类,实现IFactory中的所有方法。

2,增加OracleUser类,用于操作user表,实现IUser中所有的方法。

3,增加OracleDepartment,用于操作department表,实现IDepartment中所有方法。

其实我们通过需求的不断的演变重构出了抽象工厂模式。只有一个IUser抽象产品和IUser具体产品的时候,是只需要工厂方法模式。但现在显然数据库中有很多表,而Sqlserver和Access又是两种不同的分类,所有涉及到多个产品系列的问题,有一个专门的工厂模式叫做抽象工厂。我们可以这么理解,只创建单一产品的工厂叫做工厂方法,可以创建多个产品系列的工厂叫做抽象工厂。举个例子:富士康和可胜科技都是IPhone的代工厂,可生产IPhone6,富士康和可胜科技都是具体的工厂,富士康的IPhone6和可胜科技的IPhone6都是具体的超,这个时候是工厂方法模式。IPhone生产让苹果公司很满意,把IPhone6s的代工也交给了富士康和可胜,这个时候就是抽象工厂模式了。富士康和可胜都在不仅可以生产IPhone6产品了,也可以生产IPhone6s。

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

优点

1,最大的好处就是易于交换系列产品,那我们这个例子来说就我可以很容易的切换数据库连接。

2,让具体的创建实例过程与客户端分离,客户端通过抽象来操作他们的实例。客户端代码中也不会出现具体产品类,只会出现抽象产品接口。

缺点

1,如果我们的客户端有100个地方调用了IFactory factory = new SqlserverFactory();,在切换到Access数据库时,需要改动一百个地方。这不能实现只更改一处就实现数据库切换的目的。

2,我们需要增加一张表,还需要修改SqlserverFactory、AccessFactory和IFactory类,违背了开闭原则。

用简单工厂来改进抽象工厂解决缺点1

去除IFactory、SqlserverFactory、AccessFactory,取而代之的是简单工厂AccessData类。代码示例中只给出了AccessData和ClientTest的代码,产品相关代码没有变化。如下:

public class DataAccess {

	private static String DB = "sqlserver";

	public static IUser createUser(){
		IUser user = null;
		switch (DB) {
			case "sqlserver":
				user = new SqlserverUser();
				break;
			case "access":
				user = new AccessUser();
				break;
		}

		return user;
	}

	public static IDepartment createDepartment(){
		IDepartment dep = null;

		switch (DB) {
			case "sqlserver":
				dep = new SqlserverDepartment();
				break;
			case "access":
				dep = new AccessDepartment();
				break;
		}

		return dep;
	}

}
public class DataAccess {

	private static String DB = "sqlserver";

	public static IUser createUser(){
		IUser user = null;
		switch (DB) {
			case "sqlserver":
				user = new SqlserverUser();
				break;
			case "access":
				user = new AccessUser();
				break;
		}

		return user;
	}

	public static IDepartment createDepartment(){
		IDepartment dep = null;

		switch (DB) {
			case "sqlserver":
				dep = new SqlserverDepartment();
				break;
			case "access":
				dep = new AccessDepartment();
				break;
		}

		return dep;
	}

}

简单工厂方这种方式的改进让客户端不再依赖new SqlserverFactory()或new AccessFactory(),而是仅仅依赖于AccesData简单工厂类,让客户端与具体的工厂类解耦。将需要访问的数据库以成员变量的方式声明在AccessData中,修改变量值即可达到切换数据库访问的目的,不需要客户端传给简单工厂中的方法。这样做解决抽象工厂模式的缺点1。

虽然摒弃了IFactory、SqlserverFactory、AccessFactory类。但是我们在新增一个表时会在AccessData类总增加相应的方法。新增一个数据库时需要在每个方法添加case “oracle” 语句。还是没有解决抽象工厂模式的缺点2。

反射技术+抽象工厂来改进缺点2

我们现在的的AccessData类时这样做的:使用switch case来判读,如果是sqlserver,就创建并返回sqlserver数据库相关的类,如果是access数据库,就创建并返回access数据库相关的类。如果我们能根据String类型的变量值去找数据库相关的类就好了。实现如下:

这是程序的包结构。

代码实现:

package fly.zxy.dhms.chapter15_8.factory;

import fly.zxy.dhms.chapter15_8.IDB.IDepartment;
import fly.zxy.dhms.chapter15_8.IDB.IUser;

public class DataAccess {

	private static String DB = "sqlserver";
	private static String packageBasePath = "fly.zxy.dhms.chapter15_8";

	public static IUser createUser(){
		String name = "User";
		IUser iu =null;
		iu = (IUser) ref( getPackagePath(name) );
		return iu;
	}

	public static IDepartment createDepartment(){
		String name = "Department";
		IDepartment dep = null;
		dep = (IDepartment) ref( getPackagePath(name) );
		return dep;
	}

	private static String getPackagePath(String className){
		//fly.zxy.dhms.chapter15_8.sqlserverDB
		//fly.zxy.dhms.chapter15_8.accessDB
		className = DB.substring(0,1).toUpperCase() + DB.substring(1, DB.length()) + className;
		String path = packageBasePath+"."+DB+"DB"+"."+className;
		return path;
	}

	private static Object ref(String className){
		Object obj = null;
		try {
			Class<? extends Object> cls = Class.forName(className);
			obj = cls.newInstance();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return obj;
	}
}

由于使用了反射技术,我们在增加表的时候只需要在AccessData类中增加对应的方法,而方法中没有了一堆的switch case分支判断。新增对一种数据库的访问,无需再更改每个方法增加case语句了。解决了抽象工厂模式的缺点二。其实从某种角度来说,所有在使用简单工厂的地方,都可以考虑用反射来去除switch或if。

我们发现还有点小瑕疵,我们需要修改DB变量的值类来实现数据库的切换,这样还是需要改动代码,需要重新编译。这个小瑕疵可以使用配置文件来搞定,就是讲DB变量的值从XML文件或properties文件中读取出来。

时间: 2024-12-24 08:20:55

第15章 就不能换DB吗?—抽象工厂模式的相关文章

[Python设计模式] 第15章 如何兼容各种DB——抽象工厂模式

github地址:https://github.com/cheesezh/python_design_patterns 题目 如何让一个程序,可以灵活替换数据库? 基础版本 class User(): """ 用户类,模拟用户表,假设只有ID和name两个字段 """ def __init__(self): self.id = None self.name = None class SqlServerUser(): ""&qu

就不能换DB吗? 抽象工厂模式

15.1 就不能换DB吗? 15.2 最基本的数据访问程序 15.3 用了工厂方法模式的数据访问程序 15.4 用了抽象工厂模式的数据访问程序 15.5 抽象工厂模式 15.6 抽象工厂模式的优点与缺点 15.7 用简单工厂来改进抽象工厂 15.8 用反射+抽象工厂的数据访问程序 15.9 用反射+配置文件的数据访问程序 15.10 无痴迷 不成功 原文地址:https://www.cnblogs.com/huangxuQaQ/p/11286815.html

第3章 抽象工厂模式(Abstract Factory)

原文 第3章 抽象工厂模式(Abstract Factory) 场景我们的系统要同时支持两个数据库  SqlServer 跟Oracle数据库  并且不同的环境要进行随时切换. 看下面的代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

设计模式(15)-----抽象工厂模式

抽象工厂模式(abstract factory) 定义 提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类. UML图 例子 在开发的过程难免会遇到更换数据库的事情,也就意味着会出现大量的代码修改.而使用抽象工厂模式,可以很好的避免修改的行为.现在有两个操作对象,一个是用户,一个是部门,都具有插入和查询的功能. POJO package com.csdhsm.pattemdesign.abstractfactory; /** * @Title: User.java * @Desc

第5章 创建型模式—抽象工厂模式

1. 抽象工厂的定义 (1)提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类 ①只需各要知道创建一系列对象的接口,而无需知道具体使用的是哪一个实现 ②这一系列对象是相关或相互依赖的,也就是说既要创建对象,还要约束它们之间的关系. ③一系列对象是构建新对象所需要的组成部分,并且对象之间相互有约赖.如电脑由CPU和主板等组成,但CPU的针脚数和主板提供的插口必须是匹配的,否则无法组装. (2)产品族和产品等级 ①产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等

[Python编程实战] 第一章 python的创建型设计模式1.1抽象工厂模式

注:关乎对象的创建方式的设计模式就是"创建型设计模式"(creational design pattern) 1.1 抽象工厂模式 "抽象工厂模式"(Abstract Factory Pattern)用来创建复杂的对象,这种对象由许多小对象组成,而这些小对象都属于某个特定的"系列"(family). 比如说,在GUI 系统里可以设计"抽象控件工厂"(abstract widget factory),并设计三个"具体子

笔记-大话设计模式-15 抽象工厂模式

抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合. Demo: interface IUser { void Insert(User user); User GetUser(int id); } class User { private int _id; public int Id { get { return _id; } set

大话设计模式C++实现-第15章-抽象工厂模式

一.UML图 二.概念 抽象方法模式(Abstract Factory):提供一个创建一系列相关或互相依赖对象的接口,而无需指定他们详细的类. 三.包括的角色 (1)抽象工厂 (2)详细工厂:包含详细工厂1和详细工厂2.详细工厂1用于生产详细产品A1和详细产品B1,详细工厂2用于生产详细产品A2和详细产品B2: (3)抽象产品:包含抽象产品A和抽象产品B. (4)详细产品:包含抽象产品A所相应的详细产品A1和A2.以及抽象产品B所相应的详细产品B1和B2. 说明:在<大话设计模式>中,上述的1

第2章 简单工厂模式(Sample Factory)

原文 第2章 简单工厂模式(Sample Factory) 一般用到的场景:对象多次被实例引用,切有可能会发生变化 拿我们的简单三层举例子 先定义dal层 1 2 3 4 5 6 7 8     class Dal     {         public void Delete()         {           //...          }             } 工厂类 1 2 3 4 5 6 7 8 9 10     class Factory     {