设计模式(一)工厂模式

112 阅读7分钟

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

你能get到的知识点

  1. 工厂模式的介绍
  2. 工厂模式通过代码的实现

作者:lomtom

个人网站:lomtom.cn

个人公众号:博思奥园

你的支持就是我最大的动力。

1 简介

创建者模式模式提供创建对象的机制, 能够提升已有代码的灵活性和可复⽤性。

创建者模式包括:工厂方法、抽象工厂、生成器、原型、单例,这5类。

这一节主要讲述工厂模式:

  1. 简单工厂模式

    • 定义:定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
    • 核心就是用一个类来负责控制创建实例的过程
    • 适用场景:
      1. 工厂类负责创建对的对象比较少,因为不会造成工厂方法中的业务逻辑过于复杂
      2. 客户端只知道传入工厂类的参数,对如何创建对象不关心
  2. 工厂模式

    • 定义:在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。
    • 适用场景:
      1. 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
      2. 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
      3. 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。

图片来自https://refactoringguru.cn

2 场景引入

2.1 初始场景

场景实例: 小葛参加Jd平台的抽奖活动,而此时只有一种奖品奖品是1000元购物卡一张。 为了更好的模拟,假设没有不抽中的情况

那么我们来模拟小葛参加抽奖的这个过程,代码如下

目录结构:

└─com
    └─lomtom
        └─demo_0_0
            │  Test.java
            │
            └─service
                    AwardService.java
功能
AwardService模拟发放1000元购物卡奖品
Test测试

实现发放奖品类

public class AwardService {
    private String award = "1000元 购物卡";
    public void getAward(String username){
        System.out.println(username + "获得了" + award);
    }
}

测试验证

public class Test {

    public static void main(String[] args) {
        int index = 0;
        while(index++ < 10) {
            String employee = "小葛";
            System.out.print(employee + "抽奖兑换------    ");
            AwardService service = new AwardService();
            service.getAward(employee);
        }
    }
}

模拟十次抽奖,结果:

小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡

2.2 增加需求

场景实例: 小葛参加Jd平台的抽奖活动,平台为了增加奖品的多样性,在原来只有一种奖品的情况下新增两种奖品,分别为Iphone12 和3000元现金

如果代码其余部分与现有类已经存在耦合关系, 那么向程序中添加新类其实并没有那么容易。

下面我们不使用设计模式的情况下,我们一般还用if...else...来实现需求,通过一个标志awardNumber来表示小葛获得的奖品类型。

工程结构

└─com
    └─lomtom
        └─demo_0_1
            │  Test.java
            │
            └─service
                    AwardService.java
功能
AwardService模拟发放三种奖品
Test测试

实现发放奖品类 通过一个变量awardNumber来表明小葛抽中哪一个奖品,并且发放相应的奖品。

public class AwardService {

    private String cashAward = "3000元 现金";
    private String mallCardAward = "Iphone 12";
    private String iphoneAward = "1000元 购物卡";

    public void getAward(String username,Integer awardNumber){
        if (awardNumber == 1){
            System.out.println(username + "获得了" + mallCardAward);
        }else if (awardNumber == 2){
            System.out.println(username + "获得了" + iphoneAward);
        }else{
            System.out.println(username + "获得了" + cashAward);
        }
    }
}

测试验证

public class Test {

    public static void main(String[] args) {
        int index = 0;
        while(index++ < 10) {
            String employee = "小葛";
            //小葛随机获得奖品
            Random random = new Random();
            Integer awardNumber = random.nextInt(3);
            System.out.print(employee + "抽奖兑换------    ");
            AwardService service = new AwardService();
            service.getAward(employee,awardNumber);
        }
    }
}

这里使用0 - 2的随机数来代表小葛抽中了具体哪种商品,测试结果

小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了Iphone 12
小葛抽奖兑换------    小葛获得了Iphone 12
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了1000元 购物卡

小结: 使用if...else...非常直接的实现了业务需求,如果只从产品需求来说,使用if...else...确实满足了需求,并且也能缩短上线的时间,但是这样简单粗暴的方式,在一定程度上给我们带来了便捷,但是也种下了一定的隐患

  1. 当产品后续迭代时,需要引入更多的需求时,代码将变得臃肿;
  2. 并且也在一定程度上提高的重构代码的难度与成本;
  3. 一旦出现问题,测试所花的时间也会相应的增加。

那么,问题来了,我们怎么使用工厂模式来进行优化呢?

