人物:大鸟,小菜
事件:一天晚上小菜回来得很晚,大鸟问原因,原来小菜是因为做完一个项目,项目以SQL Server为基础,但后面要改成Access,换数据库用时很久导致了加班很晚才回来,大鸟告诉小菜用抽象工厂模式可以帮他在下一次还要换数据库时,节省很多时间
抽象工厂模式:
1.先实现最基本的数据访问方式,阐述不足,引出改进点
2.接着用工厂方法模式进行改进,阐述剩下的不足,引出抽象工厂模式
3.介绍抽象工厂模式
4.结合抽象工厂模式实现案例
5.阐述抽象工厂模式优劣
需求:大鸟让小菜先实现最近本的数据访问
最近本的数据访问
用户类
@Data public class User { private int id; private String name; }
SqlServerUser类,用于操作User表(这里只有新增和选取一条记录的功能)
@Slf4j public class SqlServerUser { public void insert(User user) { log.info("在SQL Server中给User表增加一条记录"); } public User getUser(int id) { log.info("在SQL Server中根据id在User表得到一条记录"); return null; } }
客户端代码:
public static void main(String[] args) { User user = new User(); SqlServerUser su = new SqlServerUser(); su.insert(user); su.getUser(1); }
小菜:这就是我最开始写的,很简单。
大鸟:之所以不能换数据库,是因为SqlServerUser su = new SqlServerUser();这里使得su这个对象被框死在了Sql Server上,如果是灵活的,多态的,那么在执行su.insert()语句时,就不用考虑是在Sql Server还是Access上了
小菜:那我可以使用工厂方法模式,因为工厂方法模式是定义用来创建对象的接口,让子类决定实例化哪一个类,用工厂方法模式来封装new SqlServer所造成的变化
用了工厂方法模式的数据访问
代码结构图
UserService接口,用于客户端访问,解除与具体数据库访问的耦合:
public interface UserService { void insert(User user);
User getUser(int id); }
SqlServerUser类,用于访问SQL Server的user:
@Slf4j public class SqlServerUser implements UserService { @Override public void insert(User user) { log.info("在SQL Server中给User表增加一条记录"); } @Override public User getUser(int id) { log.info("在SQL Server中根据id在User表得到一条记录"); return null; } }
AccessUser类,用于访问Access的user:
@Slf4j public class AccessUser implements UserService { @Override public void insert(User user) { log.info("在Access中给User表增加一条记录"); } @Override public User getUser(int id) { log.info("在Access中根据id在User表得到一条记录"); return null; } }
Factory接口,定义一个创建访问User表对象的抽象的工厂接口:
public interface Factory { UserService createUser(); }
SqlServerFactory类,实现Factory接口,实例化SQLServerUser:
public class SqlServerFactory implements Factory { @Override public UserService createUser() { return new SqlServerUser(); } }
AccessFactory类,实现Factory类,实例化AccessUser:
public class AccessFactory implements Factory { @Override public UserService createUser() { return new AccessUser(); } }
客户端代码:
public static void main(String[] args) { User user = new User(); Factory factory = new SqlServerFactory(); UserService iu = factory.createUser(); iu.insert(user); iu.getUser(1); }
大鸟:很好,只有User类和User操作类时,是只需要工厂方法模式的,但显然数据库中有很多表,又该怎么做呢?我来介绍下抽象工厂模式就知道了
抽象工厂模式
1.概念:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
2.代码结构图:
其中:
(1)AbstractProductA和AbstractProductB是两个抽象产品,之所以抽象是因为它们都有可能有两种不同的实现
(2)ProductA1,ProductA2,ProductB1,ProductB2都是对抽象产品具体分类的实现()ProductA1,ProductA2可以分别理解为:SqlServerUser和AccessUser)
(3)Factory是一个抽象工厂的接口,里面应该包含所有产品创建的抽象方法
(4)而ConcreteFactory1和ConcreteFactory2就是具体的工厂了(例如:SqlServerFactory和AccessFactory)
结合抽象工厂模式进行数据访问
IDepartment接口,用于客户端的访问,解除与具体数据库访问的耦合:
public interface IDepartment { void insert(Department department);
Department getDepartment(int id); }
Department类:
@Data public class Department { private int id; private String name; }
SqlServerDepartment类,用于访问SQLServer:
@Slf4j public class SqlServerDepartment implements IDepartment { @Override public void insert(Department department) { log.info("在SQL Server中给Department表增加一条记录"); } @Override public Department getDepartment(int id) { log.info("在SQL Server中根据id得到Department表一条记录"); return null; } }
AccessDepartment类,用于访问Access:
@Slf4j public class AccessDepartment implements IDepartment { @Override public void insert(Department department) { log.info("在Access中给Department表增加一条记录"); } @Override public Department getDepartment(int id) { log.info("在Access中根据id得到Department表一条记录"); return null; } }
IFactory接口,用于定义一个创建访问User表对象的抽象的工厂接口:
public interface IFactory { UserService createUser(); IDepartment createDepartment(); }
SQLServerFactory类,用于实现IFactory接口,实例化SQLServerUser和SQLServerDepartment:
public class SqlServerFactory implements IFactory { @Override public UserService createUser() { return new SqlServerUser(); } @Override public IDepartment createDepartment() { return new SqlServerDepartment(); } }
AccessFactory类,用于实现IFactory接口,实例化AccessUser和AccessDepartment:
public class AccessFactory implements IFactory { @Override public UserService createUser() { return new AccessUser(); } @Override public IDepartment createDepartment() { return new AccessDepartment(); } }
客户端代码:
public static void main(String[] args) { User user = new User(); Department dept = new Department(); IFactory factory = new AccessFactory(); UserService iu = factory.createUser(); iu.insert(user); iu.getUser(1); IDepartment id = factory.createDepartment(); id.insert(dept); id.getDepartment(1); }
结果显示如下:
在Access中给User表增加一条记录
在Access中根据id在User表得到一条记录
在Access中给Department表增加一条记录
在Access中根据id得到Department表一条记录
抽象工厂模式的优缺点
优点:
1.易于交换产品系列,由于工厂类在一个应用中只需要在初始化的时候出现过一次(如:IFactory factory = new AccessFactory()),这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置
2.它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中
缺点:
1.虽然切换数据库很方便,但如果要加新的功能,比如加一个项目表Project,这时至少要增加IProject,SqlServerProject,AccessProject,还需要改IFactory,SqlServerFactory,AccessFactory才能实现,很麻烦
2.还有就是虽然IFactory factory = new AccessFactory()这样声明一次很容易改,但如果有100个地方用到,那不是要一个一个改?
用简单工厂改进抽象工厂
代码结构图:
DataAccess类:
public class DataAccess { private static String db = "SqlServer"; //private static String db = "Access"; public static UserService createUser() { UserService 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; } }
客户端代码:
public static void main(String[] args) { User user = new User(); Department dept = new Department(); UserService iu = DataAccess.createUser(); iu.insert(user); iu.getUser(1); IDepartment id = DataAccess.createDepartment(); id.insert(dept); id.getDepartment(1); }
小结:这样抛弃了IFactory,SqlServerFactory,AccessFactory三个工厂类,用DataAccess类取而代之。由于事先设置了db值(SQLServer或Access),所以在客户端只需要DataAccess.createUser()或者DataAccess.createDepartment()就行,客户端没有出现SqlServer或Access字样,从而达到了解耦的目的。
但这样还是存在一个问题(在简单工厂模式,和简单工厂模式与工厂方法模式结合时都存在的问题),如果要新增Oracle,就需要在DataAccess中的switch case中改动了,由此为了解决这个问题引入下面内容。
用反射+抽象工厂模式的数据访问
暂存
用反射+配置文件实现的数据访问
暂存