版权声明:本文由 Hov 所有,发布于 https://chenhy.com ,转载请注明出处。
笔者注:设计模式理解起来可能比较晦涩,建议结合实例理解。在学习设计模式的过程中,可先简单了解概念,然后仔细研究实例的思路,最后回归概念。
1 工厂模式
1.1 简介
工厂模式属于创建型模式,它提供了一种 创建对象 的解决方案。在工厂模式中,创建对象时不会暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象,实现程序与创建对象操作的解耦。
工厂模式主要有三种实现方式:简单工厂、工厂方法模式、抽象工厂模式,它们的目的都是松耦合。
1.2 思路
工厂模式将创建对象的操作封装起来,实现创建对象代码与具体类的解耦。工厂模式有利于我们针对抽象编程,而不是针对具体类编程。
1.3 使用场景
当我们需要在不同条件下创建不同实例时,都可以使用工厂模式。比如,当我们需要不同汽车,只需从不同工厂获取,由工厂向我们提供不同型号的汽车,而不用管这些汽车是怎么制造出来的(对象是怎么创建的)。
创建对象也是如此,程序需要不同的对象,只需告诉工厂需要什么对象。由工厂负责创建相应的对象,并返回给程序。程序不需要考虑对象是怎么创建的,从而实现了程序与创建对象行为的解耦。
1.4 优缺点
优点:
- 扩展性高:若要增加一个产品(对象),只需扩展一个工厂(类)即可;
- 屏蔽对象的具体创建过程,调用者只负责获取对象;
缺点:
- 每增加一个产品(对象),可能就需要增加一个对象工厂,某种程度上增加了系统的复杂度。
2 实例
在讲解工厂模式前,我们先引入一个披萨店的实例。
- 披萨店中售卖不同类型的披萨,有 PepperPizza 、GreekPizza 、CheesePizza;
- 每种披萨都需要 原料准备、烘培、切割、包装 四个步骤;
- 不同的披萨 原料准备不同,但 烘培、切割、包装统一。
2.1 常规解决方案
下面我们通过程序模拟披萨店的运作。
(1)抽象类 Pizza
抽象类 Pizza 提供了 烘培、切割、包装 三个步骤的默认实现,而 原料准备 作为抽象方法,由不同的披萨子类去实现。
/* Pizza.java */
package pizza;
public abstract class Pizza { //抽象类:Pizza
protected String name; //披萨名称
public abstract void prepare(); //抽象方法:原料准备
public void bake(){ //默认实现:烘培
System.out.println(name + " baking...");
}
public void cut(){ //默认实现:切割
System.out.println(name + " cutting...");
}
public void box(){ //默认实现:包装
System.out.println(name + "boxing...");
}
public void setName(String name){
this.name = name;
}
}
(2)不同的披萨子类继承父类 Pizza
不同的披萨子类继承父类 Pizza,并 重写 prepare 方法,以此实现不同披萨的原料准备不同。同时,这些子类都继承了父类 Pizza 的 bake、cut、box 方法,因为这三个步骤是统一的,所以采用默认实现即可,无需重写。
子类 PepperPizza 继承父类 Pizza:
/* PepperPizza.java */
package pizza;
public class PepperPizza extends Pizza { //子类:PepperPizza
@Override
public void prepare() { // PepperPizza 原料准备
super.setName("PepperPizza");
System.out.println(name + " preparing...");
}
}
子类 GreekPizza 继承父类 Pizza:
/* GreekPizza.java */
package pizza;
public class GreekPizza extends Pizza { //子类:GreekPizza
@Override
public void prepare() {
super.setName("GreekPizza"); // GreekPizza 原料准备
System.out.println(name + " preparing...");
}
}
子类 CheesePizza 继承父类 Pizza:
/* CheesePizza.java */
package pizza;
public class CheesePizza extends Pizza { //子类:CheesePizza
@Override
public void prepare() { // CheesePizza 原料准备
super.setName("CheesePizza");
System.out.println(name + " preparing...");
}
}
(3)订购披萨
前面我们已经封装了三个子类 PepperPizza、GreekPizza、CheesePizza(分别代表不同的披萨),并都继承了父类 Pizza。因此,实例化这些不同的披萨子类就是生成不同的披萨。
订购披萨类 OrderPizza 的作用是 根据用户的输入来实例化不同的披萨子类。因此,用户只需告诉 OrderPizza 需要什么披萨,OrderPizza 就负责创建相应的披萨。
/* OrderPizza.java */
package pizzastore;
import pizza.*;
import java.util.Scanner;
public class OrderPizza { //订购披萨
public OrderPizza(){
Pizza pizza = null;
String orderType;
do{
orderType = getType(); //输入披萨名称
if(orderType.equals("cheese")){ //创建CheesePizza
pizza = new CheesePizza();
}
else if(orderType.equals("greek")){ //创建GreekPizza
pizza = new GreekPizza();
}
else if(orderType.equals("pepper")){ //创建PepperPizza
pizza = new PepperPizza();
}
else{
break;
}
//根据前面的选择,pizza 已经被实例化为相应的披萨对象
pizza.prepare(); //原料准备
pizza.bake(); //烘培
pizza.cut(); //切割
pizza.box(); //包装
}while(true);
}
private String getType(){ //输入披萨名称
Scanner sc = new Scanner(System.in);
String orderType = sc.next();
return orderType;
}
}
(4)模拟程序
通过 OrderPizza 接收用户输入,根据用户输入的披萨名称,创建不同的披萨,并进行原料准备、烘培、切割、包装四个步骤。
/* PizzaStore.java */
package pizzastore;
public class PizzaStore { //披萨店
public static void main(String[] args) {
OrderPizza mOrderPizza = new OrderPizza(); //订购披萨
}
}
程序运行结果:
输入 cheese,披萨店制作了 CheesePizza:
Please input the pizza you want:
cheese
CheesePizza preparing...
CheesePizza baking...
CheesePizza cutting...
CheesePizzaboxing...
输入 pepper,披萨店制作了 PepperPizza:
Please input the pizza you want:
pepper
PepperPizza preparing...
PepperPizza baking...
PepperPizza cutting...
PepperPizzaboxing...
2.2 存在的问题
上述程序虽然实现了基本的功能,但存在一些问题。例如,违反开闭原则。
当我们想要新增一种披萨,如 ChicagoPizza 。
我们需要 修改 OrderPizza.java 的代码,并增加一种披萨类型 ChicagoPizza 。
也就是说,每新增一种披萨,我们都要修改 OrderPizza.java 的代码,因此需要暂停程序。
(1)修改 OrderPizza.java
将 OrderPizza.java 中
if(orderType.equals("cheese")){
pizza = new CheesePizza();
}
else if(orderType.equals("greek")){
pizza = new GreekPizza();
}
else if(orderType.equals("pepper")){
pizza = new PepperPizza();
}
else{
break;
}
改成:
if(orderType.equals("cheese")){
pizza = new CheesePizza();
}
else if(orderType.equals("greek")){
pizza = new GreekPizza();
}
else if(orderType.equals("pepper")){
pizza = new PepperPizza();
}
else if(orderType.equals("chicago")){ //增加 ChicagoPizza
pizza = new ChicagoPizza();
}
else{
break;
}
(2)增加子类 ChicagoPizza 继承父类 Pizza
/* ChicagoPizza.java */
package pizza;
public class ChicagoPizza extends Pizza { //子类:ChicagoPizza
@Override
public void prepare() { // ChicagoPizza 原料准备
super.setName("ChicagoPizza");
System.out.println(name + " preparing...");
}
}
3 简单工厂
回顾前面的例子,我们实现了基本的功能,但存在一些问题。每新增一种披萨都需要修改代码,导致整个程序需要重新编译。通过分析变化部分和不变化部分,我们可以 将变化部分提取出来,放在所谓的简单工厂。具体到例子,我们可以定义一个实例化披萨对象的类,封装创建对象的代码,这个类就是所谓的 简单工厂。
简单工厂:定义一个创建对象的类,由这个类封装实例化对象的行为。
注:很多人将简单工厂说成简单工厂模式,事实上,简单工厂并非一种设计模式,反而更像是一种编程习惯。
下面我们通过简单工厂来进一步改进程序。
(1)简单工厂:封装创建对象的代码
还记得前面的订购披萨类 OrderPizza 吗?我们在这个类中定义了创建不同披萨对象的代码。正因如此,我们每新增一种披萨都需要修改这部分代码。
简单工厂 SimplePizzaFactory 的工作是 将订购披萨类 OrderPizza 中实例化不同对象的代码(变化部分)提取出来,并封装成一个类(简单工厂)。
/* SimplePizzaFactory.java */
package pizzastore;
import pizza.*;
public class SimplePizzaFactory { //简单工厂:负责创建各种类型的披萨对象
public Pizza createPizza(String pizzaType){ //创建披萨对象
Pizza pizza = null;
if(pizzaType.equals("cheese")){ //创建 CheesePizza
pizza = new CheesePizza();
}
else if(pizzaType.equals("greek")){ //创建 GreekPizza
pizza = new GreekPizza();
}
else if(pizzaType.equals("pepper")){ //创建 PepperPizza
pizza = new PepperPizza();
}
return pizza;
}
}
(2)在 OrderPizza.java 中使用简单工厂
有了简单工厂,订购披萨类 OrderPizza 中创建不同披萨对象的工作就可以交给简单工厂去实现。OrderPizza 只需告诉简单工厂需要创建的披萨名称,就可以获取到相应的披萨对象。
/* OrderPizza.java */
package pizzastore;
import pizza.*;
import java.util.Scanner;
public class OrderPizza { //订购披萨
SimplePizzaFactory mSimplePizzaFactory = new SimplePizzaFactory(); //简单工厂
public OrderPizza(){
setFactory(mSimplePizzaFactory);
}
public void setFactory(SimplePizzaFactory mSimplePizzaFactory){
Pizza pizza = null;
String pizzaType;
this.mSimplePizzaFactory = mSimplePizzaFactory;
do{
pizzaType = getType(); //输入披萨名称
pizza = mSimplePizzaFactory.createPizza(pizzaType); //通过简单工厂创建披萨对象
if(pizza!=null){ //创建成功
pizza.prepare(); //原料准备
pizza.bake(); //烘培
pizza.cut(); //切割
pizza.box(); //包装
}
}while(true);
}
private String getType(){ //输入披萨名称
System.out.println("Please input the pizza you want:");
Scanner sc = new Scanner(System.in);
String pizzaType = sc.next();
return pizzaType;
}
}
以上,我们已经通过简单工厂对程序进一步改进,实现创建对象行为与程序的解耦。
简单工厂其实就是将创建对象的代码封装成一个简单工厂类 SimplePizzaFactory,订购披萨类 OrderPizza 通过使用这个简单工厂来创建不同的披萨对象。
4 工厂方法模式
回顾前面的例子,披萨店可以制作不同类型的披萨。
(普通方案)一开始,我们直接使用订购披萨类 OrderPizza 创建不同的披萨对象。
(简单工厂)后面我们觉得这样每次都要修改订购披萨类 OrderPizza ,扩展性不强,而且违反开闭原则。所以,我们封装了一个简单工厂来创建不同的披萨对象,也就是将创建披萨对象的实现代码移出订购披萨类 OrderPizza。
现在,由于披萨店发展迅猛,程序有了新的需求。
- 有两家披萨店:广州分店、深圳分店;
- 不同分店可以制作不同口味的披萨;比如 CheesePizza ,广州分店制作的是广州风味的 GZCheesePizza,而深圳分店制作的是深圳风味的 SZCheesePizza。
为了实现不同分店可以制作不同风味的披萨,我们可采用工厂方法模式,将创建披萨对象的行为抽象为抽象方法,在不同分店中实例化不同的对象。
工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
下面我们通过工厂方法模式来进一步改进程序。
(1)抽象类:Pizza
抽象类 Pizza 无需改动,依然定义了原料准备的抽象方法 prepare,以及提供了烘培、切割、包装的默认实现 bake、cut、box。
/* Pizza.java */
package pizza;
public abstract class Pizza { //抽象类:Pizza
protected String name; //披萨名称
public abstract void prepare(); //原料准备
public void bake(){ //烘培
System.out.println(name + " baking...");
}
public void cut(){ //切割
System.out.println(name + " cutting...");
}
public void box(){ //包装
System.out.println(name + "boxing...");
}
public void setName(String name){
this.name = name;
}
}
(2)广州分店:两种广州风味的披萨
广州分店可制作具有广州风味的披萨: GZCheesePizza 和 GZPepperPizza。
披萨 GZCheesePizza 继承父类 Pizza:
/* GZCheesePizza.java */
package pizza;
public class GZCheesePizza extends Pizza{ //广州风味的CheesePizza
@Override
public void prepare() {
super.setName("GZCheesePizza");
System.out.println(name + " preparing...");
}
}
披萨 GZPepperPizza 继承父类 Pizza:
/* GZPepperPizza.java */
package pizza;
public class GZPepperPizza extends Pizza { //广州风味的PepperPizza
@Override
public void prepare() {
super.setName("GZPepperPizza");
System.out.println(name + " preparing...");
}
}
(3)深圳分店:两种深圳风味的披萨
深圳分店可制作具有深圳风味的披萨 SZCheesePizza 和 SZPepperPizza。
披萨 SZCheesePizza 继承父类 Pizza:
/* SZCheesePizza.java */
package pizza;
public class SZCheesePizza extends Pizza{ //深圳风味的CheesePizza
@Override
public void prepare() {
super.setName("SZCheesePizza");
System.out.println(name + " preparing...");
}
}
披萨 SZPepperPizza 继承父类 Pizza:
/* SZPepperPizza.java */
package pizza;
public class SZPepperPizza extends Pizza { //深圳风味的PepperPizza
@Override
public void prepare() {
super.setName("SZPepperPizza");
System.out.println(name + " preparing...");
}
}
(4)订购披萨抽象类: OrderPizza
回顾前面的程序,订购披萨类 OrderPizza 实现了以下功能。
- 普通方案:直接实例化披萨对象;
- 简单工厂:通过调用简单工厂类实例化对象。
在工厂方法模式下,我们可以 将 OrderPizza 定义为抽象类,将实例化操作延迟到子类。
在订购披萨抽象类 OrderPizza 中,方法 createPizza 负责创建披萨对象,同时被声明为抽象方法。这意味着创建对象的行为需要由 OrderPizza 的子类去实现。
/* OrderPizza.java */
package pizzastore;
import pizza.*;
import java.util.Scanner;
public abstract class OrderPizza { //抽象类:订购披萨
public OrderPizza(){
Pizza pizza = null;
String pizzaType;
do{
pizzaType = getType(); //输入披萨名称
pizza = createPizza(pizzaType); //创建披萨对象
pizza.prepare(); //原料准备
pizza.bake(); //烘培
pizza.cut(); //切割
pizza.box(); //包装
}while(true);
}
public abstract Pizza createPizza(String pizzaType); //抽象方法:创建披萨对象
private String getType(){ //输入披萨名称
System.out.println("Please input the pizza you want:");
Scanner sc = new Scanner(System.in);
String pizzaType = sc.next();
return pizzaType;
}
}
(5)订购披萨具体实现类:GuangzhouOrderPizza 和 ShenzhenOrderPizza
订购披萨具体实现类中需要重写抽象类 OrderPizza 的抽象方法 createPizza,createPizza 负责创建不同的披萨对象。
广州分店订购披萨 GuangzhouOrderPizza 继承父类 OrderPizza:
/* GuangzhouOrderPizza.java */
package pizzastore;
import pizza.*;
public class GuangzhouOrderPizza extends OrderPizza { //广州分店订购披萨
@Override
public Pizza createPizza(String pizzaType) {
Pizza pizza = null;
if(pizzaType.equals("cheese")){ //创建广州风味的CheesePizza
pizza = new GZCheesePizza();
}
else if(pizzaType.equals("pepper")){ //创建广州风味的PepperPizza
pizza = new GZPepperPizza();
}
return pizza;
}
}
深圳分店订购披萨 ShenzhenOrderPizza 继承父类 OrderPizza:
/* ShenzhenOrderPizza.java */
package pizzastore;
import pizza.*;
public class ShenzhenOrderPizza extends OrderPizza{ //深圳分店订购披萨
@Override
public Pizza createPizza(String pizzaType) {
Pizza pizza = null;
if(pizzaType.equals("cheese")){ //创建深圳风味的CheesePizza
pizza = new SZCheesePizza();
}
else if(pizzaType.equals("pepper")){ //创建深圳风味的PepperPizza
pizza = new SZPepperPizza();
}
return pizza;
}
}
(6)模拟程序
通过实例化 GuangzhouOrderPizza ,模拟广州分店。同理,当我们实例化 ShenzhenOrderPizza ,即可模拟深圳分店。
/* PizzaStore.java */
package pizzastore;
public class PizzaStore {
public static void main(String[] args) {
OrderPizza mOrderPizza = new GuangzhouOrderPizza(); //广州分店订购披萨
}
}
程序运行结果:
输入 cheese,广州分店制作了 GZCheesePizza:
Please input the pizza you want:
cheese
GZCheesePizza preparing...
GZCheesePizza baking...
GZCheesePizza cutting...
GZCheesePizzaboxing...
输入 pepper,广州分店制作了 GZPepperPizza:
Please input the pizza you want:
pepper
GZPepperPizza preparing...
GZPepperPizza baking...
GZPepperPizza cutting...
GZPepperPizzaboxing...
5 抽象工厂模式
回顾前面的例子,我们通过工厂方法模式对程序进一步改进。通过将订购披萨类声明为抽象类,并通过不同的子类重写创建对象的方法 createPizza ,将创建对象的操作延迟到子类。
抽象工厂模式与工厂方法模式类似,都实现了对创建对象操作的解耦。不同的是,工厂方法模式将创建对象的操作延迟到子类,而抽象工厂模式直接封装一个抽象工厂,再由具体的工厂实现类来定义不同的创建对象行为。可以这样理解,抽象工厂模式就是在简单工厂的基础上进一步抽象,将创建对象的任务交由抽象工厂下面的子工厂实现。
抽象工厂模式:定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。
下面我们通过抽象工厂模式继续改进程序。
(1)抽象工厂:PizzaFactory
抽象工厂 PizzaFactory 作为所有工厂的接口,声明了 createPizza 方法。这意味着,所有实现抽象工厂 PizzaFactory 的子工厂(类)都需要重写 createPizza 方法。在 createPizza 方法中,不同的子工厂类可以实现不同的创建对象行为。
/* PizzaFactory.java */
package factory;
import Pizza;
public interface PizzaFactory { //接口:抽象工厂
public Pizza createPizza(String pizzaType);
}
(2)不同的子工厂实现抽象工厂 PizzaFactory
不同的子工厂(类)实现抽象工厂 PizzaFactory,并重写 createPizza 方法,实现不同的创建对象行为。比如广州工厂接收到 cheese ,创建的是 GZCheesePizza ,而不是 SZCheesePizza。
广州工厂 GZFactory:
/* GZFactory.java */
package factory;
import pizza.*;
public class GZFactory implements PizzaFactory { //广州工厂
@Override
public Pizza createPizza(String pizzaType) {
Pizza pizza = null;
if(pizzaType.equals("cheese")){ //创建广州风味的CheesePizza
pizza = new GZCheesePizza();
}
else if(pizzaType.equals("pepper")){ //创建广州风味的PepperPizza
pizza = new GZPepperPizza();
}
return pizza;
}
}
深圳工厂 SZFactory:
/* SZFactory.java */
package factory;
import pizza.*;
public class SZFactory implements PizzaFactory { //深圳工厂
@Override
public Pizza createPizza(String pizzaType) {
Pizza pizza = null;
if(pizzaType.equals("cheese")){ //创建深圳风味的CheesePizza
pizza = new SZCheesePizza();
}
else if(pizzaType.equals("pepper")){ //创建深圳风味的PepperPizza
pizza = new SZPepperPizza();
}
return pizza;
}
}
(3)订购披萨
订购披萨类 OrderPizza 和简单工厂的实现类似,通过工厂来创建对象。不同的是简单工厂提供了创建对象的具体实现,而抽象工厂并没有提供创建对象的具体实现,可以设置不同的工厂,由不同的工厂创建不同的对象。
/* OrderPizza.java */
package factory;
import pizza.*;
import java.util.Scanner;
public class OrderPizza {
PizzaFactory mFactory;
public OrderPizza(PizzaFactory mFactory){
setFactory(mFactory);
}
public void setFactory(PizzaFactory mFactory){ //设定工厂名称
Pizza pizza = null;
String pizzaType;
this.mFactory = mFactory;
do{
pizzaType = getType(); //输入披萨名称
pizza = mFactory.createPizza(pizzaType); //由具体工厂创建披萨对象
if(pizza!=null){
pizza.prepare(); //原料准备
pizza.bake(); //烘培
pizza.cut(); //切割
pizza.box(); //包装
}
}while(true);
}
private String getType(){ //输入披萨名称
System.out.println("Please input the pizza you want:");
Scanner sc = new Scanner(System.in);
String pizzaType = sc.next();
return pizzaType;
}
}
(4)模拟程序
通过向 OrderPizza 传入不同的工厂实例,实现不同的创建对象行为。下面的程序将广州工厂 GZFactory 作为订购披萨类的构造参数,实现通过广州工厂创建披萨对象的效果。
/* PizzaStore.java */
package factory;
public class PizzaStore {
public static void main(String[] args) {
OrderPizza mOrderPizza = new OrderPizza(new GZFactory()); //广州工厂订购披萨
}
}
程序运行结果:
输入 cheese ,创建广州风味的 GZCheesePizza:
Please input the pizza you want:
cheese
GZCheesePizza preparing...
GZCheesePizza baking...
GZCheesePizza cutting...
GZCheesePizzaboxing...
6 总结
工厂模式通过封装对象的创建行为,减少了程序和具体类之间的依赖,促进了松耦合。通过分析程序中的变化部分和不变化部分,将变化部分(对象实例化操作)提取出来封装成简单工厂或工厂方法或抽象工厂。
- 简单工厂:通过 将对象实例化代码封装成一个类(简单工厂),实现程序和具体类(创建对象)解耦。简单工厂不是真正的设计模式,但却简单有效。
- 工厂方法模式:通过继承实现,将对象实例化代码抽象(抽象父类),通过继承将对象的创建委托给子类,子类实现工厂方法来创建对象。工厂方法模式将创建对象的操作延迟到子类。
- 抽象工厂模式:通过对象组合实现,将工厂进一步抽象(工厂接口),对象的创建被实现在工厂接口所暴露出来的方法(子工厂的方法)中,抽象工厂模式是简单工厂的进一步抽象。
依赖抽象原则:避免依赖具体类型,应尽量依赖抽象。
- 变量不要持有具体类的引用(在实例化时避免使用 new ,可通过 “ Pizza pizza = null; pizza = createPizza(); ” 代替 “ Pizza pizza = new Pizza();“;
- 不要让类继承具体类,而应该继承抽象类或接口(继承具体类时依赖性很强);
- 不要覆盖父类中已实现的方法(已实现的方法应该是对于所有子类的通用方法)。