定义
定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
使用场景
- 一个类不能预期它必须创建的对象的类
- 一个类想要它的子类指定它创建的对象
- 类将责任委托给几个辅助子类之一,并且你想要知道哪个辅助子类是委托
例子
有个披萨店,用户可以下单定披萨。披萨店首先根据用户的选择披萨(CheesePizza、GreekPizza),然后按照制作流程进行制作,最后把披萨返回给用户:
public class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza;
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("greek")){
pizza = new GreekPizza();
}
...
// 制作流程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
我们发现上面程序中,选择披萨是一个变化的部分,而披萨的制作流程是不变的,我们把这部分抽取出来提炼一个工厂函数:
// 制作工厂
public class SimplePizzaFactory{
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese")){
pizza = new CheesePizza();
}else if(type.equals("greek")){
pizza = new GreekPizza();
}
...
return pizza;
}
}
public class PizzaStore{
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza;
pizza = factory.createPizza(type);
// 制作流程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
通过这个factory,我们把依据类型选择pizza这段逻辑提取出来。这里带来一个最简单的好处是:所有需要通过类型选择pizza的地方都可以调用这个方法,比如结账、制作pizza的菜单等。但是这还不是工厂模式。
需求进一步扩展,pizza开了很多的分店,比如北京、上海,每个店就可以制作特定地区原料的pizza。我们可以用下面的方法实现:
// 从北京店定制cheese披萨
BeijingPizzaFactory factory = new BeijingPizzaFactory();
PizzaStore bjStore = new PizzaStore(factory);
bjStore.orderPizza("cheese");
// 从北京店定制greek披萨
ShanghaiPizzaFactory factory = new ShanghaiPizzaFactory();
PizzaStore shStore = new PizzaStore(factory);
shStore.orderPizza("greek");
也可以通过继承来实现:
public abstract class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza;
pizza = createPizza(type);
...
return pizza;
}
abstract Pizza createPizza(String type);
}
// 北京披萨店
public class BeijingPizzaStore extends PizzaStore{
public Pizza createPizza(String type){
if(type.equals("cheese")){
pizza = new BeijingCheesePizza();
}
...
return pizza;
}
}
到了这一步,我们还没有实现pizza类:
public abstract class Pizza{
String name; // 名字
String dough; // 面团
String sauce // 酱
void prepare(){
// do prepare
}
void bake(){
System.out.println("bake");
}
void cut(){
System.out.println("cut");
}
void box(){
System.out.println("box");
}
public String getName(){
return name;
}
}
public class BeijingCheesePizza extends Pizza{
public BeijingCheesePizza(){
name = "beijing cheese pizza";
dough = "白面";
sauce = "辣椒酱";
}
}
对上面的例子做个测试:
public class App{
public static void main(String[] args){
BeijingPizzaStore bjStore = new BeijingPizzaStore();
Pizza pizza = bjStore.orderPizza("cheese")
}
}
分析
工厂模式都用来封装对象的创建。工厂方法模式通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
上面的例子中,pizzaStore
是抽象创建类;BeijingPizzaStore
是具体创建类,它的createPizza
方法正是工厂方法,用来制作产品;Pizza
是工厂的产品类。
工厂模式体现了设计模式一个很重要的原则(依赖倒置原则):要依赖抽象,不要依赖具体类。pizzaStore
依赖了Pizza
这个抽象类,而不是CheesePizza
、GreekPizza
这些具体类。
要开一个披萨店,我们首先想到的是要能制作各种披萨,如芝士披萨、熟食披萨等。但是如果披萨店依赖这些具体的披萨那就麻烦了(第一段程序);反过来,我们把所有具体的披萨抽象成一个pizza
类,那么和披萨店依赖的是这个pizza
类,而不是具体的披萨了。
进一步延伸
接上面披萨店的需求。不同的地方原料不一样,比如北方人面食多,因此披萨用面来做;南方人喜欢吃米饭,因此披萨用大米来做(胡扯..),因此我们提供一个接口,负责创建所有的原料:
public interface PizzaIngredientFactory{
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
}
那么北京的披萨店制作披萨时需实现这个接口,用于准备披萨的材料:
public class BeijingPizzaIngredientFactory implements PizzaIngredientFactory{
public Dough createDough(){
return new BaiMianDough();
}
public Sauce createSauce(){
return new LajiaoSauce();
}
}
北京的披萨店类需做修改:
public abstract class Pizza{
String name; // 名字
Dough dough; // 面团
Sauce sauce // 酱
abstract void prepare(); // 由子类实现 准备原料
...
}
// 具体的披萨 和 原料关联,原料的实现由工厂方法提供
public class CheesePizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
void prepare(){
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
public class BeijingPizzaStore extends PizzaStore{
protected Pizza createPizza(String item){
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new BeijingPizzaIngredientFactory();
if(item.equals("cheese")){
pizza = new CheesePizza(ingredientFactory);
}
...
return pizza;
}
}
抽象工厂类
抽象工厂类模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。由于不需要知道实际产出的具体产品(上面例子中BaiMianDough、LajiaoSauce)是什么,这样客户就从具体的产品中解耦。
两者区别
工厂方法: 用来创建对象,具体说是通过子类创建对象。客户只需要知道他们所使用的抽象类型即可,而由子类负责决定具体类型。负责将客户从具体类型中解耦。
抽象工厂方法:创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。
简单点的例子
参考游戏《铁匠》:
// 铁匠能够制作武器
public interface Blacksmith {
Weapon manufactureWeapon(WeaponType weaponType);
}
// elf制作精灵族的武器
public class ElfBlacksmith implements Blacksmith {
public Weapon manufactureWeapon(WeaponType weaponType) {
return new ElfWeapon(weaponType);
}
}
// orc制作兽族的武器
public class OrcBlacksmith implements Blacksmith {
public Weapon manufactureWeapon(WeaponType weaponType) {
return new OrcWeapon(weaponType);
}
}
// 武器与种族和类别有关
public interface Weapon {
WeaponType getWeaponType();
}
// 精灵族的武器
public class ElfWeapon implements Weapon {
private WeaponType weaponType;
public ElfWeapon(WeaponType weaponType) {
this.weaponType = weaponType;
}
public String toString() {
return "Elven " + weaponType;
}
public WeaponType getWeaponType() {
return weaponType;
}
}
// 兽族的武器
public class OrcWeapon implements Weapon {
private WeaponType weaponType;
public OrcWeapon(WeaponType weaponType) {
this.weaponType = weaponType;
}
public String toString() {
return "Orcish " + weaponType;
}
public WeaponType getWeaponType() {
return weaponType;
}
}
// 武器本身分为 短剑、矛、斧三种
public enum WeaponType {
SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), UNDEFINED("");
private String title;
WeaponType(String title) {
this.title = title;
}
public String toString() {
return title;
}
}
// 测试 调用的是铁匠这个基类,通过指向不同的对象,定义其行为
public class App {
private final Blacksmith blacksmith;
public App(Blacksmith blacksmith) {
this.blacksmith = blacksmith;
}
public static void main(String[] args) {
// 用兽族的武器去战争
App app = new App(new OrcBlacksmith());
app.manufactureWeapons();
// 用精灵族的武器去战争
app = new App(new ElfBlacksmith());
app.manufactureWeapons();
}
private void manufactureWeapons() {
Weapon weapon;
weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); // 矛
System.out.println(weapon);
weapon = blacksmith.manufactureWeapon(WeaponType.AXE); // 斧
System.out.println(weapon);
}
}