设计模式之工厂模式
工厂模式案例
工人制造汽车的过程中需要组装汽车的零部件,然后生产出来汽车,不同品牌的汽车组装过程大同小异,是不是可以类比软件开发过程呢。工人组装汽车的过程涉及三个对象,分别是工人、机器以及汽车品牌。工人类似于Client,机器就相当于工厂,负责组装汽车零部件生产汽车,不同品牌的汽车就是产品。那么对应的类图就如下所示。
所有汽车的总称
public interface Car {
public void assemble();
}
public class Mercedes implements Car {
public void assemble() {
System.out.println("制造了奔驰汽车");
}
}
public class BMW implements Car {
public void assemble() {
System.out.println("制造了宝马汽车");
}
}
public class AUDI implements Car {
public void assemble() {
System.out.println("制造了奥迪汽车");
}
}
工厂类
public abstract class AbstractCarFactory {
public abstract <T extends Car> T createCar(Class<T> c);
}
public class CarFactory {
public <T extends Car> T createCar(Class<T> c) {
Car car = null;
try {
car = (T) Class.forName(c.getName()).getDeclaredConstructor().newInstance();
} catch (Exception e) {
System.out.println("制造失败!");
}
return (T)car;
}
}
工人类
public class Worker {
public static void main(String[] args) {
AbstractCarFactory machine = new CarFactory();
Car mercedes = machine.createCar(Mercedes.class);
mercedes.assemble();
Car bmw = machine.createCar(BMW.class);
bmw.assemble();
Car audi = machine.createCar(AUDI.class);
audi.assemble();
}
}
这样汽车的建造就完成了。
工厂模式定义
工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法是一个类的实例化延迟到其子类
工厂模式的通用类图如下图所示:
在工厂方法模式中,抽象产品类Product负责定义产品的共性,实现对事物最抽象的定义;Creator为抽象创建类,也就是抽象工厂,具体如何创建产品类是由具体的实现工厂 ConcreteCreator完成的。
工厂模式的优点
- 良好的封装性,代码结构清晰。一个对象创建是有条件约束的,如一个调用者需要一个具体的产品对象,只要知道这个产品的类名或者限定类名就可以了。降低模块间的耦合
- 扩展性非常优秀,在增加产品类的情况下只要适当地修改具体的工厂类或扩展一个工厂类就可以适应这种变化。
- 屏蔽产品类。无论产品类如何变化,调用者都不需要关心,它只需要关心产品的接口,只要接口保持不变,系统中的上层模块就不要发生变化。因为产品类的实例化工作是由工厂类负责的,一个产品对象具体由哪一个产品生成是由工厂类决定的。
- 工厂模式是典型的解耦框架,高层模块只需要知道产品的抽象类,其他的实现类都不用关心。符合迪米特法则,不需要的就不交流;也符合依赖倒置原则,只依赖产品类的抽象;也符合里氏替换原则,使用子类替换父类。
工厂模式的缺点
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
工厂模式使用场景
- 工厂模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以 使用,但是需要慎重地考虑是否要增加一个工厂类进行管理,增加代码的复杂度
- 需要灵活的、可扩展的框架时,可以考虑采用工厂方法模式。万物皆对象,那万物也就皆产品类,例如需要设计一个连接邮件服务器的框架,有三种网络协议可供选择: POP3、IMAP、HTTP,我们就可以把这三种连接方法作为产品类,定义一个接口如 IConnectMail,然后定义对邮件的操作方法,用不同的方法实现三个具体的产品类(也就是连接方式)再定义一个工厂方法,按照不同的传入条件,选择不同的连接方式。如此设计, 可以做到完美的扩展,如某些邮件服务器提供了WebService接口,很好,我们只要增加一个 产品类就可以了
- 可以使用在测试驱动开发的框架下
- 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
工厂模式扩展
简单工厂模式
把最初制造汽车的案例简化一下,制造汽车只需要一个工厂,没有必要把它生产出来,使用静态方法就可以了,这样可以把上例中的AbstractCarFactory去掉,新的类图如下图所示:
我们直接在CarFactory中把createCar设置为静态方法,简化类创建的过程,这样变更的源码仅仅是Worker和CarFactory。
public class CarFactory {
public static <T extends Car> T createCar(Class<T> c) {
Car car = null;
try {
car = (T) Class.forName(c.getName()).getDeclaredConstructor().newInstance();
} catch (Exception e) {
System.out.println("制造失败!");
}
return (T)car;
}
}
工人的调用方式也发生了变化
public class Worker {
public static void main(String[] args) {
Car mercedes = CarFactory.createCar(Mercedes.class);
mercedes.assemble();
Car bmw = CarFactory.createCar(BMW.class);
bmw.assemble();
Car audi = CarFactory.createCar(AUDI.class);
audi.assemble();
}
}
最终的运行结果没有变化,但我们的类图变得简单了,调用者也比较简单,该模式称为简单工厂模式。
在实际项目中,采用该方法的案例还是比较多的,其缺点是工厂类的扩展比 较困难,不符合开闭原则,但它仍然是一个非常实用的设计模式。
升级为多个工厂类
当我们做一个比较复杂的项目时,如果所有的产品类都放到一个工厂方法中进行初始化会使代码结构不清晰。
考虑到需要清晰的结构,我们可以为每一个铲平定义一个创造者,由调用者决定与哪个工厂方法关联。还拿造汽车的例子来说,其对应的类图如下图所示
每个品牌的汽车都对应一个创建者,每个创建者都独立的创建对应的产品对象,非常符合单一职责原则,那么对应的类的变化是什么呢?
public abstract class AbstractCarFactory {
public abstract Car createCar();
}
public class MercedesFactory extends AbstractCarFactory {
public Car createCar() {
return new Mercedes();
}
}
public class BMWFactory extends AbstractCarFactory {
public Car createCar() {
return new BMW();
}
}
public class AUDIFactory extends AbstractCarFactory {
public Car createCar() {
return new AUDI();
}
}
调用过程
public static void main(String[] args) {
Mercedes mercedes = (new MercedesFactory()).createCar();
mercedes.assemble();
}
替代单例模式
public class Singleton {
private Singleton() {}
public void doSomething() {}
}
public class SingletonFactory {
private static Singleton singleton;
static{
try {
Class cl= Class.forName(Singleton.class.getName());
//获得无参构造
Constructor constructor=cl.getDeclaredConstructor();
//设置无参构造是可访问的
constructor.setAccessible(true);
//产生一个实例对象
singleton = (Singleton)constructor.newInstance();
} catch (Exception e) {
//异常处理
}
}
public static Singleton getSingleton(){
return singleton;
}
}
通过获得类构造器,然后设置访问权限,生成一个对象,然后提供外部访问,保证内存中的对象唯一。
以上通过工厂方法模式创建了一个单例对象,该框架可以继续扩展,在一个项目中可以 产生一个单例构造器,所有需要产生单例的类都遵循一定的规则(构造方法是private),然后通过扩展该框架,只要输入一个类型就可以获得唯一的一个实例。
延迟初始化
延迟初始化就是一个对象被消费完毕后,并不立刻释放,工厂类保持其初始状态,等待再次被使用。延迟初始化是工厂方法模式的一个扩展应用,其通用类图如下图所示。
ProductFactory负责产品类对象的创建工作,并且通过map变量产生一个缓存,对需要再次被重用的对象保留
public class ProductFactory {
private static final Map<String,Product> prMap = new HashMap();
public static synchronized Product createProduct(String type) throws Exception{
Product product =null;
//如果Map中已经有这个对象
if(prMap.containsKey(type)){
product = prMap.get(type);
}else{
if(type.equals("Product1")){
product = new ConcreteProduct1();
}else{
product = new ConcreteProduct2();
}
//同时把对象放到缓存容器中
prMap.put(type,product);
}
return product;
}
}
代码还比较简单,通过定义一个Map容器,容纳所有产生的对象,如果在Map容器中已 经有的对象,则直接取出返回;如果没有,则根据需要的类型产生一个对象并放入到Map容 器中,以方便下次调用。 延迟加载框架是可以扩展的,例如限制某一个产品类的最大实例化数量,可以通过判断Map中已有的对象数量来实现,这样的处理是非常有意义的,例如JDBC连接数据库,都会要求设置一个MaxConnections最大连接数量,该数量就是内存中最大实例化的数量。
参考:
设计模式之禅
菜鸟教程