3 运用到简单工厂模式

使用简单工厂模式步骤:

  1. 创建抽象产品接口

  2. 创建具体产品类

  3. 创建工厂类

  4. 客户端调用工厂类

场景实例: 小葛参加Jd平台的抽奖活动,平台为了增加奖品的多样性,在原来只有一种奖品的情况下新增两种奖品,分别为Iphone12 和3000元现金

对上面使用if...else...的代码进行抽取,将其每个奖品发放的功能相应的抽取出来,然后使用一个工厂来决定发放哪一个奖品。

工程结构

└─com
    └─lomtom
        └─demo_0_2
            │  Test.java
            │
            ├─factory
            │      AwardFactory.java
            │
            └─service
                │  AwardService.java
                │
                └─impl
                        CashAwardService.java
                        IphoneAwardService.java
                        MallCardAwardService.java

我们创建一个发放奖品的接口(AwardService)和发放奖品接口的实现类,并且使用AwardFactory来获取AwardService对象,从而实现奖品的发放。

功能
AwardService奖品方法接口
CashAwardService模拟发放现金
IphoneAwardService模拟发放手机
MallCardAwardService模拟发放购物卡
AwardFactory发放奖品工厂
Test测试

实现发放奖品接口与其实现类 首先创建发放奖品的接口。 其次创建具体的发放奖品的实现类。

1. 发放奖品接口
public interface AwardService {
    void getAward(String username);
}

2. 发放手机实现类
public class IphoneAwardService implements AwardService {
    private String award = "Iphone 12";
    @Override
    public void getAward(String username){
        System.out.println(username + "获得了" + award);
    }
}

3. 发放现金实现类
public class CashAwardService implements AwardService {
    private String award = "3000元 现金";
    @Override
    public void getAward(String username){
        System.out.println(username + "获得了" + award);
    }
}


4. 发放购物卡实现类
public class MallCardAwardService implements AwardService {
    private String award = "1000元 购物卡";
    @Override
    public void getAwardService(String username){
        System.out.println(username + "获得了" + award);
    }
}

工厂类 使用一个变量awardNumber来代表小葛是抽中了哪一个奖品,然后来决定具体实现哪一个实现类。

public class AwardFactory {
    public AwardService getAward(Integer awardNumber) {
        if (awardNumber == 1){
           return new MallCardAwardService();
        }else if (awardNumber == 2){
            return new IphoneAwardService();
        }else{
            return new CashAwardService();
        }
    }
}

测试验证 用test来模拟客户端发起请求。

public class Test {

    public static void main(String[] args) {
        int index = 0;
        while(index++ < 10) {
            String employee = "小葛";
            //小葛随机获得奖品
            Random random = new Random();
            Integer awardNumber = random.nextInt(3);
            System.out.print(employee + "抽奖兑换------    ");
            AwardFactory factory = new AwardFactory();
            AwardService service = factory.getAwardService(awardNumber);
            service.getAward(employee);
        }
    }
}

结果:

小葛抽奖兑换------    小葛获得了Iphone 12
小葛抽奖兑换------    小葛获得了Iphone 12
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了Iphone 12

优点:简单工厂模式分离产品的创建者和消费者,有利于软件系统结构的优化,消费者的逻辑较为复杂时,这样的代码风格能够让我们对其功能一目了然; 缺点:但是由于一切逻辑都集中在一个工厂类中,导致了没有很高的内聚性,同时也违背了“开放封闭原则”。也就是说我需要增加一个其他的奖品,则需要修改工厂类,重新进行编译。

4 运用到工厂模式

使用简单工厂模式步骤:

  1. 创建抽象产品接口
  2. 创建具体产品类实现产品接口
  3. 创建工厂(具体创建者)接口
  4. 创建具体产品工厂类实现该工厂接口
  5. 创建工厂类(抽象创建者,可以说是一个调度中心)
  6. 客户端调用工厂类

小疑问?具体创建者与抽象创建者是什么东西? 在本实例中,因为这里有两种类型的工厂,AwardFactory 来实现具体需要创建的产品(来发放需要发放的产品),所以这个工厂叫做具体的创建者,而FactoryFactory并没有参与到产品的创建,而是将产品的创建延迟到AwardFactory来进行创建,所以FactoryFactory被称为抽象的创建者。

场景实例: 小葛参加Jd平台的抽奖活动,平台为了增加奖品的多样性,在原来只有一种奖品的情况下新增两种奖品,分别为Iphone12 和3000元现金

