工厂模式
场景描述:我们正在设计一个披萨店,因为我们对披萨的操作有一套固有的流程,比如切割,装盒等,但是我们有着非常多的披萨类型,风味不同,所以如何设计针对不同口味创建不同的披萨就成了整个程序的难点。
简单工厂
为了增加程序的弹性,并且避免频繁修改代码,我们将变化的部分拿出去,创建了一个简单工厂类。
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;
}
}
利用静态方法定义一个简单的工厂,这是很常见的技巧,常被称为静态工厂。为何使用静态方法?因为不需要使用创建对象的方法来实例化对象。getInstance()。
这样我们的披萨店代码就呼之欲出了。
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;
}
}
简单工厂其实并不是一个设计模式,反而比较像是一种编程习惯。有些开发人员的确是把这个编程习惯误认为是“工厂模式”。
下面是这个简单工厂的类图:
工厂方法
新的问题:当我们出现新的加盟店时,如纽约和芝加哥,它们的口味不太相同,这样我们就需要创建不同区域的特有工厂,然后再根据对应的口味生产特定的披萨。不过我们能够看出来,不同的工厂中,通过特定口味生产披萨这个步骤都是相同的,只不过最后生产的具体披萨不同而已,所以我们是否可以将其抽象出来呢?
我们尝试抽象披萨店的代码:
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
public abstract Pizza createPizza(String type);
}
然后针对不同地域去实现特定的制作方法
所有工厂模式都用来封装对象的创建。工厂方法模式(Factory Method Pattern)通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。
让我们来看一下通过这种方式来下单的流程如何:
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
工厂方法模式能够封装具体类型的实例化。而所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指编写创建者类时,不需要知道实际创建的产品是哪一个。
我们看一下工厂方法模式的类图:
再对比我们的披萨店,可以更加直观的感受
简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。
抽象工厂
让我们再看一个依赖性非常强的实现。
上图看出了,依赖过多,所以会导致非常大的弊端,每次新增种类,不仅需要修改代码,还造成了过多的依赖。
设计原则:要依赖抽象,不要依赖具体类。
这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,“两者”都应该依赖于抽象。
这就是“依赖倒置原则”(Dependency Inversion Principle)。
下图可以更加清晰地看出这句话的含义。
下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:
- 变量不可以持有具体类的引用。
- 不要让类派生自具体类。
- 不要覆盖基类中已实现的方法。
当然只是尽量达到这个原则,而不是随时都要遵守这个原则。
新的需求:我们又新增了新的需求,我们拥有不同地域的加盟店,并且它们使用的原料各不相同,而我们想要控制原料,而不是由他们自己提供原料,所以我们就需要一个新的设计来管理整个原料家族。
我们尝试建造一个工厂来创建所有原料。
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClams();
}
然后我们创建每个地域的工厂,如纽约的
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClams() {
return new FreshClams();
}
}
这时我们的披萨变为抽象的了。
public abstract class Pizza {
public String name;
public Dough dough;
public Sauce sauce;
public Veggies veggies[];
public Cheese cheese;
public Pepperoni pepperoni;
public Clams clams;
public abstract void prepare();
public void bake(){
System.out.println("Bake for 25 minutes at 350");
}
public void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
public void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
此时我们的不同口味披萨不再需要地域性了,因为地域性已经在原料中体现了。
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
public void prepare(){
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
这时我们再实现不同区域的披萨店
public class NYPizzaStore extends PizzaStore {
public Pizza createPizza(String item){
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (item.equals("cheese")){
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
}// 省略其他类型pizza
return pizza;
}
}
现在我们看一下一个披萨订单都经历了什么
上述模式就是一种新的工厂模式。
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
我们看一下它的类图:
比对一下我们的披萨店,更直观的理解一下
总结:
整个工厂方法模式,只不过就是通过子类来创建对象。用这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类来负责决定具体类型。换句话说,它将客户从具体类型中解耦。
而抽象工厂模式,提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。要想使用这个工厂,必须先实例化它,然后将它传入一些针对抽象类型所写的代码中。换句话说,它将客户从所使用的实际具体产品中解耦。
当你需要创建产品家族和想让制造的相关产品集合起来时,你可以使用抽象工厂。
而当你需要把你的客户代码从需要实例化的具体类中解耦,或者如果你目前还不知道将来需要实例化哪些具体类时,就可以使用工厂方法。