工厂模式
简单工厂
简单工厂的定义:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。
假如有一个披萨店,当下订单的时候代码可能是这样写的:
Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
我们发现在上述代码中实例化对象的时候使用了 new Object()的形式。但对于披萨来说,有很多类型。所以为了能够按照用户下单时所选择的类型进行制作披萨,必须增加一些代码。
Pizza orderPizza() {
Pizza pizza;
/**
* 根据pizza的类型来实例化具体的类,然后赋值给pizza变量。值得注意的是,这里的任何披萨都必须实现Pizza接口
*/
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();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是在经营过程中,可能有的一些新口味的披萨开始流行需要加入到菜单中,或是有的口味的披萨由于销售量太低想从菜单中去除。随着时间的变化,对应的代码就必须一改再改。而对于pizza的制作过程来说是不易改变的。考虑到已经知道哪些会改变,哪些不会改变,该是使用封装的时候了。我们应该将披萨对象的创建移动到orderPizza()之外,把创建披萨的代码移动到另外一个对象中,让这个新对象专职创建披萨。我们称这个新对象就为“工厂”。
将披萨的生成交给工厂后,orderPizza()方法便不在需要关心生成披萨的细节问题,只关心从工厂得到了一个披萨,并且这个披萨实现了Pizza接口,所以它可以调用prepare(),bake(),cut(),box()来进行准备、烘烤 、切片、装盒。具体实现代码如下:
package pizzas;
public class SimplePizzaFactory {
/**
* 创建pizza对象的工厂方法
*/
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;
}
}
package pizzas;
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
//把new一个对象替换为用工厂对象创建对象,不再使用具体实例化。
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
工厂方法
工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
还是拿披萨店来举例子,当披萨店的经济效益越来越好,大家都希望披萨店能够有更多的加盟店。但是不同区域的加盟店提供的披萨口味可能存在差异。如果利用简单工厂写出几种不同的工厂来对应不同区域的确可以解决披萨口味不同的问题。但是对于其他部分却限制不够,各地的加盟店可以对制作流程进行改变,有的店可能会选择不将披萨切片,或者使用其他厂商的包装盒。但是作为一种品牌的加盟,希望的是能够有统一的标准,所以品牌会希望建立一个框架,能够把加盟店和创建披萨捆绑在一起,并且还能保持一定的弹性。
有个做法可以让披萨制作活动局限于PizzaStore类,而同时又能让这些加盟店依然可以自由的制作该地区的口味。我们将createPizza()方法放回到PizzaStore中并且将它设置成抽象方法,然后为每个区域口味来创建一个PizzaStore的子类。
package pizzafm;
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;
}
}
现在有了一个PizzaStore作为超类,让每个区域类型都继承这个超类,然后自己决定如何制造披萨。
对于披萨本身来说,我们也需要一个超类,让不同类别的披萨能够继承这个超类。
package pizzafm;
import java.util.ArrayList;
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList<String> toppings = new ArrayList<String>();
void prepare() {
System.out.println("Prepare " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings: ");
for (String topping : toppings) {
System.out.println(" " + topping);
}
}
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cut the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
@Override
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();
}
}
现在我们已经创建了披萨和商店的超类,接下来是他们的实现类
package pizzafm;
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");
}
@Override
void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
package pizzafm;
public class ChicagoStyleClamPizza extends Pizza {
public ChicagoStyleClamPizza() {
name = "Chicago Style Clam Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
toppings.add("Frozen Clams from Chesapeake Bay");
}
@Override
void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
package pizzafm;
public class ChicagoStylePepperoniPizza extends Pizza {
public ChicagoStylePepperoniPizza() {
name = "Chicago Style Pepperoni Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
toppings.add("Black Olives");
toppings.add("Spinach");
toppings.add("Eggplant");
toppings.add("Sliced Pepperoni");
}
@Override
void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
package pizzafm;
public class ChicagoStyleVeggiePizza extends Pizza {
public ChicagoStyleVeggiePizza() {
name = "Chicago Deep Dish Veggie Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
toppings.add("Black Olives");
toppings.add("Spinach");
toppings.add("Eggplant");
}
@Override
void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
package pizzafm;
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");
}
}
package pizzafm;
public class NYStyleClamPizza extends Pizza {
public NYStyleClamPizza() {
name = "NY Style Clam Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
toppings.add("Fresh Clams from Long Island Sound");
}
}
package pizzafm;
public class NYStylePepperoniPizza extends Pizza {
public NYStylePepperoniPizza() {
name = "NY Style Pepperoni Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
toppings.add("Sliced Pepperoni");
toppings.add("Garlic");
toppings.add("Onion");
toppings.add("Mushrooms");
toppings.add("Red Pepper");
}
}
package pizzafm;
public class NYStyleVeggiePizza extends Pizza {
public NYStyleVeggiePizza() {
name = "NY Style Veggie Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
toppings.add("Garlic");
toppings.add("Onion");
toppings.add("Mushrooms");
toppings.add("Red Pepper");
}
}
package pizzafm;
public class ChicagoPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else if (item.equals("veggie")) {
return new ChicagoStyleVeggiePizza();
} else if (item.equals("clam")) {
return new ChicagoStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
} else {
return null;
}
}
}
package pizzafm;
public class NYPizzaStore extends PizzaStore {
@Override
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;
}
}
}
最后创建测试类:
package pizzafm;
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("clam");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("clam");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("pepperoni");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("pepperoni");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
pizza = nyStore.orderPizza("veggie");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("veggie");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}
抽象工厂
在上述的例子中,比萨店的设计已经变得很棒:具有弹性的框架,而且遵循设计原则。现在,比萨店的成功关键在于新鲜高质量的原料。而且通过导入新的框架,加盟店将遵循你的流程,但是有一些加盟店使用低价的原料来增加利润。如何确保每家加盟店使用高质量的原料变成了一个问题。
为了解决原料的问题,我们要建一个工厂来生产原料,这个工厂负责创建原料家族中的每一种原料。我们需要做的事:
1.为工厂定义一个接口,这个接口负责创建所有的原料
2.为每个区域建造一个工厂。需要创建一个继承自PizzaIngredientFactory的子类来实现每一个创建方法
3.实现一组原料类供工厂使用,这些类可以在合适的区域间共享
4.然后最后将一切组织起来,将新的原料工厂整合进久得PizzaStore代码中
在创建披萨的时候通过工厂的形式来创建原材料
PizzaIngredientFactory ingredientFactory =
new NYPizzaIngredientFactory();
NYPizzaIngredientFactory是一个实现了PizzaIngredientFactory的类,其中函数返回的对象都是实现了对应接口的原材料。
package pizzaaf;
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClam() {
return new FreshClams();
}
}
package pizzaaf;
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
完整代码请访问 https://github.com/caicaing/pizzaaf
一连串的代码的改变,我们到底做了什么?我们通过引入新类型的工厂,也就是所谓的抽象工厂,来创建披萨家族。通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口书写代码。我们的代码将从实际工厂解耦,以便于在不同上下文中实现各式各样的工厂,制造出各种不同的产品。因为代码从实际的产品中解耦了,所以我们可以替换不同的工厂来取得不同的行为。