设计模式(二):工厂方法模式

举报
水滴技术 发表于 2023/06/15 17:17:17 2023/06/15
【摘要】 在上一篇《简单工厂模式》中我们了解到,简单工厂模式每次增加新的产品时,都要修改其“工厂类”,这违背了开闭原则。而本篇介绍的工厂方法模式,对“工厂类”进一步抽象,即使新增产品也不用修改原来的代码,这满足了开闭原则。

在上一篇《简单工厂模式》中我们了解到,简单工厂模式每次增加新的产品时,都要修改其“工厂类”,这违背了开闭原则。而本篇介绍的工厂方法模式,对“工厂类”进一步抽象,即使新增产品也不用修改原来的代码,这满足了开闭原则。

1 定义

工厂方法模式(Factory Method Pattern)定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

也就是说,我们需要提供一个抽象的工厂接口,声明一个创建对象的工厂方法,由其子类具体实现工厂方法,并决定创建哪一个产品对象。

2 角色

下面是工厂方法模式中的几个角色:

  • 抽象产品(Product):所有具体产品的公共接口。
  • 具体产品(ConcreteProduct):实现了Product接口,是工厂方法中创建的对象。
  • 抽象工厂(Factory):所有具体工厂的公共接口。声明一个工厂方法,该方法返回一个Product类型的对象。
  • 具体工厂(ConcreteFactory):实现了Factory接口,工厂方法返回一个ConcreteProduct实例。
  • 客户端(Client):客户端通过具体工厂来创建对应的产品实例。

下面是该模式的UML类图:

3 实现

还是以《简单工厂模式》中的手机案例,来讲解该模式的实现。

3.1 抽象产品接口:Phone

创建一个手机接口,它可以打电话和发短信。

/**
 * 手机接口
 */
public interface Phone {

    /**
     * 打电话
     */
    void call();

    /**
     * 发短信
     */
    void sendSMS();
}

3.2 两个具体产品类:XiaomiPhone和RedmiPhone

为手机接口创建两个实现类,一个是小米手机,一个是红米手机,当然也可以有更多的实现类。

/**
 * 小米手机实现类
 */
public class XiaomiPhone implements Phone {
    @Override
    public void call() {
        System.out.println("使用小米手机打电话");
    }

    @Override
    public void sendSMS() {
        System.out.println("使用小米手机发短信");
    }
}

/**
 * 红米手机实现类
 */
public class RedmiPhone implements Phone {
    @Override
    public void call() {
        System.out.println("使用红米手机打电话");
    }

    @Override
    public void sendSMS() {
        System.out.println("使用红米手机发短信");
    }
}

3.3 一个工厂接口:PhoneFactory

创建一个手机工厂的接口,包含一个创建Phone的方法。

/**
 * 手机工厂接口
 */
public interface PhoneFactory {
    /**
     * 获取手机实例
     *
     * @return
     */
    Phone createPhone();
}

3.4 两个具体工厂类:XiaomiPhoneFactory和RedmiPhoneFactory

创建两个具体手机工厂实现类,每个工厂创建各自的产品实例。

/**
 * 小米手机工厂类
 */
public class XiaomiPhoneFactory implements PhoneFactory {
    @Override
    public Phone createPhone() {
        return new XiaomiPhone();
    }
}

/**
 * 红米手机工厂类
 */
public class RedmiPhoneFactory implements PhoneFactory {
    @Override
    public Phone createPhone() {
        return new RedmiPhone();
    }
}

3.5 客户端:Client

创建一个客户端,通过XiaomiPhoneFactory工厂创建小米手机;通过RedmiPhoneFactory工厂创建红米手机。

/**
 * 客户端类
 */
public class Client {

  public static void main(String[] args) {
        System.out.println("================小米手机================");
        Phone xiaomiPhone = new XiaomiPhoneFactory().createPhone();
        xiaomiPhone.call();
        xiaomiPhone.sendSMS();

        System.out.println("================红米手机================");
        Phone redmiPhone = new RedmiPhoneFactory().createPhone();
        redmiPhone.call();
        redmiPhone.sendSMS();
    }
}

3.6 运行结果

运行客户端main方法,会打印如下内容:

================小米手机================
使用小米手机打电话
使用小米手机发短信
================红米手机================
使用红米手机打电话
使用红米手机发短信

4 优缺点

优点:

  • 使对象的创建和使用分离,对象的创建交给具体的工厂类负责,客户端不需要关心是怎么创建的,只关心如何使用就行了。
  • 一个具体工厂只负责创建一个产品实例,遵循了单一职责。
  • 新增产品时,只需创建一个对应的工厂类即可,遵循了开闭原则。

缺点:

  • 每增加一个产品,都要增加一个对应的工厂类,类的数量激增。

5 典型应用

我们以Redis连接为例,来看下工厂方法模式在Spring Boot中的应用。

在Java中,连接Redis的两个比较常用的客户端是JedisLettuce,它们都被Spring官方加入到了spring-data-redis项目中,使用户可以自由的选择使用哪种连接方式。

来看下它的整体结构:

RedisConnection是Redis连接接口,在工厂方法模式中属于抽象产品角色。它有两个主要实现类:JedisConnectionLettuceConnection,是工厂方法中创建的具体产品。

RedisConnectionFactory是Redis连接工厂接口,在工厂方法模式中属于抽象工厂角色。它有两个实现类:JedisConnectionFactoryLettuceConnectionFactory

JedisConnectionFactory工厂中创建的是JedisConnection对象:

public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
      //...
      
      /*
       * 获取Redis连接
       */
      public RedisConnection getConnection() {
    
        assertInitialized();
    
        if (isRedisClusterAware()) {
          return getClusterConnection();
        }
    
        Jedis jedis = fetchJedisConnector();
        JedisClientConfig sentinelConfig = this.clientConfig;
    
        SentinelConfiguration sentinelConfiguration = getSentinelConfiguration();
        if (sentinelConfiguration != null) {
          sentinelConfig = createSentinelClientConfig(sentinelConfiguration);
        }
    
        JedisConnection connection = (getUsePool() ? new JedisConnection(jedis, pool, this.clientConfig, sentinelConfig)
            : new JedisConnection(jedis, null, this.clientConfig, sentinelConfig));
        connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults);
        return postProcessConnection(connection);
      }
      
      //...
}

而在LettuceConnectionFactory工厂中创建的是LettuceConnection对象:

public class LettuceConnectionFactory
    implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
      // ...

      /*
       * 获取Redis连接
       */
      public RedisConnection getConnection() {
    
        assertInitialized();
    
        if (isClusterAware()) {
          return getClusterConnection();
        }
    
        LettuceConnection connection;
        connection = doCreateLettuceConnection(getSharedConnection(), connectionProvider, getTimeout(), getDatabase());
        connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults);
        return connection;
      }
      
      // ...
 
}

6 完整代码

完整代码请访问我的Github,若对你有帮助,欢迎给个⭐,感谢~~🌹🌹🌹

https://github.com/gozhuyinglong/blog-demos/blob/main/design-patterns/src/main/java/io/github/gozhuyinglong/designpatterns/factory/FactoryMethod.java

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。