工厂模式是封装对象创建代码的模式。
前面几个章节我们了解到了面向对象编程的几个设计原则,其中针对接口编程要求我们面向接口编程而不是面向实现编程,但是前面章节中类的创建都伴随着具体类的创建(类似下图的实现过程),这就违背了面向接口编程的原则,并且,在增加了新的实现类时,这段代码也势必会违背“对扩展开放,对修改关闭“原则,这都不是我们想要的。
Duck duck;
if (picnic) {
duck = new MalllardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else {
duck = new RubberDuck();
}
如何实现对象实例化的封装呢?现在我们看这样一个场景,加入你现在是披萨店的主人,你需要在客人点餐时给出他们想要的披萨,首先你可能这样实现:
Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
为了创建更多类型的披萨,需要根据类型创建出不同的披萨实例:
Pizza orderPizza() {
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();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
但是新类型的披萨加入到菜单中了,销量落后的披萨可能也需要进行改进,所以我们在每次菜单修改时可能需要像下面一样对代码进行修改:
Pizza orderPizza() {
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();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
这违背了对修改关闭的原则,所以我们将披萨实例化的过程提取出来,放到披萨工厂中:
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();
}
}
}
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
Pizza orderPizza() {
Pizza pizza;
pizza = factory.createPizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// other methods...
}
这就是简单工厂模式,下面是它的类图结构:

现在需求发生了变化,多了数个加盟店,分别是纽约、芝加哥和加州店,而加盟店希望工厂能根据地域作出口味的调节,为此我们的工厂设计也需要作出相应调整。
首先因为不同的地域有不同的披萨口味,所以披萨工厂需要进行抽象,以此来创建不同口味的披萨,然后因为存在不同地域的披萨,所以披萨店也需要抽象,好根据地域创建不同地域的披萨店实例。
// 披萨店抽象类
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
abstract Pizza createPizza(String type);
}
// 披萨店具体类
public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (type.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (type.equals("clam")) {
return new NYStyleClamPizza();
} else if (type.equals("peperoni")) {
return new NYStylePeperoniPizza();
} else {
return null;
}
}
}
public abstract class Pizza {
String name;
String dough;
String sauce;
List toppings = new ArrayList();
void prepare() {
System.out.println("Preparing " + name);
System.out.println("Tossing dough");
System.out.println("Adding sauce");
System.out.println("Adding toppings: ");
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
}
这里createPizza方法相当于工厂,披萨店将创建披萨委托给了这个方法创建,并将其设置为抽象类,让每一个具体披萨店实现类能有自己的工厂方法实现,实现了工厂方法的解耦。

以上设计模式采用了工厂方法模式,工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。工厂方法模式的类图如下:

再来看看对象依赖,当你直接实例化一个对象时,就是在依赖它的具体类。最开始的设计方案中类对象之间的依赖关系如下图所示:

这样做的缺点在于,如果具体类的实现改变了,可能需要修改PizzaStore的代码,在具体类频繁增加和修改的情况下,PizzaStore的需要频繁维护,这样设计的可维护性非常差。
作为高级组件的披萨店类实际依赖了作为底层组件的披萨具体实现类,这时高级组件披萨店就依赖于低级组件披萨的具体实现类,这降低了代码的可维护性。
现在引入依赖倒置原则。依赖倒置表示要依赖抽象,不要依赖具体类。为了符合依赖导致原则,我们应用工厂方法将披萨具体实现提取出来,这样披萨店就依赖于披萨抽象,而披萨具体类也是披萨抽象类的实现,类图如下:

这样的好处在于后面维护披萨相关行为时,只要披萨店中的运行模式不变,就不用修改披萨店中的抽象实现,而只需要修改披萨具体类中的实现代码。即使会在披萨店中修改披萨行为,这也只需要修改披萨店抽象类中的创建披萨时的抽象代码逻辑,而不需要在每个具体抽象类中修改实现代码,从而将披萨的实例化和披萨店行为解耦了。
依赖倒置的指导原则有这三点:
- 变量不可以持有具体类的引用。
- 不要让类派生自具体类。
- 不要覆盖基类中已实现的方法。
代码不可能同时满足这些指导原则,但是在根据类间的依赖性编写代码的过程中,这些原则能帮助我们优化程序,对代码进行解耦。
工厂模式的最后一种是抽象工厂模式,现在在披萨店中加入各种各样的原材料,为了保持原材料的一致性和多样性,我们采用抽象工厂模式设计披萨原材料的代码结构。
现在我们需要创建一个原料工厂负责创建每一种原材料,这个原材料工厂决定用接口实现:
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(), new RedPepper()
};
return new Veggies[0];
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClams() {
return new FreshClams();
}
}
这是披萨抽象类:
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies[] veggies;
Cheese cheese;
Pepperoni pepperoni;
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;
}
String getName() {
return name;
}
public String toString() {
return "";
}
}
及披萨的一个实现类:
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
这是披萨店抽象类:
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza createPizza(String type);
}
及披萨店的一个实现类:
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if (type.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (type.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}
可以知道,这里在创建了具体地区的披萨店之后,会由披萨店创建原料工厂,在根据类型值创建披萨时,具体披萨类只负责实现prepare方法,会将工厂实例传递给披萨实例,最后由原料工厂实例确定类型披萨的具体原料。这就将披萨实现和原材料实现解耦了。
抽象工厂模式的定义:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确制定具体类。抽象工厂模式的类图如下图所示:

抽象工厂通常应对的是一个庞大的产品簇,它会将一系列产品的创建方法集中放在一个接口中,而工厂方法通常应对的是单个产品的创建,它会将产品创建代码放在一个抽象方法中去实现。会将产品的创建代码放在一个抽象方法中实现这点其实抽象工厂也是这么做的,所以抽象工厂的设计中其实包括了工厂方法模式,只不过它们应对的场景不一样,一个是为了创建一系列产品的设计方案而一个是为了创建一个产品的设计方案。
发表回复