java设计模式--------工厂模式
-
分类和定义
1)简单工厂模式(Simple Factory):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据自变量的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
2)工厂方法模式(Factory Method):又称为多形性工厂;工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method是一个类的实例化延迟到其子类。 在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。这个核心类则摇身一变,成为了一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
3)抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品; 抽象工厂模式提供一个创建一系列或相互依赖的对象的接口,而无需指定它们具体的类。
-
实例
简单工厂模式
一个批萨店,出售不同的批萨,有水果批萨、奶酪批萨、蔬菜批萨等等 。每一个批萨的制作过程: 准备(prepare)、烘烤(bake)、切片(cut)、装箱(box)。批萨店需要为客户提供各种各样的批萨,当然,为客户提供批萨前得制作批萨
首先看批萨的种类 定义批萨父类
abstract public class Pizza {
String name;
String dough;
String sauce;
ArrayList<String> toppings = new ArrayList<String>();
public String getName() {
return name;
}
public void prepare() {
System.out.println("Preparing " + name);
}
public void bake() {
System.out.println("Baking " + name);
}
public void cut() {
System.out.println("Cutting " + name);
}
public void box() {
System.out.println("Boxing " + name);
}
public String toString() {
// code to display pizza name and ingredients
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append(toppings.get(i) + "\n");
}
return display.toString();
}
}
批萨的种类有一些,依次继承父类实现批萨,这里用一个例子代表
public class CheesePizza extends Pizza {
public CheesePizza() {
name = "Cheese Pizza";
dough = "Regular Crust";
sauce = "Marinara Pizza Sauce";
toppings.add("Fresh Mozzarella");
toppings.add("Parmesan");
}
}
定义批萨店 为 (批萨店专职生产批萨,但是它自己不能主动提供批萨,需要操作员来调用)
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public 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 = 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;
}
}
如下类图为简单工厂模式
如果我们想吃其他地方的批萨怎么办呢,注意批萨店中的制作方法是写死的
工厂模式实例
接着上面的例子,上面的简单工厂操作中,我们仅仅实现了一种类型的批萨店PizzaStore,但是,现实中,可能每一个地方都有批萨店,如北京。上海各有各的批萨店,那么此时就需要创建更多的批萨店了
在上面的简单工厂模式中,批萨店的批萨制作模式是固定的,如准备(prepare)、烘烤(bake)、切片(cut)、装箱(box),所以,制作批萨的代码相当于绑定在批萨店中了,而现实中不同地方的批萨店的做法是不相同的
这时候就需要重新设计批萨店的,将批萨店变成抽象的超类,用其他子类继承
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;
}
}
其他地方的批萨店继承超类:每一个批萨店都有自己的制作方法
public class NYPizzaStore extends PizzaStore {
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;
}
}
public class ChicagoPizzaStore extends PizzaStore {
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;
}
}
批萨店的关系图如下:
再看此时的批萨,因为已经提供了比较多种类的批萨,不同地方的批萨店的同一款批萨做法不一样
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("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;
}
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append((String) toppings.get(i) + "\n");
}
return display.toString();
}
}
看同一款奶酪批萨的不同做法
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");
}
void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
工厂模式如何实现调用不同批萨过程:
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore(); //首先,定义一个批萨店
Pizza pizza = nyStore.orderPizza("cheese");//然后,在调用店中生产的批萨,得到一个奶酪批萨
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
PizzaStore chicagoStore = new ChicagoPizzaStore(); //再接着定义一个其他地方的批萨店
pizza = chicagoStore.orderPizza("cheese"); //得到该店里的奶酪批萨
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}
工厂模式中很明显的将工厂(不同的批萨店)和产品(不同的批萨)分开了
如下图:
工厂类
产品类
工厂方法模式定义了一个创建对象的接口,但有子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
- 简单工厂模式和工厂模式区别
简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现,比方说,在工厂方法中,oreserPizza()方法提供了一般的框架,以便创建批萨,orderPizza()方法依赖于工厂方法创建具体类,并制造出实际的批萨。可通过继承PizzaStore类,决定实际制造出的批萨是什么。简单工厂的做法,可以将对象的创建封装起来。但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。
这正体现了 工厂模式的精髓:要依赖抽象,而不是具体类(依赖倒置原则)
几个指导方针,防止在OO设计时违反依赖倒置原则
- 1 -变量不可以持有具体类的引用
- 2-不要让类派生自具体类
- 3-不要覆盖基类中以实现的方法
但是规则是死的,并不是要一定完全遵循,可以尽量
抽象工厂模式
接着上面的例子,我们已经实现了能够制作出不同地方的批萨。虽然各地批萨是采用当地的制作方法,但是原材料的采用没有一定得标准,就是说,这样可能会导致不同的加盟店加入了劣质的原材料,从而影响口碑。
现在就是要采用统一的原材料供应,实现统一的原材料供应系统。
注意,北京的批萨店采用的蘑菇跟上海店采用的蘑菇可能不同。这样每一个地方的原材料品种都有可能不同。
北京 : 小蘑菇,黄奶酪,小洋葱
上海 : 大蘑菇,白奶酪,大洋葱
深圳: 金针菇, 甜奶酪,短洋葱
首先,我们定义一个批萨原材料接口
public interface PizzaIngredientFactory {
public Dough createDough(); //蘑菇
public Sauce createSauce(); //洋葱
public Cheese createCheese(); //奶酪
}
然后定义北京的原材料厂,实现接口:
public class BJPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough(); //小蘑菇
}
public Sauce createSauce() {
return new MarinaraSauce(); //小洋葱
}
public Cheese createCheese() {
return new ReggianoCheese(); // 黄奶酪
}
}
原材料一个例子
public interface Dough {
public String toString();
}
public class ThinCrustDough implements Dough {
public String toString() {
return "Thin Crust Dough";
}
}
同理可以实现其他地方的原材料厂,这里代码就不一一例举了
有了新的原材料就可以重新做正宗原材料的批萨了
public abstract class Pizza {
String name;
Dough dough; //三种原材料
Sauce sauce;
Cheese cheese;
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;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("---- " + name + " ----\n");
if (dough != null) {
result.append(dough);
result.append("\n");
}
if (sauce != null) {
result.append(sauce);
result.append("\n");
}
if (cheese != null) {
result.append(cheese);
result.append("\n");
}
return result.toString();
}
}
批萨超类已经做好了,那么就可以实现奶酪批萨了
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
奶酪批萨做好之后,就可以制作具有北京黄奶酪原材料的北京奶酪批萨了,但是在这之前,我们还需要一个北京批萨店,由于这里采用了全新的北京原材料(所以和上面已经不同了),北京批萨店如下:
public class BJPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory =
new BJPizzaIngredientFactory();
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("BeiJIng Style Cheese Pizza");
}
return pizza;
}
}
有了这些之后就可以下单叫北京奶酪批萨了
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new BJPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese"); //先调用父类 pizzastroe order方法
System.out.println("Ethan ordered a " + pizza + "\n");
}
}
类图如下所示:
可能很多人觉得工厂模式和抽象工厂模式很相似,这其实是有依据的。如上面,抽象工厂模式中有很多工厂模式的方法,如createDough()、createSauce()等等都是声明为抽象,然后用子类来实现。这个地方我们定义了一组对象产品,如 蘑菇、奶酪、洋葱等等都是一个个抽象的对象,都放在一起封装成一个 原材料 工厂 。
洋葱和奶酪应该是两个不相关的东西,但是通过抽象工厂模式将其组合到一起。
工厂模式中的pizzastroe提供一个接口,创建一个产品
抽象工厂模式中的 原材料 工厂 提供一个接口,创建 一个产品家族
三种模式的优缺点介绍
-简单工厂模式
- 简单工厂模式的优点如下:
(1)工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
(2)客户端无需知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
(3)通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
-
简单工厂模式的缺点如下:
(1)由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
(2)使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
(3)系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
(3)简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
- 工厂模式
- 工厂方法模式的优点如下:
(1)在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类的类名。
(2)基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,正是因为所有的具体工厂类都具有同一抽象父类。
(3)使用工厂方法模式的另一个优点是在系统中加入新产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,也无需修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
-
工厂方法模式的缺点如下:
(1)在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
(2)由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
- 抽象工厂模式
- 优点:
(1) 隔离了具体类的生成,使得用户不需要知道什么被创建了。
(2) 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
- 缺点:
(1)添加新的产品对像时,难以扩展抽象工厂以便生产新种类的产品。