今天是第几天我也忘了,反正就是继续写。前几天的还需要完善,今天力求完整!
奠定基调的例子
作为一名披萨店店主,我们需要售卖不同种类的披萨,所以就需要设计很多披萨的类型,并且需要增加一下代码来确定顾客需要的是哪种披萨,然后再进行烘焙包装售卖。
我们的披萨不是一成不变的,是需要有提供新类型的,而且还有一些披萨卖的不好需要下架,这时候我们就需要修改代码了。
所以我们把需要创建对象的部分抽出来,独立成一个类,就是把创建披萨的代码拿到另一个对象中,由这个对象专门创建创建披萨对象。这个新对象就被我们称之为「工厂」。
既然我们已经有了一个简单工厂,那么我们再创建披萨对象的时候只需要叫工厂加工一个,再去做烘烤、切片、装盒操作。现在我们就来制作这么一个简单披萨工厂。
简单工厂代码
首先是 Pizza 超类,定义了一个属性,描述自己的类型,还有几个加工工艺方法。
public class Pizza {
String description = "pizza";
public void prepare() {
System.out.println("prepare: " + description);
}
public void bake() {
System.out.println("bake: " + description);
}
public void cut() {
System.out.println("cut: " + description);
}
public void box() {
System.out.println("box: " + description);
System.out.println(description + " successful");
}
}
定义几个 Pizza 子类,就是修改下继承到父类的属性。
public class CheesePizza extends Pizza {
public CheesePizza() {
this.description = "cheese pizza";
}
}
public class ClamPizza extends Pizza {
public ClamPizza() {
this.description = "clam pizza";
}
}
public class PepperoniPizza extends Pizza {
public PepperoniPizza() {
this.description = "pepperoni pizza";
}
}
public class VeggiePizza extends Pizza {
public VeggiePizza() {
this.description = "veggie pizza";
}
}
简单披萨工厂代码,通过一个 createPizza(String type) 方法,传入需要的披萨类型,返回一个披萨对象。
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;
}
}
披萨店代码,包含一个披萨工厂属性,制作披萨的方法,传入一个类型,按照需要制作披萨。
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
定义一个测试方法。
public static void main(String[] args) {
new PizzaStore(new SimplePizzaFactory()).orderPizza("cheese");
}
披萨制作完成,实际上啊,简单工厂并不是一个设计模式,更类似于是一种编程习惯。由于经常使用,所以习惯上把它看成是「简单工厂模式」。
简单工厂免除了咱们的披萨店直接创建披萨对象的工作,由工厂代工,而仅仅「使用」创建好的披萨;简单工厂模式对通过这种做法实现了对责任的分割,提供了专门的工厂对象创建需要的对象,在使用的时候无需知道具体的产品类名,只需要提供特定的标识即可创建对应的对象,这样无需使用者记住要创建的类名,省去不必要的记忆量。
但是由于我们的工厂对象集中了所有创建对象的逻辑,假设由于一个小 bug,这个工厂对象罢工了,那我们整个系统都会由于这个环节出问题而受影响。还有就是破坏了「开放-封闭原则」,添加新产品的时候,不得不修改工厂的逻辑,添加的多了,那这个工厂类就过于复杂了,不利于系统的扩展和维护。
工厂方法模式
所有工厂模式都用来封装对象的创建。工厂方法模式,是通过让子类决定该创建的对象是什么,来打到将对象创建的过程封装的目的。
有一个顶层的抽象创建者类,定义一个抽象的工厂方法用以子类创建差您,创建者通常会包含依赖抽象产品的代码,而这些抽象产品由子类制造。创建者不需要知道在哪制造具体产品。
现在我们来看看 PizzaStore 的代码。
public abstract class PizzaStore {
abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.description + "---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
抽象方法 createPizza(String item) 用于子类创建对象,而 orderPizza(String type) 是对我们创造者类创建对象的代码的调用返回包装完的披萨。
接着我们来看创建者类。
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();
}
return null;
}
}
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
}
return null;
}
}
继承上面的 PizzaStore 类,实现 createPizza(String item) 方法,根据不同需求创建不同的披萨对象,子类创建的对象供父类使用,包装完毕后披萨制造完成。这就是我们刚说的把需要创建的对象交给子类来创建,达到封装创建对象的目的。
继续我们来看产品的超类,Pizza 类。
public abstract class Pizza {
String description = "Pizza";
void prepare() {
System.out.println("Prepare " + description);
}
void bake() {
System.out.println("Bake " + description);
}
void cut() {
System.out.println("Cut " + description);
}
void box() {
System.out.println("Box " + description);
System.out.println(description + " successful");
}
}
没啥特别的,就是有一个 description 属性,用以描述是什么种类的披萨,还有几个准备、烘焙、切片和包装的方法,在创建者超类中的 orderPizza(String type) 中使用。下面只列举两个披萨类的代码,其他的也都一样,只是更改了 description 属性。
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
this.description = "Chicago style cheese pizza";
}
}
public class NYStyleClamPizza extends Pizza {
public NYStyleClamPizza() {
description = "NY style clam pizza";
}
}
最后我们来看测试类。
public class Test {
public static void main(String[] args) {
ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
NYPizzaStore nyPizzaStore = new NYPizzaStore();
chicagoPizzaStore.orderPizza("cheese");
System.out.println("-------------------");
chicagoPizzaStore.orderPizza("clam");
System.out.println("-------------------");
chicagoPizzaStore.orderPizza("pepperoni");
System.out.println("-------------------");
chicagoPizzaStore.orderPizza("veggie");
System.out.println("-------------------");
nyPizzaStore.orderPizza("cheese");
System.out.println("-------------------");
nyPizzaStore.orderPizza("clam");
System.out.println("-------------------");
nyPizzaStore.orderPizza("pepperoni");
System.out.println("-------------------");
nyPizzaStore.orderPizza("veggie");
}
}
只需要创建两个披萨店对象,然后调用 orderPizza(String type) 方法,传入需要传入的字符串就可以得到包装好的披萨了,当然我没有声明变量去引用这个披萨,如果需要的话,只声明一个变量即可Pizza pizza = nyPizzaStore.orderPizza("veggie")
。
产品超类以及子类们和创建者超类以及子类们是平行的类层级,他们都有抽象类,而抽象类都有许多具体的子类,每个子类都有自己特定的实现。
工厂方法模式的定义:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
工厂方法模式能够封装具体类型的实例化。在抽象的创建者超类中,其他的实现的方法都有可能会用到工厂方法创建的对象,但是创建产品的位置是实现了这个抽象的工厂方法的子类中。
让子类决定创建哪个产品对象实际上是指创建者类中并不知道具体要创建的产品是哪一个。在根据类型选择使用哪个类时才决定了实际创建的是哪个产品的对象。
工厂方法模式是在创建对象时需要大量重复的代码,且客户端并不需要知道产品需要如何创建的情况。作为用户,我们只需要知道哪个工厂,还有需要什么标识,这样我们就可愉快的创建对象并使用。工厂方法模式的缺点也是很明显,可以从代码中看出,需要创建的类很容易飙升,很多,增加系统的复杂度,可能会增加程序员的理解难度。
在这个设计模式中又涉及到一个设计原则——依赖倒置原则:要依赖抽象,不要依赖具体类。
不能让高层组件依赖低层组件,而且,不管高层或底层组件,都应该依赖于抽象。
我们的例子中,PizzaStore 是「高层组件」,而披萨是「低层组件」,PizzaStore 依赖的 Pizza 抽象类,符合我们的原则。
抽象工厂模式
现在我们的披萨店由于很成功,出现了加盟店的加入,而我们披萨的成功就是因为我们原料新鲜质量高,现在我们想让加盟店也按照我们的原料流程进行制作披萨,所以我们就新增加了一个原料工厂,确保原料的质量。
先来看看原料工厂的接口。
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
这个工厂的接口负责创建所有的原料,所以我们的每个区域的工厂类都应该是这个类的子类,并实现每一个创建原料的方法。
我们接下来创建一个纽约原料工厂,实现上面的接口。
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 veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClam() {
return new FreshClams();
}
}
现在创建一个披萨超类。
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies[] veggies;
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
abstract void prepare();
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");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
@Override
public String toString() {
return "Pizza{" +
"name='" + name + '\'' +
", dough=" + dough +
", sauce=" + sauce +
", veggies=" + Arrays.toString(veggies) +
", cheese=" + cheese +
", pepperoni=" + pepperoni +
", clams=" + clams +
'}';
}
}
这个披萨中,包含几个属性,是我们的原料属性,然后有一个抽象方法 prepare(),收集我们的原料,还有就是制作比萨必备的工序。
接下来制作几个品种的披萨。
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
public class ClamPizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public ClamPizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
包含一个属性,是原料工厂的属性,在 prepare() 方法中使用工厂进行原料的创建。这就是神奇的地方了,把原料的创建与披萨超类分隔开,这样 Pizza 类就完全不关心原料是怎样来的,可以轻易的复用,获取原料是在他的子类实现的 prepare() 方法以及工厂属性共同创建的。当我们需要什么样的披萨,只需要创建一个披萨子类继承超类,然后传入对应的披萨工厂即可获得对应的披萨原料。
现在我们再回到披萨店。
public abstract class PizzaStore {
abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.name + "---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
public abstract class PizzaStore {
abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.name + "---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
public class NYPizzaStore extends PizzaStore {
@Override
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");
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (item.equals("clam")) {
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if (item.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}
纽约披萨店通过对应的标识创建对应的披萨,但在创建披萨对象的时候,将披萨工厂传入披萨对象,用以提供原料。
总结一下抽象工厂模式,我们引入了新类型的工厂,创建披萨原料族,通过抽象工厂所提供的接口,可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各种各样的工厂,从而制造出各种不同的产品。
由于我们创建原料的方法是在工厂的子类中实现的,所以我们可以通过创建新的工厂类来实现原料的替换。
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么。抽象工厂模式最大的特点就是在类的内部对产品族进行约束,而缺点是对这些产品族的扩展是很费力的事情,倘若需要新增产品,所有的工厂类几乎都要修改,所以对产品结构的划分要慎之又慎。
到这里,几个工厂模式都介绍完了,他们的目的都是为了解耦,在使用时,无需区分是什么特定的模式,只要我们降低耦合度的目的达到即可。