Head First 设计模式笔记 4.工厂模式

摘要

这篇博客记录了new操作符新建对象不够灵活的问题,通过一个披萨系统讲解了简单工厂,工厂方法模式,抽象工厂模式。并对依赖倒置原则进行了简单的说明。

new的噩梦

小明又接到了新活,这次甲方是一家披萨店,要求设计一个披萨订单系统。作为一名优秀的程序设计师,小明很快有了思路。

//返回一个做好的披萨
Pizza orderPizza(){
	Pizza pizza = new Pizza()
	//下面是披萨的加工,烘烤,切片,装箱的过程
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

但是过了几天,披萨店老板表示:"小明,时代变了。现在我们有很多种披萨,我们要求用户要什么披萨,我们就做什么披萨。”

小明想,这个简单,只要把披萨抽象成一个接口,再用不同种类的披萨去继承它就好了。最后根据用户输入的字符串,生成所需的披萨。

//根据类型返回一个用户选定的披萨
Pizza orderPizza(String type){
	Pizza pizza;
	
	//根据用户的口味生成不同的披萨
	if(type.equals("cheese"))
		pizza = new CheesePizza();
	else if(type.equals("greek"))
		pizza = new GreekPizza();
	else if(type.equals("pipperoni"))
		pizza = new Pipperoni();
	
	//下面是披萨的加工,烘烤,切片,装箱的过程
	pizza.prepare();
	pizza.bake();
	pizza.cut();
	pizza.box();
	return pizza;
}

然而过了几天,老板表示:“小明,时代又变了,有的披萨经营不善,我们不做了。我们又加入了一些新的披萨种类。”这意味着小明又得忙活了。

然而就算这次修改了代码,随着时间过去,菜单改变,代码必须一改再改。这里的代码没有对修改封闭。

图1 不断修改的代码

问题出在哪里呢?小明仔细观察了一下代码,哦,原来问题出在了new这个关键字上。当看到new,就会想到具体。例如

Pizza pizza = new CheesePizza();

这里等号左边Pizza pizza使用接口的确让代码更具备弹性,但是等号右边new CheesePizza()还是得建立具体的实例。当使用具体类的时候,一旦引入新的具体类或者修改具体类,就必须修改代码。能不能不用new就新建对象呢?小明心想。

简单工厂

小明补习了设计模式秘籍,终于找到了好办法。秘籍告诉他,有一种方法叫简单工厂,把创建披萨的代码移到另一个对象,由这个对象专门负责制造披萨。这个负责制造披萨的对象,我们就叫它披萨工厂吧。

披萨工厂SimplePizzaFactory的代码。

public class SimplePizzaFactory {

	public Pizza createPizza(String type) {
		Pizza pizza = null;

		if (type.equals("cheese")) {
			pizza = new CheesePizza();
		} else if (type.equals("pepperoni")) {
			pizza = new PepperoniPizza();
		} else if (type.equals("clam")) {
			pizza = new ClamPizza();
		} else if (type.equals("veggie")) {
			pizza = new VeggiePizza();
		}
		return pizza;
	}
}

没有neworderPizza方法。再也无需再关心它所要制造什么种类的披萨,如果要修改披萨种类,只需要修改工厂代码就可以了。

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;
	}

}

有人会觉得这是个障眼法,实际上不过是把new的操作放到了另一个对象中,要修改类的时候,还是得不停地去修改那些if-else语句,不过是换了一个地方修改,这有什么意义呢?

小明表示,别忘了,我们的工厂可以有很多的客户,我们可能有个快递系统,也要创建披萨,假如所有的客户都要自己去new实例化这披萨,那么修改起来会更加麻烦。我们给所有的客户提供工厂创建接口,将所有的new实例化代码全部从客户程序删除。要修改实例化代码,只需要集中到工厂中修改就好了。

简单工厂并不是一个设计模式,只是一种编程习惯,即将创建对象A的任务放到简单工厂B中,以后创建A时候都调用简单工厂B就行了。接下来我们将讲讲两个重量级的设计模式。

工厂方法模式

披萨店老板在小明的设计下,击败了对手,在全国开起了连锁店。现在分店开到了纽约,芝加哥,加州。每个地方的披萨口味都不一样。
假如还是按照之前的简单工厂方法。那就要新建三个工厂。但是这么做未免太过繁琐,这三个工厂仅仅是披萨的味道不一样,加工流程都是一样的。小明想,我能不能从中抽出不变的部分,让它们继承同一个超类呢?说干就干,小明决定新建一个抽象类PizzaStore,将creatPizza()方法设为抽象。

图2 修改后的工厂

下面是他改造的抽象披萨店的代码

public abstract class PizzaStore {
 
	abstract Pizza createPizza(String item);
 
	public Pizza orderPizza(String type) {
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}

实现纽约披萨店的代码

package headfirst.designpatterns.factory.pizzafm;

public class NYPizzaStore extends PizzaStore {

	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new NYStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new NYStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

这里实际上用到了新的设计模式——工厂方法模式。工厂模式中,有两个角色

  • 抽象创建者类,定义了抽象的工厂方法,让子类去实现它制造产品。如PizzaStore,它提供的工厂方法是orderPizza()
  • 具体创建者类,继承了抽象创建者类,实现了工厂方法。如NYPizzaStore

工厂方法模式定义了创建对象的接口,但由子类决定具体要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

图3 工厂模式图解

依赖倒置原则

让我们来看看一个垃圾的披萨店设计

package headfirst.designpatterns.factory.pizzafm;

public class DependentPizzaStore {
 
