Java设计模式之工厂模式
1. 引子
在介绍这篇内容之前,先以一个小故事开头。
话说有一个暴发户,他家有三辆轿车(奔驰、宝马、奥迪),还雇了司机为他开车。不过,暴发户每次坐车时总是这样:上奔驰车后跟司机说“开奔驰车!”,坐上宝马后他说“开宝马车!”,坐上奥迪后他说“开奥迪车!”。
也许你会说:这人有病吧!直接说开车不就行了?!非得多此一举是要表明自己是壕了吗?这是病,得治。
其实像这样的行为放到程序中来实现时,在面向过程的语言中暴发户一直是通过这种方式来坐车的。
幸运的是,这种病在面向对象的语言中有得治了。那如何来治病,下面我们开始今天的主题:工厂模式。
2. 工厂模式简介
工厂模式主要是为创建对象提供过渡接口,专门负责将大量有共同接口的类实例化,而且不必事先知道每次是要实例化哪一个类的模式,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
一般工厂模式可以分为三类:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
这三种模式从上到下逐步抽象,并且更具一般性。
2.1 简单工厂模式
简单工厂模式又称静态工厂方法模式,从命名上就可以看出这个模式很简单。它存在的目的很简单:定义一个用于创建对象的接口。
这个模式本身很简单而且使用在业务较简单的情况下,一般用于小项目或者具体产品很少扩展的情况(这样工厂类才不用经常更改)。
它主要由三种角色组成:
抽象产品角色:它一般是具体产品继承的父类或者实现的接口,由接口或者抽象类来实现。
具体产品角色:工厂类所创建的对象就是此角色的实例,在java中由一个具体类实现。
工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。
下面我们来给那个暴发户治病:在使用了简单工厂模式后,现在暴发户只需要坐在车里对司机说句:“开车”就可以了。来看看怎么用代码实现的:
package com.demo;
/**
* 简单工厂测试类
*
* @author 小明
*
*/
public class SimpleFactoryTest {
public static void main(String[] args) {
// 从工厂中创建具体产品对象
// 比如老板说今天要坐奔驰,司机去仓库取车
Car car = CarFactory.createCar("benz");
car.setName("大奔");
car.setPrice(798000);
// 老板说开车,司机开着车出发
car.drive();
}
}
/**
* 轿车类,抽象产品角色
*
* @author 小明
*
*/
abstract class Car {
private String name; // 名字
private double price; // 价格
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
/**
* 启动
*/
public abstract void drive();
}
/**
* 奔驰车,具体产品角色
*
* @author 小明
*
*/
class Benz extends Car {
@Override
public void drive() {
System.out.println("身份的象征:" + this.getName() + "启动......");
}
}
/**
* 宝马车,具体产品角色
*
* @author 小明
*
*/
class Bmw extends Car {
@Override
public void drive() {
System.out.println("就是这么滴拽:" + this.getName() + "启动......");
}
}
/**
* 奥迪车,具体产品角色
*
* @author 小明
*
*/
class Audi extends Car {
@Override
public void drive() {
System.out.println("不差钱:" + this.getName() + "启动......");
}
}
/**
* 工厂类角色,用于产生具体的产品对象
*
* @author 小明
*
*/
class CarFactory {
/**
* 公有静态方法,产生具体产品对象
*
* @param carType
* 车型字符串形式
* @return 具体产品对象
*/
public static Car createCar(String carType) {
Car car = null; // 具体产品对象的引用
/* 根据传递参数值创建不同的具体产品对象 */
if ("benz".equals(carType))
car = new Benz();
else if ("bmw".equals(carType))
car = new Bmw();
else if ("audi".equals(carType))
car = new Audi();
return car; // 返回具体产品对象
}
}
这便是简单工厂模式了,它符合现实中的情况,并且客户免除了直接创建产品对象的责任,而仅仅负责“消费”产品(正如暴发户所为)。
当暴发户增加了一辆车的时候,只要符合抽象产品角色的特点,就可以通知工厂类知道,然后让工厂类创建具体角色对象就可以被客户使用了。对于具体产品角色部分来说,它是符合开闭原则的;但是工厂类就不太理想,因为每增加一辆车,都要在工厂类中增加相应的业务逻辑,即需要修改工厂类的代码,这显然是违背开闭原则的。
正如前面提到的简单工厂模式适用于业务简单的情况下或者具体产品很少扩展的情况,而对于复杂的业务环境可能不太适应了,这时我们就可以考虑使用工厂方法模式了。
2.2 工厂方法模式
当暴发户生意越做越大,自己的车也越来越多,司机开始抱怨,因为对司机师傅来说,什么车他都要记得、维护,而且也都要经过他来使用!于是暴发户说:我给你分配几个助手,你只要管好他们就可以了!这时,工厂方法模式就出现了。
工厂方法涉及的角色:
- 抽象工厂角色: 这是工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类。
- 具体工厂角色:它含有和具体业务逻辑有关的代码,由应用程序调用以创建对应的具体产品的对象。
- 抽象产品角色:它是具体产品角色继承的父类或者是实现的接口。
- 具体产品角色:具体工厂角色所创建的对象是此角色的实例。
一个抽象产品角色派生出多个具体产品角色,一个抽象工厂角色派生出多个具体工厂角色。每个具体工厂角色只能创建一个具体产品角色的实例,即定义一个创建对象的接口(即抽象工厂接口),让其实现(具体工厂类)决定实例化哪一个类(具体产品类)的对象。
下面我们来帮司机师傅减轻工作压力:
package com.demo6;
/**
* 工厂方法测试
*
* @author 小明
*
*/
public class FactoryMethodTest {
public static void main(String[] args) {
// 司机师傅找到管理宝马的助手
CarFactory factory = new BmwFactory();
// 司机让助手去取车
Car car = factory.createCar();
car.setName("宝马");
car.setPrice(698000);
// 老板说开车
car.drive();
}
}
/**
* 轿车类,抽象产品角色
*
* @author 小明
*
*/
abstract class Car {
private String name; // 名字
private double price; // 价格
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
/**
* 启动
*/
public abstract void drive();
}
/**
* 奔驰车,具体产品角色
*
* @author 小明
*
*/
class Benz extends Car {
@Override
public void drive() {
System.out.println("身份的象征:" + this.getName() + "启动......");
}
}
/**
* 宝马车,具体产品角色
*
* @author 小明
*
*/
class Bmw extends Car {
@Override
public void drive() {
System.out.println("就是这么滴拽:" + this.getName() + "启动......");
}
}
/**
* 奥迪车,具体产品角色
*
* @author 小明
*
*/
class Audi extends Car {
@Override
public void drive() {
System.out.println("不差钱:" + this.getName() + "启动......");
}
}
/**
* 抽象工厂角色
*
* @author 小明
*
*/
interface CarFactory {
/**
* 创建具体产品角色对象的方法
*
* @return 具体产品角色对象的引用,通过父类引用
*/
Car createCar();
}
/**
* 具体工厂角色,创建奔驰车的工厂
*
* @author 小明
*
*/
class BenzFactory implements CarFactory {
@Override
public Car createCar() {
return new Benz();
}
}
/**
* 具体工厂角色,创建宝马车的工厂
*
* @author 小明
*
*/
class BmwFactory implements CarFactory {
@Override
public Car createCar() {
return new Bmw();
}
}
/**
* 具体工厂角色,创建奥迪车的工厂
*
* @author 小明
*
*/
class AudiFactory implements CarFactory {
@Override
public Car createCar() {
return new Audi();
}
}
当有新的产品产生时,只要按照抽象产品角色、抽象工厂角色提供的规则来扩展,那么就可以被客户使用,而不必去修改任何已有的代码。即当有新产品时,只要创建一个具体产品类并继承抽象产品的类,创建一个具体工厂类继承抽象工厂的类,不用修改任何一个已有类。工厂方法模式是完全符合开闭原则的!
2.3 抽象工厂模式
抽象工厂模式是一种比较常用的模式,其定义是:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定它们的具体类。
抽象工厂模式的各个角色和工厂方法的如出一辙,抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
来认识一个概念,产品族:位于不同产品等级结构中,功能相关联的产品组成的家族。比如对于轿车来说有跑车家族,那么不管是宝马的跑车,还是奔驰的跑车,我们都可以放到跑车这个产品家族中。
抽象工厂模式的用意为:给客户提供一个接口,可以创建多个产品族中的产品对象。
下面我们来看抽象工厂模式的通用源码格式:
package com.demo7;
/**
* 抽象工厂模式测试
*
* @author 小明
*
*/
public class AbstractFactoryTest {
public static void main(String[] args) {
// 老板要坐宝马跑车
CarFactory factory = new SportsCarFactory();
BmwCar car = factory.createBmwCar();
car.setName("宝马");
car.setPrice(698000);
car.drive();
}
}
/**
* 奔驰轿车类,抽象产品角色
*
* @author 小明
*
*/
abstract class BenzCar {
private String name; // 名字
private double price; // 价格
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
/**
* 启动
*/
public abstract void drive();
}
/**
* 奔驰跑车,具体产品角色
*
* @author 小明
*
*/
class BenzSportsCar extends BenzCar {
@Override
public void drive() {
System.out.println("轰轰轰...:" + this.getName() + "跑车启动......");
}
}
/**
* 奔驰商务车,具体产品角色
*
* @author 小明
*
*/
class BenzBusinessCar extends BenzCar {
@Override
public void drive() {
System.out.println("身份的象征:" + this.getName() + "商务车启动......");
}
}
/**
* 宝马轿车类,抽象产品角色
*
* @author 小明
*
*/
abstract class BmwCar {
private String name; // 名字
private double price; // 价格
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
/**
* 启动
*/
public abstract void drive();
}
/**
* 宝马跑车,具体产品角色
*
* @author 小明
*
*/
class BmwSportsCar extends BmwCar {
@Override
public void drive() {
System.out.println("轰...:" + this.getName() + "跑车启动......");
}
}
/**
* 宝马商务车,具体产品角色
*
* @author 小明
*
*/
class BmwBusinessCar extends BmwCar {
@Override
public void drive() {
System.out.println("商务专用:" + this.getName() + "商务车启动......");
}
}
/**
* 抽象工厂角色
*
* @author 小明
*
*/
interface CarFactory {
/**
* 创建奔驰车对象,创建具体产品角色对象的方法
*
* @return 具体产品角色对象的引用,通过父类引用
*/
BenzCar createBenzCar();
/**
* 创建宝马车对象,创建具体产品角色对象的方法
*
* @return 具体产品角色对象的引用,通过父类引用
*/
BmwCar createBmwCar();
}
/**
* 具体工厂角色,创建跑车的工厂
*
* @author 小明
*
*/
class SportsCarFactory implements CarFactory {
@Override
public BenzCar createBenzCar() {
return new BenzSportsCar();
}
@Override
public BmwCar createBmwCar() {
return new BmwSportsCar();
}
}
/**
* 具体工厂角色,创建商务车的工厂
*
* @author 小明
*
*/
class BusinessCarFactory implements CarFactory {
@Override
public BenzCar createBenzCar() {
return new BenzBusinessCar();
}
@Override
public BmwCar createBmwCar() {
return new BmwBusinessCar();
}
}
抽象工厂模式具有很好的封装性,高层模块只关心接口、抽象类,它不关心对象是如何创建出来,因为对象的创建由工厂类负责,只要知道工厂类是谁,我就能创建出一个需要的对象,省时省力。
抽象工厂模式的最大缺点就是产品族扩展非常困难。如果要增加一个产品(奥迪),看看我们的程序有多大改动吧!抽象类CarFactory要增加一个方法createAudiCar(),然后,两个实现类都要修改,想想看,这在项目中的话,还怎么让人活!这严重违反了开闭原则,而且我们说抽象类和接口是一个契约,改变契约,所有与契约有关系的代码都要修改!