【深入设计模式】工厂模式—简单工厂和工厂方法

1,547 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

设计模式(Design pattern)是软件工程的基础,最近在对公司项目代码进行迭代升级之后,决定把设计模式相关的知识点拉出来再重新整理一遍进行分享。学习设计模式,不仅是为了面试,更是为了在工作中能够写出易于维护、扩展性好的代码,否则不考虑设计的功能代码也是在给后人挖坑(已经深受其害)。 首先分享的是工厂模式,将这部分分为两部分,第一部分是是简单工厂模式和工厂方法模式的介绍及演进,主要介绍这两种模式结构,以及如何从简单工厂衍生出工厂方法模式。第二部分是抽象工厂模式和工厂模式在源码应用(下一篇博客进行分享),介绍抽象工厂模式结构,并且从框架源码中解析工厂模式,加深对这一模式的理解。

1. 简单工厂模式

1.1 简介

在生活中,我们从商场或线上购买到的产品,都是由工厂生产出来的,而在代码中,我们创建一个对象时,大部分时间都是通过手动 new 构造出来。当项目逐渐扩大,需要的对象越来越多时,我们就需要不停地 new 对象,并且某些大对象需要在构造的同时初始化变量等。渐渐的就会在项目中出现大量重复的构建某一对象代码,使得代码变得臃肿,并且如果修改类中参数,则可能需要对每一处调用同时进行修改,耦合度太高。

其实在对于调用者来说,我仅仅只是需要传一些参数从而得到一个对象,对于其中初始化过程是完全不关心的,只需要返回一个正确的对象就行了。因此,我们便可以使用工厂模式,将初始化对象的过程交给工厂类,调用方只需要接收生成的对象(产品)即可。

简单工厂模式便是工厂模式的一种,在简单工厂模式中,生成的对象叫做产品,生成这些产品的类叫做工厂类,该模式中可以用一个工厂类来生成多种产品,生产过程交给工厂类实现,调用方仅仅从工厂类获取对象便可以了。

1.2 简单工厂模式结构

从上面简介中我们可以知道,简单工厂模式有简单工厂、抽象产品和具体产品三种角色:

  • 简单工厂:负责对具体产品进行生产
  • 抽象产品:具体产品的父类,利用多态在简单工厂类中进行生产
  • 具体产品:抽象产品接口的具体实现,最终得到的产品类

结构图如下:

image.png

/**
 * 简单工厂类
 */
public class SimpleFactory {
    public static Product create() {
        
    }
}
/**
 * 抽象产品接口
 */
