任何可以生产对象的方法或类,都可以称为工厂。单例模式也是一种工厂。
1. 工厂模式(静态工厂模式/简单工厂模式)
先来看看下面的例子:汉堡店卖汉堡
假设目前有三种种类的汉堡:鸡腿汉堡、牛肉汉堡、鲜虾汉堡。可以使用一个通用接口:
public abstract class Burger {
String name;
void machining() {
System.out.println("制作" + this.name + "中");
}
void boxing() {
System.out.println("包装" + this.name + "中");
}
void end() {
System.out.println("完毕");
}
}
public class ChickenBurger extends Burger {
public ChickenBurger() {
this.name = "鸡腿汉堡";
}
}
public class BeefBurger extends Burger {
public BeefBurger() {
this.name = "牛肉汉堡";
}
}
public class ShrimpBurger extends Burger {
public ShrimpBurger() {
this.name = "鲜虾汉堡";
}
}
现在商店卖汉堡:
public class BurgerStore {
public void orderBurger(String name) {
Burger burger = null;
// 这里就假设输入的name一定是这三种之一
if(name.equals("鸡腿汉堡")) {
burger = new ChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new BeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new ShrimpBurger();
}
burger.machining();
burger.boxing();
burger.end();
}
}
客户端:
public class Client {
public static void main(String[] args) {
BurgerStore burgerStore = new BurgerStore();
burgerStore.orderBurger("牛肉汉堡");
burgerStore.orderBurger("鸡腿汉堡");
}
}
上面的全部代码是没问题的,现在来新增汉堡种类,首先是添加类,然后得去修改BurgerStore类的这段代码:
if(name.equals("鸡腿汉堡")) {
burger = new ChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new BeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new ShrimpBurger();
}
问题就出现在这,不是说这段代码不行,而是设计原则的问题,根据《HeadFirst设计模式》中提出的原则:“找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。”。
现在就把这段代码封装起来,封装后的类就专门来创建汉堡种类,称为简单工厂类:
public class SimpleBurgerFactory {
public Burger createBurger(String name) {
Burger burger = null;
if(name.equals("鸡腿汉堡")) {
burger = new ChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new BeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new ShrimpBurger();
}
return burger;
}
}
需要修改BurgerStore类的代码:
public class BurgerStore {
SimpleBurgerFactory simpleBurgerFactory;
public BurgerStore(SimpleBurgerFactory simpleBurgerFactory) {
this.simpleBurgerFactory = simpleBurgerFactory;
}
public void orderBurger(String name) {
Burger burger = simpleBurgerFactory.createBurger(name);
burger.machining();
burger.boxing();
burger.end();
}
}
至此,这样做的好处:以后都不用去修改BurgerStore类,每次新增汉堡种类时,只需要修改SimpleBurgerFactory类。这样难道有什么不同???把这段代码独立出来,因为可能也有别的商店卖汉堡,而且都是来自同一个工厂(厂商)的,所以这段代码就可以复用。
简单工厂模式的创建对象方法可以是静态方法也可以是普通方法。
来看一张类关系图(UML图):
根据《HeadFirst设计模式》中的说法,简单工厂模式并不是一种真正的设计模式,而是一种编程习惯。
2. 工厂方法模式
现在我们来更改下需求:要求按品牌店来分汉堡种类,比如肯德基的鸡腿堡和麦当劳的鸡腿堡。(现实中汉堡种类不一定同时存在在这些品牌店中,纯属虚构),现在假设有肯德基和麦当劳两个品牌,因为肯德基的鸡腿堡和麦当劳的鸡腿堡是不同的,所以不能用原来的ChickenBurger类,得创建各自的ChickenBurger类。
一种做法就是直接在简单工厂中进行修改:
public class SimpleBurgerFactory {
public Burger createBurger(String type, String name) {
Burger burger = null;
if(type.equals("肯德基")) {
if(name.equals("鸡腿汉堡")) {
burger = new KFCChickenBurger(); // 前面加个KFC,表示是肯德基的产品
} else if(name.equals("牛肉汉堡")) {
burger = new KFCBeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new KFCShrimpBurger();
}
} else if(type.equals("麦当劳")) {
if(name.equals("鸡腿汉堡")) {
burger = new MChickenBurger(); // 前面加个M,表示是麦当劳的产品
} else if(name.equals("牛肉汉堡")) {
burger = new MBeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new MShrimpBurger();
}
}
return burger;
}
}
来想一想,把所有实现放在一个工厂类中,要是该工厂类不能正常工作,那么想创建任意一个汉堡种类也可能会出错,也就是耦合度高。而且另一个问题就是,跟上面说的一样,每当新增品牌时,这段代码总是会变化,我们需要找出不变的,封装变的。
现在来看看另一种实现方式,把createBurger() 移到BurgerStore类中,然后createBurger()和BurgerStore都变成抽象的,然后每个品牌去继承BurgerStore类,实现createBurger(),看代码:
为了方便我省略KFCBeefBurger、KFCShrimpBurger、MChickenBurger、MBeefBurger、MShrimpBurger这些产品类,就上面的那些类,前面加个KFC和M,里面就打印加个KFC和M,就拿KFCChickenBurger做例子,其他类似:
public class KFCChickenBurger extends Burger {
public KFCChickenBurger() {
this.name = "(KFC)鸡腿汉堡";
}
}
重点来了:
public abstract class BurgerStore {
public void orderBurger(String name) {
Burger burger = createBurger(name);
burger.machining();
burger.boxing();
burger.end();
}
// 设置称protected,为了只让子类使用
protected abstract Burger createBurger(String name);
}
现在来实现两个具体的品牌:
public class KFCBurgerStore extends BurgerStore {
@Override
protected Burger createBurger(String name) {
Burger burger = null;
if(name.equals("鸡腿汉堡")) {
burger = new KFCChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new KFCBeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new KFCShrimpBurger();
}
return burger;
}
}
public class MBurgerStore extends BurgerStore {
@Override
protected Burger createBurger(String name) {
Burger burger = null;
if(name.equals("鸡腿汉堡")) {
burger = new MChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new MBeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new MShrimpBurger();
}
return burger;
}
}
客户端:
public class Client {
public static void main(String[] args) {
KFCBurgerStore kfcBurgerStore = new KFCBurgerStore();
kfcBurgerStore.orderBurger("牛肉汉堡");
kfcBurgerStore.orderBurger("鸡腿汉堡");
MBurgerStore mBurgerStore = new MBurgerStore();
mBurgerStore.orderBurger("牛肉汉堡");
mBurgerStore.orderBurger("鸡腿汉堡");
}
}
输出:
制作(KFC)牛肉汉堡中
包装(KFC)牛肉汉堡中
完毕
制作(KFC)鸡腿汉堡中
包装(KFC)鸡腿汉堡中
完毕
制作(M)牛肉汉堡中
包装(M)牛肉汉堡中
完毕
制作(M)鸡腿汉堡中
包装(M)鸡腿汉堡中
完毕
现在每当新增品牌,只需要去继承BurgerStore类就行,而要是KFCBurgerStore或者MBurgerStore出现问题都不会互相影响。
现在来看看工厂方法模式和简单工厂的不同:
- 工厂方法模式是针对接口编程,使用继承去扩展一个类,而简单工厂是去修改代码。
- 工厂方法模式如果有一个品牌类出问题,其他品牌类不会受影响(解耦合),这些找错误也很容易;而简单工厂如果出错,可能全部都不能正常工作。
UML图:
虽然UML图比简单工厂模式复杂点,但是是值得的。
这里的KFCBeefBurger、KFCShrimpBurger、MChickenBurger、MBeefBurger、MShrimpBurger等产品类可以再用一个抽象类继承,比如使用KFCBurger、MBurger去继承Burger,然后就使用KFCBurger、MBurger抽象类进行扩展。
3. 抽象工厂模式
现在假设要新增薯条的产品(属于肯德基和麦当劳两种品牌的产品),有一种方法就是:需要再写一个像上面那样的工厂方法模式:FrenchFriesStore。
但是想想,因为是两种品牌下的两种产品(汉堡和薯条),所以需要一个更大的抽象类。现在肯德基就不单单卖汉堡,麦当劳也是。
薯条产品的代码:
public abstract class FrenchFries {
String name;
void machining() {
System.out.println("制作" + this.name + "中");
}
void boxing() {
System.out.println("包装" + this.name + "中");
}
void end() {
System.out.println("完毕");
}
}
public class KFCFrenchFries extends FrenchFries {
public KFCFrenchFries() {
this.name = "(KFC)薯条";
}
}
public class MFrenchFries extends FrenchFries {
public MFrenchFries() {
this.name = "(M)薯条";
}
}
现在修改上面的BurgerStore类:使得变成一个更大的抽象类
public abstract class Store {
public void orderBurger(String name) {
Burger burger = createBurger(name);
burger.machining();
burger.boxing();
burger.end();
}
// 新增薯条的订单
public void orderFrenchFries(String name) {
FrenchFries frenchFries = createFrenchFries();
frenchFries.machining();
frenchFries.boxing();
frenchFries.end();
}
protected abstract Burger createBurger(String name);
// 新增产品,因为两个品牌只有一种薯条所以不带参数
protected abstract FrenchFries createFrenchFries();
}
public class KFCStore extends Store {
@Override
protected Burger createBurger(String name) {
Burger burger = null;
if(name.equals("鸡腿汉堡")) {
burger = new KFCChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new KFCBeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new KFCShrimpBurger();
}
return burger;
}
@Override
protected FrenchFries createFrenchFries() {
return new KFCFrenchFries();
}
}
public class MStore extends Store {
@Override
protected Burger createBurger(String name) {
Burger burger = null;
if(name.equals("鸡腿汉堡")) {
burger = new MChickenBurger();
} else if(name.equals("牛肉汉堡")) {
burger = new MBeefBurger();
} else if(name.equals("鲜虾汉堡")) {
burger = new MShrimpBurger();
}
return burger;
}
@Override
protected FrenchFries createFrenchFries() {
return new MFrenchFries();
}
}
客户端:
public class Client {
public static void main(String[] args) {
MStore mStore = new MStore();
mStore.orderBurger("鸡腿汉堡");
mStore.orderFrenchFries();
KFCStore kfcStore = new KFCStore();
kfcStore.orderBurger("牛肉汉堡");
kfcStore.orderFrenchFries();
}
}
输出:
制作(M)鸡腿汉堡中
包装(M)鸡腿汉堡中
完毕
制作(M)薯条中
包装(M)薯条中
完毕
制作(KFC)牛肉汉堡中
包装(KFC)牛肉汉堡中
完毕
制作(KFC)薯条中
包装(KFC)薯条中
完毕
这种设计结构就是抽象工厂模式。
抽象工厂模式和工厂方法模式的不同:
- 抽象工厂模式是一个比较大的范围,比如肯德基的所有产品,苹果的所有产品;而工厂方法模式只是一类产品,比如肯德基的汉堡,苹果手机。
4. 总结
下面转自菜鸟教程:
意图: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类(意思是:不是由我们程序员来指定,把具体类创建死了,而是由客户端根据需要创建具体类)。
主要解决: 主要解决接口选择的问题。
何时使用: 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
优点: 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点: 虽然说扩展容易,但是要增加一个系列的某一产品,既要在Store抽象类里加代码,又要在具体的里面加代码。而且如果产品很多的话,类也很多,比较复杂。
使用场景:
1、软件换皮肤,一整套一起换。
2、生成不同操作系统的程序。
如果只是一类产品使用工厂方法模式(如果是多个互不相干的产品也可以使用),如果是多类产品使用抽象工厂(当然需要有联系,比如是同一个品牌下的产品)
参考:
《HeadFirst设计模式》