《HeadFirst设计模式》第四章-2.工厂方法模式

本文深入探讨工厂方法模式,解析其在解决比萨店连锁经营中口味多样化与生产流程标准化矛盾的应用,通过代码示例对比简单工厂模式,阐述工厂方法模式的优势及其实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.工厂方法模式小结

  1. 即使只有一个具体的创建者,那么工厂方法模式仍然有有点,它可以帮助我们让产品"实现“从”使用“中解耦,日后新增或改变产品都不会影响抽象创建者,因为抽象创建者和具体创建者是松耦合的。
  2. 工厂方法不一定必须是抽象的,可以定义默认的工厂方法,这样即使抽象创建者没有任何子类也可以进行生产。
  3. 此工厂方法根据传入的参数不同("CheesePizza")从而创建出不同的比萨,这种方法是参数化工厂方法,当然有时我们可以去掉参数,因为很多时候工厂只会创建一种对象。
  4. 利用String作为工厂方法的参数并不严谨,我们可以用静态常量或者使用枚举作为参数。
  5. 工厂方法模式和简单工厂看起来很像,但不同的是,工厂方法模式定义了框架,具体让子类去实现,但是简单工厂却包揽了对象的创建和使用,简单工厂不能变更正在创建的产品,不具有工厂方法模式的弹性。
  6. 在工厂方法模式中,我们归根结底终究还是用new来创建对象了,但是这是不可避免的,因为这是Java的Api,但是工厂方法模式做出的贡献就是,对象的创建有了自己的边界,不是完全对外暴露的,是很有条理的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琴瘦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值