文章目录
- 一 概述
- 二 简单工厂
- 三 工厂方法
- 四 抽象工厂
- 五 反射+配置文件优化简单工厂
一 概述
工厂模式:
实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
其实设计模式和面向对象设计原则都是为了使得开发项目更加容易扩展和维护,解决方式就是一个“分工”。
遵循开闭原则、迪米特原则和依赖倒转原则。
根据迪米特法则,工厂类可以看做一个中介。
核心本质:实例化对象时,用工厂方法代替 new 操作。
工厂模式的分类:
简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改工厂的代码,违反了开闭原则。)
工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品,仅需要扩展对应的工厂类,遵循开闭原则。)
抽象工厂模式:用来生产不同产品族的全部产品。(是对工厂方法的扩展,支持增加产品族,需要扩展工厂的抽象类和实现类中的代码。)
二 简单工厂
它存在的目的很简单:定义一个用于创建对象的工厂类,把调用者与创建者分离。
例子:(描述代替UML类图)
接口:Car;两个实现类:Audi BYD
Client:想要造Audi BYD这两个类的对象
简单工厂:CarFactory 只有一个静态方法public static Car getCar(String name)
//接口
interface Car{
void run();
}
//接口的实现类
class Audi implements Car{
public void run() {
System.out.println("奥迪在跑");
} }
class BYD implements Car{
public void run() {
System.out.println("比亚迪在跑");
} }
//工厂类
class CarFactory {
public static Car getCar(String type) {
if ("奥迪".equals(type)) {
return new Audi();
} else if ("比亚迪".equals(type)) {
return new BYD();
} else {
return null; } }
//客户端
public class Client {
public static void main(String[] args) {
//创建对象时,已经不需要直接访问目标类了。
//但是要显式输入目标对象类型。
Car a = CarFactory.getCar("奥迪");
a.run();
Car b = CarFactory.getCar("比亚迪");
b.run();
} }
小结:
简单工厂模式也叫静态工厂模式,就是工厂类一般是使用静态方法,通过接收的参数的不同来返回不同的实例对象。
缺点:对于增加新产品,不修改代码的话,是无法扩展的。违反了开闭原则。
三 工厂方法
工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式只有一个工厂类。
而工厂方法模式有一组实现了相同接口的工厂类。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
设计思路:
工厂方法模式先创建一个工厂接口,提供抽象方法。
然后每个实现类对应一个实现工厂接口的具体工厂类。
下图可以形象地描述该方法的思路:
例子:(业务逻辑与数据访问解耦,描述代替UML类图)
User表 - 存储在不同类型的数据库中
User表的数据库访问接口及两个抽象方法:IUserDao{insert;getUserEntity};
两种数据库访问接口的具体实现类:MysqlUserDao SqlServerUserDao
Client:想要造这两个数据库访问接口 实现类的对象,操作不同数据库中的User表。
工厂方法中的工厂接口:DaoFactory 只有一个抽象方法 IUserDao getUserDao();用于造对象。
工厂方法中每个数据库访问接口 实现类对应的具体工厂类:MysqlDaoFactory SqlServerDaoFactory
//User表 - JavaBean
public class UserEntity {
private int id;
private String name;
public UserEntity() {
}
public UserEntity(int id, String name) {
this.id = id;
this.name = 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;
}
}
//User表的数据库访问接口及两个抽象方法
public interface IUserDao {
//插入
void insert(UserEntity userEntity);
//查询
UserEntity getUserEntity(int id);
}
//表User dao接口的 Mysql实现类
public class MysqlUserDao implements IUserDao {
public MysqlUserDao() {
}
@Override
public void insert(UserEntity userEntity) {
System.out.println("在mysql中给User表添加一条记录");
}
@Override
public UserEntity getUserEntity(int id) {
System.out.println("在mysql中根据id查询User表的一条记录");
return null;
}
}
//表User dao接口的 sqlServer实现类
public class SqlServerUserDao implements IUserDao {
public SqlServerUserDao() {
}
@Override
public void insert(UserEntity userEntity) {
System.out.println("在sqlServer中给User表添加一条记录");
}
@Override
public UserEntity getUserEntity(int id) {
System.out.println("在sqlServer中根据id查询User表的一条记录");
return null;
}
}
//工厂方法中的工厂接口,每个数据库对应一个工厂,后面每个工厂都实现该接口
public interface DaoFactory {
IUserDao getUserDao();
}
//工厂方法中mysql数据库访问接口 实现类(Mysql工厂)
public class MysqlDaoFactory implements DaoFactory {
@Override
public IUserDao getUserDao() {
return new MysqlUserDao();
}
}
//工厂方法中SqlServer数据库访问接口 实现类(SqlServer工厂)
public class SqlServerDaoFactory implements DaoFactory {
@Override
public IUserDao getUserDao() {
return new SqlServerUserDao();
}
}
//Client:创建这两个数据库访问接口 实现类的对象,操作不同数据库中的User表
public class ClientFactoryMethod {
public static void main(String[] args) {
//创建MysqlDaoFactory工厂对象,且只需创建一次。
MysqlDaoFactory mysqlDaoFactory = new MysqlDaoFactory();
//利用工厂创建IUserDao类型的对象,而不是MysqlUserDao类型的对象。
//通过抽象接口类型对象操作具体业务,实现了客户端与数据源的解耦。
IUserDao userDao = mysqlDaoFactory.getUserDao();
userDao.getUserEntity(1);
userDao.insert(new UserEntity());
}
}
四 抽象工厂
其实和工厂方法差别不大,是对工厂方法的扩展,仍然是有一个工厂接口,但是里面有多个抽象方法,用于创建不同的产品族。
抽象工厂模式和工厂方法模式的区别就在于需要创建对象的复杂程度上。
而且抽象工厂模式是三个里面最为抽象、最具一般性的。
抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象。
而且使用抽象工厂模式还要满足一下条件:
- 系统中有多个产品族,而系统一次只可能消费其中一族产品。
- 同属于同一个产品族的产品可以同时使用。
例子:(业务逻辑与数据访问解耦,描述代替UML类图)
User表 - 存储在不同类型的数据库中
Department表 - 存储在不同类型的数据库中
User表的数据库访问接口及两个抽象方法:IUserDao{insert;getUserEntity};
两种数据库访问接口对于User表的具体实现类:MysqlUserDao SqlServerUserDao
Department表的数据库访问接口及两个抽象方法:IDepartmentDao{}
两种数据库访问接口对于Department表的具体实现类:MysqlDepartmentDao SqlServerDepartmentDao
产品族:Mysql的所有dao实现类可以认为是一个产品族,sqlServer的所有dao实现类可以认为是一个产品族。
Client:想要造一个产品族(同一个数据库)对于不同表的访问接口 实现类的对象,操作同一个数据库中的User表和Department表。
抽象工厂中的工厂接口:DaoAbstractFactory相比于工厂方法的工厂接口新增了一个抽象方法 IDepartmentDao getDepartmentDao();。
抽象工厂中每个数据库访问接口 实现类对应的具体工厂类(产品族):MysqlDaoFactory SqlServerDaoFactory,也都实现了新增的方法。
//Department表
public class DepartmentEntity {
private int id;
private String name;
public DepartmentEntity() {
}
public DepartmentEntity(int id, String name) {
this.id = id;
this.name = 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;
}
@Override
public String toString() {
return "DepartmentEntity{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
//表Department的dao接口
public interface IDepartmentDao {
//插入
void insert(DepartmentEntity departmentEntity);
//查询
DepartmentEntity getDepartmentEntity(int id);
}
//表Department dao接口的 Mysql实现类
public class MysqlDepartmentDao implements IDepartmentDao {
public MysqlDepartmentDao() {
}
@Override
public void insert(DepartmentEntity departmentEntity) {
System.out.println("在mysql中给department表添加一条记录");
}
@Override
public DepartmentEntity getDepartmentEntity(int id) {
System.out.println("在mysql中根据id查询department表的一条记录");
return null;
}
}
//表User dao接口的 sqlServer实现类
public class SqlServerDepartmentDao implements IDepartmentDao {
public SqlServerDepartmentDao() {
}
@Override
public void insert(DepartmentEntity departmentEntity) {
System.out.println("在sqlServer中给department表添加一条记录");
}
@Override
public DepartmentEntity getDepartmentEntity(int id) {
System.out.println("在sqlServer中根据id查询department表的一条记录");
return null;
}
}
//其实就是对DaoFactory的扩展
//之前只有一个方法,只用来获取User表的Dao,这里扩展了获取Department表的dao.
public interface DaoAbstractFactory {
IUserDao getUserDao();
IDepartmentDao getDepartmentDao();
}
//DaoAbstracFactory的mysql实现类 - mysql产品族的工厂
public class MysqlDaoAbstractFactory implements DaoAbstractFactory {
@Override
public IUserDao getUserDao() {
return new MysqlUserDao();
}
@Override
public IDepartmentDao getDepartmentDao() {
return new MysqlDepartmentDao();
}
}
//DaoAbstracFactory的SqlServer实现类 - SqlServer产品族的工厂
public class SqlServerDaoAbstractFactory implements DaoAbstractFactory {
@Override
public IUserDao getUserDao() {
return new SqlServerUserDao();
}
@Override
public IDepartmentDao getDepartmentDao() {
return new SqlServerDepartmentDao();
}
}
//Client:想要造一个产品族(同一个数据库)对于不同表的访问接口 实现类的对象,操作同一个数据库中的User表和Department表。
public class ClientAbstractFactory {
public static void main(String[] args) {
//创建Mysql产品族的工厂对象
//创建一个需要访问的数据源类型的工厂对象,例如mysql,且只需创建一次。
MysqlDaoAbstractFactory mysqlDaoAbstractFactory = new MysqlDaoAbstractFactory();
//获得抽象类型的Dao,然后操作对象
//然后获得抽象接口类型 IUserDao类型或者IDepartmentDao类型 的对象,屏蔽了具体的数据源类型。
//通过 抽象接口类型的对象 操作数据,实现了客户端与数据源的解耦。
IUserDao userDao = mysqlDaoAbstractFactory.getUserDao();
userDao.insert(new UserEntity());
userDao.getUserEntity(0);
IDepartmentDao departmentDao = mysqlDaoAbstractFactory.getDepartmentDao();
departmentDao.insert(new DepartmentEntity());
departmentDao.getDepartmentEntity(0);
}
}
简单工厂模式、工厂方法模式、抽象工厂模式真正的避免了代码的改动了吗?
在简单工厂模式中,新产品的加入要修改工厂角色中的判断语句;
而在工厂方法模式/抽象工厂模式中,要么将判断逻辑留在抽象工厂角色中,要么在客户程序中将具体工厂角色写死。
面对这种情况,Java 的反射机制与配置文件的巧妙结合突破了限制。
五 反射+配置文件优化简单工厂
编程方式:依赖注入,用反射来减少if switch语句。
注:在所有用简单工厂的地方,都可以考虑用反射去除switch if,动态获取。
我们利用反射+配置文件只创建一个简单工厂,便可以实现第四章中客户端的需求。
Client:想要造一个产品族(同一个数据库)对于不同表的访问接口 实现类的对象,操作同一个数据库中的User表和Department表。
//配置文件db.properties
dBName=Mysql
//用反射+配置文件优化的简单工厂模式,用于创建指定db的dao类
//使用ClassLoader的getResourceAsStream加载配置文件时,配置文件应放在当前工程的src目录下
public class BaseDaoFactoryWProperty {
private static String prefix = "main.factorymodel.dao.";
private static String dBName;
private static String suffix;
static {
InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
dBName = properties.getProperty("dBName");
}
/**
*无需传参,从配置文件中加载配置的数据库名称
* @param
* @return
*/
public static IUserDao getUserDao(){
StringBuilder className = new StringBuilder();
suffix = "UserDao";
//获取全类名
StringBuilder userDaoClassName = className.append(prefix).append(dBName).append(suffix);
try {
//获取需要创建的UserDao的类对象实例
Class<IUserDao> aClass = (Class<IUserDao>) Class.forName(userDaoClassName.toString());
IUserDao userDao = aClass.newInstance();
return userDao;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static IDepartmentDao getDepartmentDao(){
StringBuilder className = new StringBuilder();
suffix = "DepartmentDao";
//获取全类名
StringBuilder userDaoClassName = className.append(prefix).append(dBName).append(suffix);
try {
//获取需要创建的DepartmentDao的类对象实例
Class<IDepartmentDao> aClass = (Class<IDepartmentDao>) Class.forName(userDaoClassName.toString());
IDepartmentDao departmentDao = aClass.newInstance();
return departmentDao;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
//造一个产品族(同一个数据库)对于不同表的访问接口 实现类的对象,操作同一个数据库中的User表和Department表。
//使用简单工厂创建对象,无需输入数据库名称,在配置文件中设置即可,面向配置编程。
public class ClientExampleFactoryWProperty {
public static void main(String[] args) {
IUserDao userDao = BaseDaoFactoryWProperty.getUserDao();
userDao.insert(new UserEntity());
userDao.getUserEntity(0);
IDepartmentDao departmentDao = BaseDaoFactoryWProperty.getDepartmentDao();
departmentDao.insert(new DepartmentEntity());
departmentDao.getDepartmentEntity(0);
}
}
在上述的实现中,
每增加一个产品族时(按照指定位置放置该产品族的dao),无需修改工厂代码,只需在配置文件中配置产品族即可。
每新增一个表时,只需扩展一个获取该表dao的方法即可。
仍然可以优化,把获取表的dao的方法中重复的部分提取出来。