	public Pizza createPizza(String style, String type) {
		Pizza pizza = null;
		if (style.equals("NY")) {
			if (type.equals("cheese")) {
				pizza = new NYStyleCheesePizza();
			} else if (type.equals("veggie")) {
				pizza = new NYStyleVeggiePizza();
			} else if (type.equals("clam")) {
				pizza = new NYStyleClamPizza();
			} else if (type.equals("pepperoni")) {
				pizza = new NYStylePepperoniPizza();
			}
		} else if (style.equals("Chicago")) {
			if (type.equals("cheese")) {
				pizza = new ChicagoStyleCheesePizza();
			} else if (type.equals("veggie")) {
				pizza = new ChicagoStyleVeggiePizza();
			} else if (type.equals("clam")) {
				pizza = new ChicagoStyleClamPizza();
			} else if (type.equals("pepperoni")) {
				pizza = new ChicagoStylePepperoniPizza();
			}
		} else {
			System.out.println("Error: invalid type of pizza");
			return null;
		}
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
}

这里我们在实例化不同的对象,实际上就是在依赖它的具体类。当这些具体类要修改的时候,就要修改依赖它的类。

图4 高度依赖的类

所以,在代码中,减少对具体类的依赖是我们的追求。这实际上就是“依赖倒置原则”所提倡的。

要依赖抽象,而不是具体类

听起来是不是很像“针对接口编程,不针对实现编程”,并不一样,这里更加强调的是抽象。它想要强调的是:不要让高层组件依赖低层组件。并且高层组件和低层组件都应该依赖于抽象。例如上文中,高层组件“PizzaStore”就依赖于各种低层组件披萨类。我们应该去重写代码以便于我们依赖于抽象类,而不依赖具体类。

现在就让根据这个原则改造一下代码,在上述代码中,我们需要自己去实例化每个具体的披萨类。我们可以用工厂方法把实例化的过程抽取出来。

图5 依赖置换后

那么为什么要叫依赖置换原则呢?我们置换了什么呢?

在以往的设计的流程中,我们要开一个披萨店,就要提供各种口味的披萨。这样披萨店就会牢牢依靠这些具体的披萨种类。后面的设计里,我们抛开具体的披萨种类,将所有口味的披萨抽象化。得到一个抽象的Pizza类。这样,我们的过去的高层组件(披萨店)依赖于底层组件(各种口味的披萨),现在,无论高层组件(披萨店)还是底层组件(各种口味的披萨)都依赖于抽象的Pizza类。

抽象工厂模式

现在披萨店已经具备了弹性的框架,并且遵循设计原则。只剩下具体的披萨没有设计了。这里,我们发现,披萨是由面团,酱料,芝士,佐料构成。而面团,酱料等组件有不同的种类。这些不同种类搭配出了不同口味的披萨。

分析一下,就可以发现,所有的披萨具有相同的组件,而这些披萨的组件有不同的实现。不同区域的披萨店实现了完整的原料家族。

  • 根据之前的工厂模式,我们建立一个抽象的原料工厂PizzaIngredientFactory。然后用具体制造的工厂实现它,用来生产不同的原料。
  • 把每种披萨的原料如面粉,酱料等设计为接口,让具体的类去继承这个接口。
  • 披萨店的具体实例作为抽象原料工厂PizzaIngredientFactory的客户。

我们设计出了这样的类图。

图5 抽象工厂类图

这里,我们邂逅了新的设计模式——抽象工厂模式,这个模式可以创建对象的家族,例如披萨,一套时装。PizzaIngredientFactory就是一个抽象工厂。

抽象工厂模式提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确具体的种类。

抽象工厂模式 VS 工厂方法模式

抽象工厂模式和工厂方法模式都用于创建对象,将创建对象这一任务单独抽出。但是它们的实现方法不同。

  • 工厂方法模式主要用继承,用具体类继承抽象类,覆盖它的工厂方法。这样,客户就只需要关心他们所使用的抽象类型就可以了,而不必去担心具体的类。一般适用于计划不同条件下创建不同实例时。
  • 抽象工厂模式主要使用组合,将一组产品集合为一个抽象类,然后让子类去实现它。主要用于创建一个产品家族的抽象类型。不过,当加入新的产品的时候,就需要修改接口。一般用于创建对象家族。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Head First设计模式》是一本设计模式学习的畅销书籍。这本书的作者以幽默风格和易于理解的语言,引导读者深入了解面向对象设计的基础知识和常见设计模式的应用。 《Head First设计模式》首先介绍了面向对象设计原则,如开闭原则、依赖倒置原则和单一职责原则等。它强调了设计模式的重要性,这些模式是在常见的软件开发场景中提供解决方案的经验总结。 接着,书中详细介绍了23个经典的设计模式,分为创建型模式、结构型模式和行为型模式三个大类。每个模式都通过生动的例子和图表进行解释,使读者能够更好地理解其应用场景和使用方法。同时,还提供了一些实际案例,帮助读者将学到的知识应用到实际项目中。 《Head First设计模式》以问题驱动的方式进行讲解,通过解决具体的问题来引导读者理解模式的思想和用法。这种方式使得学习变得更加有趣和互动,读者可以通过参与问题的解决过程来更好地理解模式的实际应用。 总的来说,这本书是一本通俗易懂的设计模式入门教材。它以轻松幽默的方式向读者介绍了设计模式的基本概念和常见应用。读者通过阅读这本书,能够对设计模式有一个清晰的理解,并能够在实际项目中灵活运用。无论是初学者还是有一定经验的开发者,都能从中收获实用的知识和提升自己的设计能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值