预设场景
假设我们要经营一个披萨店,我们希望根据用户要求的为其提供不同口味的披萨,例如用户可能喜欢:CheesePizza,PepperoniPizza,PepperoniPizza,ClamPizza,VeggiePizza等等,创造出这样一块符合用户口味的披萨之后,我们再对其进行准备,烘烤,切片和包装等一系列加工,那么就产生这么一个预订披萨的方法orderPizza()。
//披萨店的实现类
public class PizzaShop {
//预订披萨方法
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("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")){
pizza = new VeggiePizza();
} else {
pizza = new CommonPizza();
}
//加工
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
但是,当披萨店想要新增或者下架一些口味的披萨时,就需要对这个披萨店的orderPizza()的逻辑做一定的修改,这违反了我们的设计原则:
面对修改关闭,面对拓展开放
我们已经知道面对新的需求,有哪些地方是会发生改变的,就应该使用封装去设计,比如:简单工厂。
简单工厂
我们可以以上orderPizza()方法中,创建实例的代码放到一个简单工厂中,由这个工厂负责所有实例的创建,而orderPizza()方法只负责对Pizza进行加工,因此我们代码就变成:
//披萨店的实现类
public class PizzaShop {
SimplePizzaFactory factory;
public PizzaShop(SimplePizzaFactory factory) {
this.factory = factory;
}
//预订披萨方法
Pizza orderPizza(String type) {
Pizza pizza;
//简单工厂负责创建实例
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
//简单披萨工厂
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")){
pizza = new VeggiePizza();
} else {
pizza = new CommonPizza();
}
return pizza;
}
}
这样我们就把创建实例的代码放到了简单工厂中,如果有新的需求,我们只需要在简单工厂中修改,披萨店PizzaShop的代码就可以面对修改封闭。同时这个简单工厂还可以被多个不同的PizzaShop实现类调用,这样也能更好的实现模块化。
其实简单工厂严格上来说不是一种设计模式,而更像是一种编程习惯
工厂模式
- 代码演练
当在使用简单工厂创建披萨的时候,你会发现,调用工厂的不同披萨店(我们称其为加盟店),每个店铺除了创建Pizza是用了我们的工厂创建,其他的加工部分(prepare,bake,cut,box)都是用他们自己店铺的实现,加工的方法每个店铺都不一样。
想想这个问题,我们希望能够建立起一个框架,把加盟店和所创建的Pizza绑定到一起,多一些加工流程的质量控制,同时又具有一定的弹性。
所以我们创建一个抽象的披萨店类PizzaStore作为工厂类:
//抽象类披萨店(工厂类)
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);
}
此时这个抽象披萨店类已经有一个不错的订单系统了,orderPizza()方法负责处理订单,能满足我们希望所有加盟店对订单处理一致的要求
各个不同的披萨店之间的差异主要在于他们制作披萨的风味,我们按区域划分不同的披萨店,每个披萨店都有自己的特色,比如(纽约的披萨饼薄,芝加哥的披萨饼厚),现在我们就想让抽象披萨店中的createPizza()方法能够面对这些变化来创建出正确种类的披萨。做法就是让子类自己负责createPizza()方法,这也是为什么该方法是抽象的原因,如图:
加盟店有它自己的特色,又可以从PizzaStore继承标准的加工方法,加盟店只需要提供createPizza()方法的实现即可,以下是两个加盟店的实现编码:
//纽约哥加盟披萨店
public class NYPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new NYStyleCheesePizza();
} else if (type.equals("greek")) {
pizza = new NYStyleGreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new NYStylePepperoniPizza();
} else if (type.equals("clam")) {
pizza = new NYStyleClamPizza();
} else if (type.equals("veggie")){
pizza = new NYStyleVeggiePizza();
} else {
pizza = new NYStyleCommonPizza();
}
return pizza;
}
}
//芝加哥加盟披萨店
public class ChicagoPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new ChicagoStyleCheesePizza();
} else if (type.equals("greek")) {
pizza = new ChicagoStyleGreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new ChicagoStylePepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ChicagoStyleClamPizza();
} else if (type.equals("veggie")){
pizza = new ChicagoStyleVeggiePizza();
} else {
pizza = new ChicagoStyleCommonPizza();
}
return pizza;
}
}
此时我已经有两家披萨加盟店了,准备来写一个测试类,尝试着用加盟店订单系统预订披萨,在此之前,我们还需要Pizza本身的类:
//披萨抽象类
public abstract class Pizza {
//名字
String name;
//饼材料
String dough;
//酱汁
String sauce;
//辅料
ArrayList toppings = new ArrayList();
public void prepare() {
System.out.println("preparing " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce");
System.out.println("Adding sauce");
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
public void bake() {
System.out.println("Bake for 25 minutes at 350");
}
public void cut() {
System.out.println("Cut the pizza into diagonal slices");
}
public void box() {
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
}
同时我们还需要一些具体的Pizza子类来定义纽约和芝加哥风味的披萨,这里给出两个芝士口味的披萨作为范例,其他的子类Pizza实现形式差不多:
//纽约芝士披萨
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
//芝加哥芝士披萨
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded mozzarella Cheese");
}
public void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
测试案例:
public class PizzaTestDrive {
public static void main(String[] args) {
NYPizzaStore nyPizzaStore = new NYPizzaStore();
ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
Pizza nyCheesePizza = nyPizzaStore.createPizza("cheese");
System.out.println("小二预订了:" + nyCheesePizza.getName());
Pizza chicagoCheesePizza = chicagoPizzaStore.createPizza("cheese");
System.out.println("张三预订了:" + chicagoCheesePizza.getName());
}
}
测试结果:
小二预订了:NY Style Sauce and Cheese Pizza
张三预订了:Chicago Style Deep Dish Cheese Pizza
Process finished with exit code 0
- 优势分析
工厂模式实际上定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类吧实例化推迟到子类
当我们实例化一个对象时,实际上就是在依赖它的具体类,当我们返回观看第一个代码块时,可以发现这个PizzaShop的订单系统依赖所有的具体披萨类,依赖关系如图:
一但这些披萨具体类发生变化,我们披萨店订单系统需要跟着变化,如此高度耦合的代码,不是一个很好的设计。
而减少代码中对具体类的依赖一直是我们推崇的设计方法,这也是面向对象的设计原则,这也有一个比较响亮的名字:依赖倒置原则(Dependency Inversion Principle)。
虽然我们创建了一个抽象Pizza类,但是我们也在代码中实实在在地创建了Pizza,所以这个抽象没有什么影响力。
如何在orderPizza()方法中把这些实例对象代码独立出来?工厂方法刚好能派上用场,用上工厂方法之后,依赖图就变成:
这样高层组件(PizzaShop)变成只依赖Pizza抽象类,底层组件(披萨具体类)也也来Pizza抽象类,这样就实现了依赖倒置,对代码的实现进行了解耦。想要实现依赖倒置,工厂方法可能不是唯一的方法,但是确是一个最好的方法。
抽象工厂模式
我去复习一下…