我们平时都在使用new来创建一个新的对象,但是仔细想想,我们这样公开的初始化一个对象,会经常造成耦合问题。今天我们要说的这个工厂模式便可以帮我们从复杂的依赖中解脱出来。
一、披萨的故事
我们现在需要建一个能够制作披萨的程序,这里面有不同口味的披萨,同时还有披萨的所有步骤,这里给出了四个步骤(准备,烘烤,切割,打包)。
1、思考“new”
当我们使用“new”时,是在实例化一个具体的类,当有一群相关的具体类时,通常会写出这样的代码:
/**
* 披萨店类
*/
public class PizzaStore {
SimplePizzaFactory pizzaFactory;
public PizzaStore(SimplePizzaFactory pizzaFactory) {
this.pizzaFactory = pizzaFactory;
}
/**
* 点披萨
*
* @param type 类型
* @return Pizza 披萨
*/
Pizza orderPizza(String type) {
Pizza pizza;
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
} else if ("pepperoni".equals(type)) {
pizza = new PepperoniPizza();
} else {
return null;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
这里有一群要实例化的具体类,究竟实例化哪个类,要在运行时由一些条件决定。但是当程序有变化或者需要扩展的时候,就必须重新打开这段代码进行检查和修改,这会使我们的代码更加脆弱,缺乏弹性,容易出现bug。
但是,“new”真的有错吗,答案当然是否定啦。真正有错的是“变化”。上面的设计没有遵循“开闭原则”!
下面我们用最简单的封装方式,将披萨的创建从 PizzaStore 类中移除,创建另一个类来实现披萨的创建,从而使披萨的创建过程对于 PizzaStore 来说,是不可见的。
二、简单工厂
1、简单工厂下的披萨定做流程UML
上面说的这种做法便是简单工厂方法,我们先上代码。
2、实现:
抽象披萨类:
/** * 披萨抽象类 */ public abstract class Pizza { /** * 披萨的名字 */ protected String name; /** * 准备 */ public void prepare() { System.out.println(name + " preparing"); } /** * 烘烤 */ public void bake() { System.out.println(name + " baking"); } /** * 切割 */ public void cut() { System.out.println(name + " cutting"); } /** * 打包 */ public void box() { System.out.println(name + " boxing"); } }
芝士披萨
/** * 芝士披萨 类 */ public class CheesePizza extends Pizza { public CheesePizza() { this.name = "CheesePizza"; } }
希腊披萨
/** * 希腊披萨 类 */ public class GreekPizza extends Pizza { public GreekPizza() { this.name = "GreekPizza"; } }
腊香肠披萨
/** * 腊香肠披萨 类 */ public class PepperoniPizza extends Pizza { public PepperoniPizza() { this.name = "PepperoniPizza"; } }
简单披萨工厂类
/** * 简单披萨工厂类 */ public class SimplePizzaFactory { /** * 创建披萨 * @param type 类型 * @return Pizza */ public Pizza createPizza(String type) { Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(); } else if ("greek".equals(type)) { pizza = new GreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(); } else { return null; } return pizza; } }
改良后的披萨店
/** * 披萨店类 */ public class PizzaStore { SimplePizzaFactory pizzaFactory; public PizzaStore(SimplePizzaFactory pizzaFactory) { this.pizzaFactory = pizzaFactory; } /** * 点披萨 * * @param type 类型 * @return Pizza 披萨 */ Pizza orderPizza(String type) { /*Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(); } else if ("greek".equals(type)) { pizza = new GreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(); } else { return null; }*/ Pizza pizza = pizzaFactory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
3、定义简单工厂
首先,要强调一点,简单工厂不是设计模式的一种!!!而更像是一种编程习惯,并且在实际应用中经常被用到。
4、现在呢,由于披萨卖的很好,披萨店的老板准备开加盟店,但是,这时候问题就来了,每个加盟店所在的地域不尽相同,因此,设计出的披萨肯定更加符合当地人的口味,因此即便同样名字的披萨,可能因为地域的不同,口味会不相同。比如,对于(芝士披萨)CheesePizza,纽约有纽约的风味,芝加哥有芝加哥的风味。这边引出了我们下面要说的工厂方法。
三、工厂方法
1、下面我们用工厂方法来实现披萨店程序。这里,我们将 PizzaStore(披萨店)做成抽象类,将创建披萨交给子类去做。UML类图如下:
2、代码实现:
我们将披萨的子类重新定义,变为纽约风味的某某披萨,芝加哥风味的某某披萨
芝加哥风味的芝士披萨
/** * 芝加哥风味的芝士披萨 */ public class ChicagoStyleCheesePizza extends Pizza { public ChicagoStyleCheesePizza() { this.name = "ChicagoStyleCheesePizza"; } }
芝加哥风味的希腊披萨
/** * 芝加哥风味的希腊披萨 */ public class ChicagoStyleGreekPizza extends Pizza { public ChicagoStyleGreekPizza() { this.name = "ChicagoStyleGreekPizza"; } }
芝加哥风味的腊香肠披萨
/** * 芝加哥风味的腊香肠披萨 */ public class ChicagoStylePepperoniPizza extends Pizza { public ChicagoStylePepperoniPizza() { this.name = "ChicagoStylePepperoniPizza"; } }
纽约风味的芝士披萨
/** * 纽约风味的芝士披萨 */ public class NYStyleCheesePizza extends Pizza { public NYStyleCheesePizza() { this.name = "NYStyleCheesePizza"; } }
纽约风味的希腊披萨
/** * 纽约风味的希腊披萨 */ public class NYStyleGreekPizza extends Pizza { public NYStyleGreekPizza() { this.name = "NYStyleGreekPizza"; } }
纽约风味的腊香肠披萨
/** * 纽约风味的腊香肠披萨 */ public class NYStylePepperoniPizza extends Pizza { public NYStylePepperoniPizza() { this.name = "NYStylePepperoniPizza"; } }
披萨店
/** * 披萨店 抽象类 */ public abstract class PizzaStore { /** * 点披萨 * @param type 类型 * @return Pizza */ public Pizza orderPizza(String type) { Pizza pizza = createPizza(type); if (pizza != null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } return null; } /** * 创建披萨 由子类决定创建什么Pizza * @param type 类型 * @return Pizza */ public abstract Pizza createPizza(String type); }
芝加哥风味的披萨店
/** * 芝加哥风味的披萨店 */ public class ChicagoStylePizzaStore extends PizzaStore { /** * 创建具体的芝加哥风味的披萨 * @param type 类型 * @return Pizza */ @Override public Pizza createPizza(String type) { Pizza pizza; if ("cheese".equals(type)) { pizza = new ChicagoStyleCheesePizza(); } else if ("greek".equals(type)) { pizza = new ChicagoStyleGreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new ChicagoStylePepperoniPizza(); } else { return null; } return pizza; } }
纽约风味的披萨店
/** * 纽约风味的披萨店 */ public class NYStylePizzaStore extends PizzaStore { /** * 创建具体的纽约风味的披萨 * * @param type 类型 * @return Pizza */ @Override public Pizza createPizza(String type) { Pizza pizza; if ("cheese".equals(type)) { pizza = new NYStyleCheesePizza(); } else if ("greek".equals(type)) { pizza = new NYStyleGreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new NYStylePepperoniPizza(); } else { return null; } return pizza; } }
模拟顾客点单
/** * 模拟顾客点单 */ public class TestFactoryMethod { public static void main(String[] args) { PizzaStore nyStylePizzaStore = new NYStylePizzaStore(); PizzaStore chicagoStylePizzaStore = new ChicagoStylePizzaStore(); //分别点纽约 芝加哥风味的cheese披萨 nyStylePizzaStore.orderPizza("cheese"); chicagoStylePizzaStore.orderPizza("cheese"); } }
运行结果
从运行结果,工厂方法完美的解决了我么上面的问题,如果我们要删除或者新加某个风味的披萨,只需改对应的类就行了,减少了代码改动对整体的影响范围。从工厂方法的实现,我们可以看出,产品类和创建者类时平级的。这里用到了依赖倒置原则。
3、定义:
工厂方法模式:定义了一个创建对象的接口,但由子类确定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类。
4、优点:
①、一个调用者想创建一个对象,只要知道其名称就可以了。
②、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
③、屏蔽产品的具体实现,调用者只关心产品的接口。
5、缺点:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
6、使用场景:
①、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
②、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
③、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
7、注意事项:
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
8、现在呢,问题又出现了。我们建了纽约风味的披萨类,又建了芝加哥风味的披萨类,但是,我们想一下,他们的不同的地方无非就是准备阶段所做的工作不一样。所以我们想只建一个披萨类,至于是纽约风味的还是芝加哥风味的,我们可以通过抽象工厂来实现,下面让我们看一下抽象工厂模式。
四、抽象工厂方法
1、我们通过建立不同的披萨生产工厂,从未让准备阶段不相同,进而实现不同风味的披萨。我们的披萨店在创建披萨的时候,不需要关心创建的是啥,只需要将具体的工厂类传入即可。
Pizza类 将准备方法变为抽象方法,具体的实现在子类中实现
/** * 披萨抽象类 */ public abstract class Pizza { /** * 披萨的名字 */ protected String name; /** * 准备 */ public abstract void prepare(); /** * 烘烤 */ public void bake() { System.out.println(name + " baking"); } /** * 切割 */ public void cut() { System.out.println(name + " cutting"); } /** * 打包 */ public void box() { System.out.println(name + " boxing"); } }
披萨原料准备工厂 抽象类 建立了一个create方法,模拟“准备”方法所干的事,并简化
/** * 披萨原料准备工厂 */ public interface PizzaIngredientFactory { String create(); }
芝加哥风味的原料工厂 将 chicagoStyle 返回,模拟准备各种原料
/** * 芝加哥风味的原料工厂 */ public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory { @Override public String create() { return "chicagoStyle"; } }
纽约风味的原料工厂
/** * 纽约风味的原料工厂 */ public class NYPizzaIngredientFactory implements PizzaIngredientFactory { @Override public String create() { return "nyStyle"; } }
芝士披萨 初始化的时候,传入一个工厂,并在“准备”方法中获取相应的名字
/** * 芝士披萨 类 */ public class CheesePizza extends Pizza { PizzaIngredientFactory ingredientFactory; public CheesePizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } @Override public void prepare() { this.name = ingredientFactory.create() + "CheesePizza"; System.out.println(this.name + " preparing"); } }
希腊披萨
/** * 希腊披萨 类 */ public class GreekPizza extends Pizza { PizzaIngredientFactory ingredientFactory; public GreekPizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } @Override public void prepare() { this.name = ingredientFactory.create() + "GreekPizza"; System.out.println(this.name + " preparing"); } }
腊香肠披萨
/** * 腊香肠披萨 类 */ public class PepperoniPizza extends Pizza { PizzaIngredientFactory ingredientFactory; public PepperoniPizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } @Override public void prepare() { this.name = ingredientFactory.create() + "PepperoniPizza"; System.out.println(this.name + " preparing"); } }
PizzaStore
/** * 披萨店 抽象类 */ public abstract class PizzaStore { /** * 点披萨 * @param type 类型 * @return Pizza */ public Pizza orderPizza(String type) { Pizza pizza = createPizza(type); if (pizza != null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } return null; } /** * 创建披萨 由子类决定创建什么Pizza * @param type 类型 * @return Pizza */ public abstract Pizza createPizza(String type); }
芝加哥风味的披萨店 在创建披萨的时候,生成一个芝加哥原料工厂,并用这个工厂来创建具体的披萨
/** * 芝加哥风味的披萨店 */ public class ChicagoStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { PizzaIngredientFactory chicagoPizzaIngredientFactory = new ChicagoPizzaIngredientFactory(); Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(chicagoPizzaIngredientFactory); } else if ("greek".equals(type)) { pizza = new GreekPizza(chicagoPizzaIngredientFactory); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(chicagoPizzaIngredientFactory); } else { return null; } return pizza; } }
纽约风味的披萨店
/** * 纽约风味的披萨店 */ public class NYStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { PizzaIngredientFactory nyPizzaIngredientFactory = new NYPizzaIngredientFactory(); Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(nyPizzaIngredientFactory); } else if ("greek".equals(type)) { pizza = new GreekPizza(nyPizzaIngredientFactory); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(nyPizzaIngredientFactory); } else { return null; } return pizza; } }
模拟顾客点单
/** * 模拟顾客点单 */ public class TestAbstractFactory { public static void main(String[] args) { PizzaStore nyStylePizzaStore = new NYStylePizzaStore(); nyStylePizzaStore.orderPizza("cheese"); } }
运行结果:
通过抽象工厂,我们将程序进一步抽象,来达到我们想要的结果。
2、定义:
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类、