在装饰者模式中我们讲到一条设计原则,即我们应该为对象间的松耦合而努力。
在初始化对象时,除了使用new操作符之外,还有更多制造对象的方法,实例化这个活动不应该总是公开的进行,初始化经常造成“耦合”问题,我们不希望这样,而工厂模式能够将我们从复杂的依赖中解脱出来。
工厂模式
- 简单工厂模式
- 工厂方法
- 抽象工厂模式
简单工厂模式
准确的说,简单工厂其实不是一个设计模式,反而比较像是一种编程习惯
- 工厂客户:持有工厂的依赖,使用工厂生产出来的产品,不关系产品的具体生产过程
- 简单工厂:生产产品,即负责产品的实例化
- 抽象产品:产品类接口
- 具体产品:具体产品实现类
例子
现在有家披萨店,里面有奶酪披萨(cheese pizza),意大利香肠披萨(pepperoni pizze),素披萨(veggie pizza)这三种披萨,对外提供一个预定披萨的方法(orderPizza),我们来看下非工厂模式下会如何实现:
package factory;
/**
* 披萨店,提供预定披萨的做法
*/
public class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza = null;
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("pepperoni")){
pizza = new PepperoniPizza();
}else if(type.equals("veggie")){
pizza = new VeggiePizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
如果这家店现在上新了新披萨,或者说不做某种披萨了,那么我们就会对这段代码将作出大量改动,稍微好一点的办法就是增加一个配置文件,里面放着我们所需要做的披萨的名称,通过读取配置文件来制作披萨,然后用一个父类的披萨指向它,利用多态执行披萨的方法。
这两种做法都存在着显而易见的问题,即
我们没有对变化的部分封装
那么接下来我们就使用简单工厂来实现这个功能:
package factory;
/**
* 简单披萨工厂
*/
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("veggie")){
pizza = new PepperoniPizza();
}else if(type.equals("epperoni")){
pizza = new VeggiePizza();
}
return pizza;
}
}
package factory;
/**
* 披萨店,提供预定披萨的做法
*/
public class PizzaStore {
private SimplePizzaFactory pizzaFactory = new SimplePizzaFactory();
public Pizza orderPizza(String type){
Pizza pizza = pizzaFactory.createPizza(type);
// if(type.equals("cheese")){
// pizza = new CheesePizza();
// }else if(type.equals("pepperoni")){
// pizza = new PepperoniPizza();
// }else if(type.equals("veggie")){
// pizza = new VeggiePizza();
// }
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
我们看到,披萨的实例化功能交给了这个工厂类实现,需要改动的话只需到工厂类中去改动,披萨店不再关系披萨的具体实例化过程,它只知道它拿到的是“一份披萨”,但缺点就是,改动依旧需要到工厂类中去改。
进阶
针对这种需要到工厂类中去改动的做法,事实上,我们可以通过反射来解决。
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import Pizza;
public class PizzaFactory2 {
public static Car getPizza() {
Pizza pizza = null;
String name = null;
try {
XMLConfiguration config = new XMLConfiguration("pizza.xml");
name = config.getString("factory2.class");
} catch (ConfigurationException ex) {
LOG.error("Parsing xml configuration file failed", ex);
}
try {
Pizza = (Pizza)Class.forName(name).newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
}
return pizza;
}
}
简单工厂在Java中的典型使用
简单工厂模式在JDK中最典型的应用要数JDBC了。可以把关系型数据库认为是一种抽象产品,各厂商提供的具体关系型数据库(MySQL,PostgreSQL,Oracle)则是具体产品。DriverManager是工厂类。应用程序通过JDBC接口使用关系型数据库时,并不需要关心具体使用的是哪种数据库,而直接使用DriverManager的静态方法去得到该数据库的Connection。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JDBC {
private static final Logger LOG = LoggerFactory.getLogger(JDBC.class);
public static void main(String[] args) {
Connection conn = null;
try {
Class.forName("org.apache.hive.jdbc.HiveDriver");
conn = DriverManager.getConnection("jdbc:hive2://127.0.0.1:10000/default");
PreparedStatement ps = conn.prepareStatement("select count(*) from test.test");
ps.execute();
} catch (SQLException ex) {
LOG.warn("Execute query failed", ex);
} catch(ClassNotFoundException e) {
LOG.warn("Load Hive driver failed", e);
} finally {
if(conn != null ){
try {
conn.close();
} catch (SQLException e) {
// NO-OPT
}
}
}
}
}
工厂方法
如简单工厂模式直接使用静态工厂方法创建产品对象不同,在工厂方法,客户端通过实例化具体的工厂类,并调用其创建实例接口创建具体产品类的实例。
- 客户:不再赘述
- 抽象产品:即一类产品族
例子
import ouc.lpc.dao.IUserDao;
import ouc.lpc.factory.IDaoFactory;
import ouc.lpc.factory.MySQLDaoFactory;
public class Client {
public static void main(String[] args) {
IDaoFactory factory = new MySQLDaoFactory();
IUserDao userDao = factory.createUserDao();
userDao.getUser("admin");
}
}
即现在一种产品的实例化由一类工厂来实现。
但是如果我们需要多种产品的时候,实现多个工厂类将带来代码的迅速臃肿庞大。
抽象工厂
抽象工厂模式(Factory Method Pattern)中,抽象工厂提供一系列创建多个抽象产品的接口,而具体的工厂负责实现具体的产品实例。抽象工厂模式与工厂方法模式最大的区别在于抽象工厂中每个工厂可以创建多个种类的产品。
例子
import ouc.lpc.bean.Product;
import ouc.lpc.bean.User;
import ouc.lpc.dao.role.IRoleDao;
import ouc.lpc.dao.user.IUserDao;
import ouc.lpc.dao.user.product.IProductDao;
import ouc.lpc.factory.IDaoFactory;
import ouc.lpc.factory.MySQLDaoFactory;
public class Client {
public static void main(String[] args) {
IDaoFactory factory = new MySQLDaoFactory();
IUserDao userDao = factory.createUserDao();
User user = new User();
user.setUsername("demo");
user.setPassword("demo".toCharArray());
userDao.addUser(user);
IRoleDao roleDao = factory.createRoleDao();
roleDao.getRole("admin");
IProductDao productDao = factory.createProductDao();
Product product = new Product();
productDao.removeProduct(product);
}
}
上例是J2EE开发中常用的DAO(Data Access Object),操作对象(如User和Role,对应于数据库中表的记录)需要对应的DAO类。
在实际项目开发中,经常会碰到要求使用其它类型的数据库,而不希望过多修改已有代码。因此,需要为每种DAO创建一个DAO接口(如IUserDao,IRoleDao和IProductDao),同时为不同数据库实现相应的具体类。
调用方依赖于DAO接口而非具体实现(依赖倒置原则),因此切换数据库时,调用方代码无需修改。
这些具体的DAO实现类往往不由调用方实例化,从而实现具体DAO的使用方与DAO的构建解耦。实际上,这些DAO类一般由对应的具体工厂类构建。调用方不依赖于具体工厂而是依赖于抽象工厂(依赖倒置原则,又是依赖倒置原则)。
每种具体工厂都能创建多种产品,由同一种工厂创建的产品属于同一产品族。例如PostgreSQLUserDao,PostgreSQLRoleDao和PostgreSQLProductDao都属于PostgreSQL这一产品族。
切换数据库即是切换产品族,只需要切换具体的工厂类。如上文示例代码中,客户端使用的MySQL,如果要换用Oracle,只需将MySQLDaoFactory换成OracleDaoFactory即可。
源码在这里:我的github地址
其他设计模式:设计模式学习笔记