1、最基本的数据访问程序
用‘新增用户’和‘得到用户为例’
用户类,假设只有ID和Name两个字段
/**
* 用户类
* Created by ZhuPengWei on 2018/1/26.
*/
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;
}
}
SqlServerUser类:用户操作User表,假设只有新增用户和得到用户的方法,其余的方法以及具体的SQL语句省略。
/**
* 操作user表
* Created by ZhuPengWei on 2018/1/26.
*/
public class SqlServerUser {
public void insert(User user) {
System.out.println("在SQLServer中给User表增加一条记录");
}
public User getUser(int id) {
System.out.println("在SQLSever中根据ID得到User表一条记录");
return null;
}
}
客户端代码
/**
* 客户端
* Created by ZhuPengWei on 2018/1/26.
*/
public class Client {
public static void main(String[] args) {
User user = new User();
// 与SQLServer耦合
SqlServerUser sqlServerUser = new SqlServerUser();
// 插入用户
sqlServerUser.insert(user);
// 得到id为1的用户
User u = sqlServerUser.getUser(1);
}
}
新增需求:如果要使得用户不使用SQL SERVER数据库而使用MySQL
上述的代码是不满足需求的,原因就在于 SqlServerUser sqlServerUser = new SqlServerUser()使得sqlServerUser这个对象被框死在SQL Server上了。如果这里是
灵活的,专业点的说法是多态的,那么在执行sqlServerUser.insert(user)和sqlServerUser.getUser(1)时就不用考虑是在用SQL Server还是用MySQL。
所以,可以使用工厂方法模式来封装new SqlServer()所造成的变化
工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类
2、用工厂方法模式的数据访问程序
代码结构图
IUser接口:用于客户端访问,解除与具体数据库访问的耦合。
/**
* 用于客户端访问,解除与具体数据库访问的耦合
* Created by ZhuPengWei on 2018/1/26.
*/
public interface IUser {
void insert(User user);
User getUser(int id);
}
SqlServerUser类:用于访问SqlServer的User。
/**
* 用于访问SqlServer的User
* Created by ZhuPengWei on 2018/1/26.
*/
public class SqlServerUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在SQL SERVER中给User表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在SQL SERVER中根据Id查询User表一条记录");
return null;
}
}
MySqlUser类:用于访问MySql的User。
/**
* 用于访问MySQL的User
* Created by ZhuPengWei on 2018/1/26.
*/
public class MySqlUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在MySQL中给User表增加一条记录");
}
@Override
public User getUser(int id) {
System.out.println("在MySQL中根据Id查询User表一条记录");
return null;
}
}
IFactory接口,定义一个创建访问User表对象的抽象的工厂接口。
/**
* 定义一个创建访问User表对象的抽象的工厂接口
* Created by ZhuPengWei on 2018/1/26.
*/
public interface IFactory {
IUser createUser();
}
SqlServerFactory类,实现IFactory接口,实例化SqlServerUser。
/**
* 实例化SqlServerUser。
* Created by ZhuPengWei on 2018/1/26.
*/
public class SqlServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
}
MySqlFactory:实现IFactory接口,实例化MySqlUser。
/**
* 实例化MySqlUser。
* Created by ZhuPengWei on 2018/1/26.
*/
public class MySqlFactory implements IFactory {
@Override
public IUser createUser() {
return new MySqlUser();
}
}
客户端代码
/**
* 客户端
* Created by ZhuPengWei on 2018/1/26.
*/
public class Client {
public static void main(String[] args) {
User user = new User();
// 若要更改成MySql数据库,只需要将本句改成 IFactory factory = new MySqlFactory();
IFactory factory = new SqlServerFactory();
IUser iu = factory.createUser();
iu.insert(user);
iu.getUser(1);
}
}
3、用抽象工厂模式的数据访问程序
再次的修改代码,增加了关于部门表的处理
代码结构图
此时IFactory
/**
* 定义一个创建访问User表对象的抽象的工厂接口
* Created by ZhuPengWei on 2018/1/26.
*/
public interface IFactory {
IUser createUser();
IDepartment createDepartment();
}
其余略过
客户端代码
/**
* 客户端
* Created by ZhuPengWei on 2018/1/26.
*/
public class Client {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
// IFactory factory=new SqlServerFactory();
IFactory factory = new MySqlFactory();
IUser iUser = factory.createUser();
iUser.insert(user);
iUser.getUser(1);
IDepartment iDepartment = factory.createDepartment();
iDepartment.insert(department);
iDepartment.getDepartment(1);
}
}
抽象工厂模式的优点与缺点
优点
1、抽象工厂模式最大的好处便是交换产品系列,由于具体工厂类,例如IFactory factory = new MySqlFactory(),在一个应用中只需要在初始化的时候出现一次,
这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂就可以使用不通的产品配置
2、它让具体的创建实例过程与客户端分离,客户端是通过他们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点
抽象工厂模式可以很方便地切换两个数据库访问的代码,但是比如说要增加一张项目表(Project),那至少要增加三个类IProject、SqlServerProject、MySqlProject,还
需要更改IFactory、SqlServerFactory和MySqlFactory才可以完全实现。
4、用简单工厂来改进抽象工厂
编程是门艺术,这样大批量的改动,显然是十分丑陋的做法。
去除IFactory,SqlServerFactory和MySqlFactory三个工厂类,取而代之的是DataAccess类,用一个简单的工厂来实现
/**
* 替代IFactory,SqlServerFactory和MySqlFactory
* 简单工厂模式
* Created by ZhuPengWei on 2018/1/26.
*/
public class DataAccess {
// 数据库名称,可替换成SQLSERVER
private static final String DB = "MYSQL";
public static IUser createUser() {
IUser result = null;
switch (DB) {
case "MYSQL":
result = new MySqlUser();
break;
case "SQLSERVER":
result = new SqlServerUser();
break;
}
return result;
}
public static IDepartment createDepartment() {
IDepartment result = null;
switch (DB) {
case "MYSQL":
result = new MySqlDepartment();
break;
case "SQLSERVER":
result = new SqlServerDepartment();
break;
}
return result;
}
}
这样的改进确实是比之前的代码更近一步了,客户端已经不再受改动数据库访问的影响,但是如果我需要增加Oracle数据库访问,本来抽象工厂只增加一个OracleFactory工厂类就可以了,
现在就需要在DataAccess类中每个方法的switch中加case了。
使用反射+配置文件实现数据访问程序,改进简单工厂模式
/**
* 用反射+配置文件实现数据访问程序
* 简单工厂模式
* Created by ZhuPengWei on 2018/1/26.
*/
public class DataAccess {
private static final ResourceBundle rb;
// 数据库名称,根据配置文件读取
private static final String DB;
static {
// 获得资源包
rb = ResourceBundle.getBundle("pers/zpw/AbstractFactoryPattern/config");
DB = rb.getString("DB");
}
public static IUser createUser() {
try {
return (IUser) Class.forName("pers.zpw.AbstractFactoryPattern.version3." + DB + "User").newInstance();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
public static IDepartment createDepartment() {
try {
return (IDepartment) Class.forName("pers.zpw.AbstractFactoryPattern.version3." + DB + "Department").newInstance();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
所有用在简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。