工程结构

└─com
    └─lomtom
        └─demo_0_3
            │  Test.java
            │
            ├─factory
            │  │  AwardFactory.java
            │  │  FactoryFactory.java
            │  │
            │  └─impl
            │          CashAwardFactory.java
            │          IphoneAwardFactory.java
            │          MallCardAwardFactory.java
            │
            └─service
                │  AwardService.java
                │
                └─impl
                        CashAwardService.java
                        IphoneAwardService.java
                        MallCardAwardService.java

首先第一步: 让所有产品都遵循同一接口。

1.声明一个发放奖品的接口
 public interface AwardService {
    void getAward(String username);
}

2.编写具体发放奖品的实现类并且实现发放奖品接口
public class CashAwardService implements AwardService {
    private String award = "3000元 现金";
    @Override
    public void getAward(String username){
        System.out.println(username + "获得了" + award);
    }
}

public class IphoneAwardService implements AwardService {
    private String award = "Iphone 12";
    @Override
    public void getAward(String username){
        System.out.println(username + "获得了" + award);
    }
}

public class MallCardAwardService implements AwardService {
    private String award = "1000元 购物卡";
    @Override
    public void getAward(String username){
        System.out.println(username + "获得了" + award);
    }
}

其次,创建工厂接口(具体创建者),然后创建具体产品的工厂类实现工厂接口(一个工厂只负责生产一个产品,做到专职)

1.创建奖品发放工厂接口
public interface AwardFactory {
    AwardService getAwardService(Integer awardNumber);
}

2.创建不同的奖品发放工厂,并且实现奖品发放工厂
public class CashAwardFactory implements AwardFactory {

    @Override
    public AwardService getAwardService(Integer awardNumber) {
        return new CashAwardService();
    }
}

public class IphoneAwardFactory implements AwardFactory {
    @Override
    public AwardService getAwardService(Integer awardNumber) {
        return new IphoneAwardService();
    }
}

public class MallCardAwardFactory implements AwardFactory {
    @Override
    public AwardService getAwardService(Integer awardNumber) {
        return new MallCardAwardService();
    }
}

最后,创建抽象创建者工厂,工厂方法中添加临时参数来控制返回的需要发放奖品的类型。

public class FactoryFactory {
    public static AwardFactory createFactory(Integer awardNumber) {
        AwardFactory factory;
        if (awardNumber == 1){
            factory = new MallCardAwardFactory();
        }else if (awardNumber == 2){
            factory = new IphoneAwardFactory();
        }else{
            factory = new CashAwardFactory();
        }
        return factory;
    }
}

测试验证

public class Test {

    public static void main(String[] args) {
        int index = 0;
        while(index++ < 10) {
            String employee = "小葛";
            //小葛随机获得奖品
            Random random = new Random();
            Integer awardNumber = random.nextInt(3);
            System.out.print(employee + "抽奖兑换------    ");
            AwardFactory factory = FactoryFactory.createFactory(awardNumber);
            AwardService service = factory.getAwardService(awardNumber);
            service.getAward(employee);
        }
    }
}

结果

小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了Iphone 12
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了Iphone 12
小葛抽奖兑换------    小葛获得了3000元 现金
小葛抽奖兑换------    小葛获得了1000元 购物卡
小葛抽奖兑换------    小葛获得了1000元 购物卡

从结果中我们可以看出,我们使用工厂模式同样到达了预期效果,相对于最开始使用if...else...来实现,

  1. 其更满足设计模式中的单一职责原则开闭原则,我们每一种奖品的发放都由相应的类来进行控制,以及在后续进行扩展的时候,我们能够更加方便的进行扩展。
  2. 其代码可以在一定程度上避免创建者与产品的解耦

当然,面对非常复杂的业务,仅仅使用工厂模式远远是不够的,还需要配合其他的设计模式来进行使用。而对于特别简单的业务,使用工厂模式往往会增加系统的复杂性,具体使用场景还是根据当前的业务来。

5 工厂模式在源码的应用:

  1. jdk 的Integer的valueOf方法(静态工厂方法)
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

JDK在1.5中添加的一项新特性,把-128~127的数字缓存起来了。所以这个范围内的自动装箱的数字都会从缓存中获取,返回同一个数字。这样的好处就是提升性能和节省内存。

  1. Mybatis数据源(工厂 + 代理)
  2. Dubbo - Registry提供服务的注册 (工厂 + 模板)