public interface Product {
    void operation();
}
/**
 * 具体产品 1
 */![在这里插入图片描述](https://img-blog.csdnimg.cn/78002b518fed4b9db37cd8acddc171a3.jpeg#pic_center)

public class SpecificProduct1 implements Product {
    public void operation() {
        
    }
}
/**
 * 具体产品 2
 */
public class SpecificProduct2 implements Product {
    public void operation() {
        
    }
}

1.3 简单工厂模式示例

场景模拟:等下要去饭店吃午餐了,饭店里面有炒饭、面、水饺等等可以选择,我们去点菜时,只需要告诉老板我要吃啥,然后老板就会把做好的食物交给你,而实际上这些炒饭、面、水饺怎么做的,你是根本不关心的,只要能够拿到食物就行了。在这里简单工厂就是餐厅(Restaurant),抽象产品就是食物(Food),具体产品就是面(Noodles)、炒饭(FriedRice)、水饺(Dumplings)。

// 餐厅 (简单工厂)
public class Restaurant {
    private static final int NOODLES = 0;
    private static final int FRIED_RICE = 1;
    private static final int DUMPLINGS = 2;
	// 根据点餐的食物编号,创建对应的具体食物对象
    public static Food order(Integer foodNum) {
        Food food = null;
        switch (foodNum) {
            case NOODLES:
                food = new Noodles();
                break;
            case FRIED_RICE:
                food = new FriedRice();
                break;
            case DUMPLINGS:
                food = new Dumplings();
                break;
            default:
                System.out.println("sorry, this dish is not available in the restaurant.");
        }
        return food;
    }
}

// 食物(抽象产品)及对应的具体食物
public interface Food {
    void eat();
}
// 面
public class Noodles implements Food {
    public void eat() {
        System.out.println("eat noodles.");
    }
}
// 炒饭
public class FriedRice implements Food {
    public void eat() {
        System.out.println("eat fried rice.");
    }
}
// 水饺
public class Dumplings implements Food {
    public void eat() {
        System.out.println("eat dumplings.");
    }
}

// 客户调用部分(Client)
public class Client {
    private static final int NOODLES = 0;
    private static final int FRIED_RICE = 1;
    private static final int DUMPLINGS = 2;

    public static void main(String[] args) {
        Food order = Restaurant.order(NOODLES); // 点了面
        if (order != null) {
            order.eat(); // 吃面
        }
        Food order2 = Restaurant.order(FRIED_RICE); // 点了炒饭
        if (order2 != null) {
            order2.eat(); // 吃炒饭
        }
        Food order3 = Restaurant.order(DUMPLINGS); // 点了饺子
        if (order3 != null) {
            order3.eat(); // 吃饺子
        }
    }
}

控制台打印如下:

eat noodles.
eat fried rice.
eat dumplings.

从结果中可以看到,餐厅 Restaurant (简单工厂类)根据客户所点的食物编号做出了相应的食物(具体对象),最后客户拿到食物后开心的吃了起来(具体对象的方法实现)。可以看到整个点餐过程并不需要客户去参与到食物制作过程(具体对象构建过程),将来如果餐厅厨师对食物进行改进的时候仅仅只需要具体对象的生成过程就可以了,对客户点餐是完全没有影响的。比如面要升级,点的面全都赠送一个煎蛋,这个时候我们仅需要修改 Noodles 类,Client 类中的调用过程完全不改变客户依然点面就可以了。

public class Noodles implements Food {

    boolean egg = false;

    public Noodles() {
        egg = true;
    }

    public void eat() {
        System.out.println("eat noodles. " + (egg ? "with egg!" : "no egg")); // 控制台输出 eat noodles. with egg!
    }
}

1.4 简单工厂模式优缺点

优点:

  1. 通过一个简单工厂类将客户端调用和具体对象生成两个过程进行解耦
  2. 客户端只需要决定参数,简单工厂便根据参数决定返回哪一个对象

缺点:

  1. 多引入一个工厂类,增加代码复杂度
  2. 违背开闭原则,当需要新增一个产品时,不经要新增一个产品具体实现,并新增对应的工厂及实现

2. 工厂方法模式

2.1 简介

工厂方法模式其实就是简单工厂模式的一种改进,用于处理在引入新产品时,不需要对原有的工厂和产品类进行修改,由此满足开闭原则。相比于简单工厂模式,工厂方法模式引入了抽象工厂类(AbstractFactory),通过实现抽象工厂类得到对应的工厂,从而生产出对应的产品。通过引入抽象工厂类,将简单工厂类中的产品选择进行解耦,选择权交给调用者,通过拿到具体的工厂来生产具体的产品。

2.2 工厂方法模式结构

image.png

/**
 * 抽象工厂类
 */
public class AbstractFactory {
    abstract Product create();
}

/**
 * 具体工厂类 1
 */
public class SpecificFactory1 {
    public Product create() {
        return new SpecificProduct1();
    }
}
/**
 * 具体工厂类 2
 */
public class SpecificFactory2 {
    public Product create() {
        return new SpecificProduct2();
    }
}


/**
 * 抽象产品接口
 */
public interface Product {
    void operation();
}
/**
 * 具体产品 1
 */
public class SpecificProduct1 implements Product {
    public void operation() {
        
    }
}
/**
 * 具体产品 2
 */
public class SpecificProduct2 implements Product {
    public void operation() {
        
    }
}

2.3 工厂方法模式示例

场景模拟:又到了饭点了,之前去的那家餐厅临时放假,这荒郊野岭的只剩下一个超市和一个快餐店了,超市卖零食,快餐店卖垃圾食品,今天就只能在这两家二选一凑合一下了。超市和快餐店在两个地方,各自卖各自的产品,由于午休时间不够因此我们一次只能选择一家去解决温饱问题。

// 商铺(抽象工厂类)及对应具体工厂
public abstract class AbstractShop {
    abstract Food getFood();
}
// 快餐店(工厂具体实现)
public class FastFoodRestaurant extends AbstractShop {
    Food getFood() {
        return new JunkFood();
    }
}
// 超市(工厂具体实现)
public class Supermarket extends AbstractShop {
    Food getFood() {
        return new Snacks();
    }
}

// 食物(抽象产品)及对应的具体食物
public interface Food {
    void eat();
}
// 垃圾食品(具体产品)
public class JunkFood implements Food{
    public void eat() {
        System.out.println("eat junk food.");
    }
}
// 零食(具体产品)
public class Snacks implements Food {
    public void eat() {
        System.out.println("eat snacks.");
    }
}

// 客户调用部分(Client)
public class Client {
    public static void main(String[] args) {
        AbstractShop shop = new FastFoodRestaurant(); // 去快餐店
        Food food = shop.getFood();
        food.eat(); // 吃垃圾食品

        AbstractShop shop2 = new Supermarket(); // 去超市
        Food food2 = shop2.getFood();
        food2.eat(); // 吃零食
    }
}

控制台打印如下:

eat junk food.
eat snacks.

从上面的代码可以看出,产品选择的选择权从工厂类转移到了客户端调用中,客户端通过构建具体工厂来获取具体产品。快餐店和超市两个类(工厂具体实现类)中并不再进行判断需要返回哪个产品,而是统一调用抽象工厂的 getFood() 方法返回其生产的食物(具体产品)。当附近再开一家烧烤店时,不需要对原来的代码进行修改,直接新增烧烤店(具体工厂)和对应的食物(具体产品)即可,客户需要吃烧烤时直接去烧烤店就可以了(调用具体工厂)。

public class BarbecueShop  extends  AbstractShop{
    Food getFood() {
        return new Barbecue();
    }
}

public class Barbecue implements Food {
    public void eat() {
        System.out.println("eat barbecue.");
    }
}


public static void main(String[] args) {
    AbstractShop shop = new BarbecueShop(); // 去烧烤店
    Food food = shop.getFood();
    food.eat(); // 吃烧烤
}

2.4 工厂方法模式优缺点

优点:

  1. 解决了简单工厂违背开闭原则的问题
  2. 需要新增产品时只需新增产品及对应工厂即可,不在对已有代码进行修改
  3. 将工厂与产品选择进行解耦,选择权交予客户端,工厂只专注于生产对应产品

缺点:

  1. 引入抽象工厂和具体工厂,类增多代码复杂度增加
  2. 一个工厂只能生产一个产品,往往会造成工厂类过多