1.声明
设计模式中的设计思想、图片和部分代码参考自《Head First设计模式》,作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates。
在这里我只是对这本书进行学习阅读,并向大家分享一些心得体会。
2.简单工厂模式的局限
2.1简单工厂模式给比萨店带来的改进
在上一篇中,我们讲到的简单工厂模式,将披萨的创建单独由SimplePizzaFactory此类负责,这样的好处是可以复用对象的创建,例如不光披萨餐店可以使用SimplePizzaFactory生产披萨,宅急送也可以用SimplePizzaFactory创建比萨;另外一个好处就是如果比萨类型有增删,那么我们只要维护SimplePizzaFactory就可以了,并不会接触到其他无关的代码。
2.2新的需求
由于比萨店使用我们的简单工厂模式作为其后台系统,这款优秀的系统迅速让这个比萨店打败了他的竞争者们,所以现在这个比萨店要开全国连锁店,但是现在有了新的问题,就是各地人民的口味不太一致,纽约的居民喜欢口味淡一点的比萨,芝加哥居民喜欢调料重一点的比萨。
这样一来,原来的设计方式就不能采用了,因为生产出来的比萨都是一个风味的。
所以需求就是,按照总部的生产流程生产比萨(包装、大小等相同),但是风味可以不同。
2.3最初的设计
为了满足各地居民的口味,那么自然而然的就需要设计不同的口味的比萨:
比萨基类:
//比萨的基类
public class Pizza {
//准备材料
public void prepare() {
System.out.println("prepare");
}
//烘烤
public void bake() {
System.out.println("bake");
}
//切分
public void cut() {
System.out.println("cut");
}
//包装
public void box() {
System.out.println("box");
}
}
芝士风味的比萨:
注:其他口味的这里省略,详见上一篇:"简单工厂模式"。
public class CheesePizza extends Pizza{
}
芝加哥风味的芝士比萨:
//芝加哥风味的比萨饼
public class ChicagoStyleCheesePizza extends CheesePizza {
@Override
public void prepare() {
System.out.println("芝加哥风味的比萨饼食材");
}
@Override
public void bake() {
System.out.println("芝加哥风味的烘烤方式");
}
//使用默认比萨饼的切分大小
@Override
public void cut() {
super.cut();
}
//使用默认比萨饼的打包方式
@Override
public void box() {
super.box();
}
}
纽约风味的芝士比萨:
//纽约风味的比萨饼
public class NYStyleCheesePizza extends CheesePizza {
@Override
public void prepare() {
System.out.println("纽约风味的比萨饼食材");
}
@Override
public void bake() {
System.out.println("纽约风味的烘烤方式");
}
//使用默认比萨饼的切分大小
@Override
public void cut() {
super.cut();
}
//使用默认比萨饼的打包方式
@Override
public void box() {
super.box();
}
}
有了不同口味的比萨,那么自然而然就得需要不同的比萨工厂,比萨工厂仍然参照简单工厂模式的设计方式:
比萨工厂的基类:
public abstract class PizzaFactory {
public abstract Pizza createPizza(String type);
}
芝加哥风味比萨工厂:
//芝加哥风味比萨工厂
public class ChicagoPizzaFactory extends PizzaFactory {
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("CheesePizza")) {
pizza = new ChicagoStyleCheesePizza();
}else if(type.equals("GreekPizza")) {
//同CheesePizza
}else if(type.equals("PepperoniPizza")) {
//同CheesePizza
}
return pizza;
}
}
纽约风味比萨工厂:
//纽约风味比萨工厂
public class NYPizzaFactory extends PizzaFactory{
@Override
public Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("CheesePizza")) {
pizza = new NYStyleCheesePizza();
}else if(type.equals("GreekPizza")) {
//同CheesePizza
}else if(type.equals("PepperoniPizza")) {
//同CheesePizza
}
return pizza;
}
}
有了不同风味的比萨工厂,下面创建不同风味的比萨商店。
比萨商店的基类:
//比萨店
public class PizzaStore {
PizzaFactory factory;
public PizzaStore(PizzaFactory factory) {
this.factory = factory;
}
//订单处理流程方法
public Pizza orderPizza(String type) {
//返回的pizza
Pizza pizza = null;
//根据比萨类型创建对应的比萨饼
pizza = factory.createPizza(type);
//制作过程
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
加盟店-芝加哥分店:
public class ChicagoPizzaStore extends PizzaStore {
public ChicagoPizzaStore(PizzaFactory factory) {
super(factory);
// TODO Auto-generated constructor stub
}
//对外提供订购比萨的方法
@Override
public Pizza orderPizza(String type) {
//返回的pizza
Pizza pizza = null;
//根据比萨类型创建对应的比萨饼
pizza = factory.createPizza(type);
//注:这里的制作过程中居然没有包装成盒,pizza.box();
pizza.prepare();
pizza.bake();
pizza.cut();
return pizza;
}
}
加盟店-纽约分店:
public class NYPizzaStore extends PizzaStore{
public NYPizzaStore(PizzaFactory factory) {
super(factory);
}
//对外提供订购比萨的方法
@Override
public Pizza orderPizza(String type) {
// 返回的pizza
Pizza pizza = null;
// 根据比萨类型创建对应的比萨饼
pizza = factory.createPizza(type);
// 注:纽约比萨的制作过程居然没有切分,pizza.cut();
pizza.prepare();
pizza.bake();
pizza.box();
return pizza;
}
}
下面来测试一下,两种风味的比萨饼各选购一个。
测试类:
public class PizzaTest {
public static void main(String[] args) {
//------------商店购买一份纽约风味的比萨------------
PizzaFactory nyFac = new NYPizzaFactory();
PizzaStore nyStore = new NYPizzaStore(nyFac);
nyStore.orderPizza("CheesePizza");
System.out.println();
//------------商店购买一份芝加哥风味的比萨------------
PizzaFactory chicagoFac = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new ChicagoPizzaStore(chicagoFac);
chicagoStore.orderPizza("CheesePizza");
}
}
输出结果:
纽约风味的比萨饼食材
纽约风味的烘烤方式
box
芝加哥风味的比萨饼食材
芝加哥风味的烘烤方式
cut
设计缺陷:
由上面的测试结果可知,纽约风味的比萨没有按照总部要求要求的方式进行切分,芝加哥风味的比萨没有按照总部要求的方式进行包装成盒。
所以问题的关键点就在于,我们需要让各个分店使用总部规定的生产流程(订单处理流程),这样的话,就可以统一全国的连锁店的样式了,但是问题是,怎么才能让各个分店心甘情愿的使用总部的生产流程呢?(毕竟有的人就是很有个性)。
3使用工厂方法模式进行设计
解决方案就是:总部有了新的规定,就是所有加盟店必须遵守一条规则,规则就是所有加盟店可以自己生产比萨,但是比萨的加工必须由总部的人来完成,否则不允许加盟。
方案一出,这样各个分店都老实了,这样便保证了比萨的样式同意了,但是代码上是如何设计的呢?
3.1设计思路
需求:
既需要让加盟店按照总部的生产流程,有需要让加盟点有一定的自由度(制作自己风味的比萨)。
思路:
以上面的代码为基础,将PizzaStore视为总部的pizza店,然后将PizzaStore中的orderPizza设为final类型(不许子类重写此方法),之后在PizzaStore定义抽象的createPizza方法(疑问:这里将各个Factory中createPizza方法放回到PizzaStore,为什么这样做?)。
3.2设计代码
=================================定义比萨饼(产品类Product)=================================
重新定义比萨饼基类:
public abstract class Pizza {
String name; //比萨名称
String dough; //比萨所使用的面团类型
String sauce; //比萨所使用的酱香类型
//比萨使用的一套佐料
ArrayList<String> toppings = new ArrayList<String>();
//比萨的准备阶段
void prepare() {
System.out.println("准备 " + name);
System.out.println("揉面团。。。");
System.out.println("添加酱。。。");
System.out.println("添加佐料: ");
for (String topping : toppings) {
System.out.print(" " + topping);
}
}
//----------------提供的默认的一些订单流程----------------
void bake() {
System.out.println("350度烘烤25分钟");
}
void cut() {
System.out.println("将披萨切成对角切片");
}
void box() {
System.out.println("将披萨放入总部的PizzaStore包装盒中");
}
public String getName() {
return name;
}
//比萨的完整显示
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (String topping : toppings) {
display.append(topping + "\n");
}
return display.toString();
}
}
重新定义芝加哥风味芝士比萨:
//芝加哥风味重芝士比萨
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "芝加哥风味重芝士比萨";
dough = "厚饼";
sauce = "小番茄酱";
toppings.add("上面覆盖着高级的意大利芝士");
}
void cut() {
System.out.println("将比萨饼切成方形切片");
}
}
重新定义纽约风味轻芝士比萨:
//纽约风味轻芝士比萨
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "纽约风味轻芝士比萨";
dough = "博饼";
sauce = "意大利西红柿酱";
toppings.add("上面覆盖着意大利轻芝士");
}
//没有重写cut方法,即使用默认的切片方法
}
=================================定义比萨商店(创建者Creator)=================================
重新定义比萨商店:
//比萨商店基类
public abstract class PizzaStore {
//总部规定的订单流程已经成熟稳点,且不可更改(不可重写)
public final Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- 制作一个 " + pizza.toString() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/*
* 现在创建比萨的任务移到了一个方法中,这个方法就如同是一个工厂
* 具体如何创建比萨,由子类决定,但是比萨的订单处理流程,
* 完全由总部的orderPizza负责。
* */
abstract Pizza createPizza(String item);
}
重新定义芝加哥比萨商店:
//芝加哥比萨商店
public class ChicagoPizzaStore extends PizzaStore {
//使用之前ChicagoPizzaFactory的创建方式
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("CheesePizza")) {
pizza = new ChicagoStyleCheesePizza();
}else if(type.equals("GreekPizza")) {
//同CheesePizza
}else if(type.equals("PepperoniPizza")) {
//同CheesePizza
}
return pizza;
}
}
重新定义纽约比萨商店:
//纽约风味比萨店
public class NYPizzaStore extends PizzaStore {
//使用之前NYPizzaFactory的创建方式
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if(type.equals("CheesePizza")) {
pizza = new NYStyleCheesePizza();
}else if(type.equals("GreekPizza")) {
//同CheesePizza
}else if(type.equals("PepperoniPizza")) {
//同CheesePizza
}
return pizza;
}
}
编写测试类:
public class PizzaTest {
public static void main(String[] args) {
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza chicagoPizza = chicagoStore.orderPizza("CheesePizza");
System.out.println("购买了一个芝加哥风味比萨:"+chicagoPizza.toString());
System.out.println();
PizzaStore nyStore = new NYPizzaStore();
Pizza nyPizza = nyStore.orderPizza("CheesePizza");
System.out.println("购买了一个纽约风味比萨:"+nyPizza.toString());
}
}
输出结果:
--- 制作一个 ---- 芝加哥风味重芝士比萨 ----
厚饼
小番茄酱
上面覆盖着高级的意大利重芝士
---
准备 芝加哥风味重芝士比萨
揉面团。。。
添加酱。。。
添加佐料:
上面覆盖着高级的意大利重芝士350度烘烤25分钟
将比萨饼切成方形切片
将披萨放入总部的PizzaStore包装盒中
购买了一个芝加哥风味比萨:---- 芝加哥风味重芝士比萨 ----
厚饼
小番茄酱
上面覆盖着高级的意大利重芝士
--- 制作一个 ---- 纽约风味轻芝士比萨 ----
博饼
意大利西红柿酱
上面覆盖着意大利轻芝士
---
准备 纽约风味轻芝士比萨
揉面团。。。
添加酱。。。
添加佐料:
上面覆盖着意大利轻芝士350度烘烤25分钟
将披萨切成对角切片
将披萨放入总部的PizzaStore包装盒中
购买了一个纽约风味比萨:---- 纽约风味轻芝士比萨 ----
博饼
意大利西红柿酱
上面覆盖着意大利轻芝士
4.工厂方法模式介绍
所有工厂模式都用来封装对象的创建。工厂方法模式(Factory Method Pattern)通过让子类决定改创建的对象是上面,来达到将对象的创建过程封装的目的。
4.1类图
=================================第一种划分方式=================================
创建者:
产品类:
=================================第二种划分方式=================================
将一个orderPizza()方法和一个工厂方法联合起来,就可以成为一个框架。除此之外,工厂方法将生产知识封装进各个创建者,这样的做法,也可以被视为是一个框架。
创建者和产品类是平行的层级,它们的关系可看成如下关系:
4.2工厂方法模式的定义
工厂方法模式:
定义了一个创建对象的接口,但是由子类决定实例化哪个具体的类,工厂方法让类的实例化推迟到了子类。
5.工厂方法模式小结
- 即使只有一个具体的创建者,那么工厂方法模式仍然有有点,它可以帮助我们让产品"实现“从”使用“中解耦,日后新增或改变产品都不会影响抽象创建者,因为抽象创建者和具体创建者是松耦合的。
- 工厂方法不一定必须是抽象的,可以定义默认的工厂方法,这样即使抽象创建者没有任何子类也可以进行生产。
- 此工厂方法根据传入的参数不同("CheesePizza")从而创建出不同的比萨,这种方法是参数化工厂方法,当然有时我们可以去掉参数,因为很多时候工厂只会创建一种对象。
- 利用String作为工厂方法的参数并不严谨,我们可以用静态常量或者使用枚举作为参数。
- 工厂方法模式和简单工厂看起来很像,但不同的是,工厂方法模式定义了框架,具体让子类去实现,但是简单工厂却包揽了对象的创建和使用,简单工厂不能变更正在创建的产品,不具有工厂方法模式的弹性。
- 在工厂方法模式中,我们归根结底终究还是用new来创建对象了,但是这是不可避免的,因为这是Java的Api,但是工厂方法模式做出的贡献就是,对象的创建有了自己的边界,不是完全对外暴露的,是很有条理的。