【Java设计模式 设计模式与范式】创建型模式 二:简单工厂模式

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【Java设计模式 设计模式与范式】创建型模式 二:简单工厂模式

本篇Blog继续学习创建型模式,创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离,这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。本篇学习的是简单工厂模式。由于学习的都是设计模式,所有系列文章都遵循如下的目录:

  • 模式档案:包含模式的定义、模式的特点、解决什么问题、优缺点、使用场景等
  • 模式结构:包含模式的结构,包含的角色定义及调用关系
  • 模式实现:包含模式的实现方式代码举例或者生活中简单问题映射代码举例
  • 模式实践:如果工作中或开源项目用到了该模式,就将使用过程贴到这里,并且客观讨论使用的是否恰当
  • 模式对比:如果模式相似或模式有额外的替换方法,有必要体现其相似点及不同点,区分使用,说明哪些场景下使用哪种模式比较好
  • 模式扩展:如果模式有与标准结构定义不同的变体形式,一并体现出其变体结构;对模式的思考需要进行发散等。

接下来所有设计模式的介绍都暂且遵循此基本行文逻辑吗,如果某一条目没有则无需体现,但条目顺序遵循此结构

模式档案

在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况,因为类的构造过于复杂,如果直接在其他业务类内使用,则两者的耦合过重,后续业务更改,就需要在任何引用该类的源代码内进行更改,光是查找所有依赖就很消耗时间了,更别说要一个一个修改了

一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。不过,在 GoF 的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。实际上,前面一种分类方法更加常见,所以我们沿用第一种分类方法,在后续的两篇Blog中逐一对工厂方法模式和抽象工厂模式进行学习

模式定义:简单工厂模式是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在简单工厂模式中,创建对象不会对调用者暴露创建逻辑,而是通过使用接口来指向新创建的对象

模式特点:简单工厂模式的主要特点是:通过静态工厂方法,调用者不需要知道创建细节,只需要知道自己想要的对象名称即可获取该对象。

解决什么问题:在面向对象编程中,最常用的创建对象的方法是通过new操作符构造一个对象实例。但是在一些情况下, new操作符直接生成对象会带来一些问题。举例来说,许多类型对象的创造需要一系列的步骤: 可能需要计算获取对象的初始设置; 可能需要选择创建哪个对象的子对象实例; 可能在创建需要的对象前必须先创建一些辅助功能的对象。 在这些情况对象的创建就是一个 【过程】,不仅是一个【操作】,像一部大机器中的一个个齿轮传动,使调用者能轻松方便地构造对象实例,而不必关心构造对象实例的细节和复杂过程

优点: 一个调用者想创建一个对象,只要知道其名称就可以了,细节不必关注

缺点:每次增加一个产品时,都需要在静态工厂类中增加一段处理逻辑,不符合开闭原则。

使用场景: 当一种产品实现有多种不同的呈现方式或表现形式时。例如:日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口

模式结构

简单工厂模式又称为静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。它的特点就是:有一个抽象产品类和多个具体的产品类,只有一个具体的工厂类,一个具体的工厂类可以创建多个具体产品类的实例 先来看看它的组成:

  • 具体工厂角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
  • 抽象产品角色:它一般是具体产品继承的父类或者实现的接口
  • 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。

静态工厂方法模式结构如下

模式实现

我们来看依据上边的工厂方法结构图各类角色如何配合实现:

抽象产品角色

//抽象产品
    public interface Product {
        void show();
    }

具体产品角色

//具体产品:ProductA
    class ConcreteProduct1 implements Product {
        public void show() {
            System.out.println("具体产品1显示...");
        }
    }
    //具体产品:ProductB
   class ConcreteProduct2 implements Product {
        public void show() {
            System.out.println("具体产品2显示...");
        }
    }

具体工厂角色

实际上具体工厂角色有好几种实现方式,例如静态方法形式、单例形式、反射+枚举形式

1 静态方法形式

实际上,简单工厂模式还叫作静态工厂方法模式(Static Factory Method Pattern)。之所以叫静态工厂方法模式,是因为其中创建对象的方法是静态的:

public class ProductFactory {
  public static Product getProduct(String kind) {
            switch (kind) {
                case "product1":
                    return new ConcreteProduct1();
                case "product2":
                    return new ConcreteProduct2();
            }
            return null;
        }
}

之所以设计成静态方法基于如下原因考虑:

  • 首先静态的使用更方便,可以不需要通过创建工厂类对象来调用创建对象的方法;
  • 第二点这种工具类没有其他面向对象的属性,只负责创建对象,也不需要严格通过对象来进行使用;
  • 第三点如果要创建工厂类的对象要么就会发生频繁创建和销毁工厂对象,要么就需要引入单例模式来实现资源节约,而用静态方法可以实现和单例一样的效果

2 单例设计形式

我们也可以依据单例的变体多例的形式实现

class ProductFactory {
    private static final ConcurrentHashMap<String, Product> map = new ConcurrentHashMap();
    static {
        map.put("product1", new ConcreteProduct1());
        map.put("product2", new ConcreteProduct2());
    }
    public static Product getProduct(String productName) {
        return map.get(productName);
    }
}

3 反射枚举形式

public enum ProductEnum {
    ConcreteProduct1 ("com.example.factorymethods.ConcreteProduct1 "),
    ConcreteProduct2 ("com.example.factorymethods.ConcreteProduct2 "),
    private String className;
    ProductEnum (String className) {
        this.className = className;
    }
    public String getClassName() {
        return className;
    }
}
public class ProductFactory {
    public static Product getProduct(ProductEnum productEnum ){
        Product product= null;
        String className = productEnum .getClassName();
        try {
            Class clazz = Class.forName(className);
            product= (Product)clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return product;
    }
}

客户端调用如下:

public class FactoryPatternDemo {
   public static void main(String[] args) {
      ProductFactory productFactory = new ProductFactory ();
      //获取产品A
      ConcreteProduct1 product1= productFactory.getProduct("product1");
      //获取产品B
      ConcreteProduct2 product2= productFactory.getProduct("product2");
      product1.show();
      product2.show();
   }
}

打印结果如下:

具体产品1显示...
具体产品2显示...

模式实践

接下来我们看两个简单工厂的模式实践:文件解析工厂组织处理工厂

设计一个文件解析工厂

我们依据思考模式线性向前,从没有工厂,到简单工厂再到工厂方法。非工厂方法模式就是不使用工厂模式,它的特点就是:只有多个具体的产品类,没有工厂类。我们先来看看不使用工厂方法模式来创建对象是怎样的过程,看如下的一个例子:我们根据配置文件的后缀(json、xml、yaml、properties),选择不同的解析器(JsonRuleConfigParser、XmlRuleConfigParser……),将存储在文件中的配置解析成内存对象 RuleConfig

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {
      parser = new PropertiesRuleConfigParser();
    } else {
      throw new InvalidRuleConfigException(
             "Rule config file format is not supported: " + ruleConfigFilePath);
    }
    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
}

可以看到,调用者需要自己去创建不同的对象才能调用对象方法,而这几个Parser对象都有相同的执行方法,只不过执行具体内容不同。这样调用者其实就需要关心对象的创建过程了。

1 封装独立功能处理逻辑

为了让代码逻辑更加清晰,可读性更好,我们要善于将功能独立的代码块封装成函数。按照这个设计思路,我们可以将代码中涉及 parser 创建的部分逻辑剥离出来,抽象成 createParser() 函数。重构之后的代码如下所示:

public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = createParser(ruleConfigFileExtension);
    if (parser == null) {
      throw new InvalidRuleConfigException(
              "Rule config file format is not supported: " + ruleConfigFilePath);
    }
    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
  private IRuleConfigParser createParser(String configFormat) {
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(configFormat)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(configFormat)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(configFormat)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(configFormat)) {
      parser = new PropertiesRuleConfigParser();
    }
    return parser;
  }
}

2 使用简单工厂模式创建文件解析器

继续上边的例子,为了让类的职责更加单一、代码更加清晰,我们还可以进一步将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。而这个类就是我们现在要讲的简单工厂模式类。具体的代码如下所示实现方式也比较简单:

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFilePath) {
    String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
    IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFileExtension);
    if (parser == null) {
      throw new InvalidRuleConfigException(
              "Rule config file format is not supported: " + ruleConfigFilePath);
    }
    String configText = "";
    //从ruleConfigFilePath文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
  private String getFileExtension(String filePath) {
    //...解析文件名获取扩展名,比如rule.json,返回json
    return "json";
  }
}
public class RuleConfigParserFactory {
  public static IRuleConfigParser createParser(String configFormat) {
    IRuleConfigParser parser = null;
    if ("json".equalsIgnoreCase(configFormat)) {
      parser = new JsonRuleConfigParser();
    } else if ("xml".equalsIgnoreCase(configFormat)) {
      parser = new XmlRuleConfigParser();
    } else if ("yaml".equalsIgnoreCase(configFormat)) {
      parser = new YamlRuleConfigParser();
    } else if ("properties".equalsIgnoreCase(configFormat)) {
      parser = new PropertiesRuleConfigParser();
    }
    return parser;
  }
}

IRuleConfigParser 为抽象产品,JsonRuleConfigParser、XmlRuleConfigParser、YamlRuleConfigParser、PropertiesRuleConfigParser为具体产品,RuleConfigParserFactory就是我们的简单工厂,依据不同的后缀提供不同的实现。

3 单例+简单工厂实现资源复用

我们每次调用 RuleConfigParserFactorycreateParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用可以试着把多例模式和简单工厂结合一下,让工厂只提供一份实例对象:

public class RuleConfigParserFactory {
  private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();
  static {
    cachedParsers.put("json", new JsonRuleConfigParser());
    cachedParsers.put("xml", new XmlRuleConfigParser());
    cachedParsers.put("yaml", new YamlRuleConfigParser());
    cachedParsers.put("properties", new PropertiesRuleConfigParser());
  }
  public static IRuleConfigParser createParser(String configFormat) {
    if (configFormat == null || configFormat.isEmpty()) {
      return null;//返回null还是IllegalArgumentException全凭你自己说了算
    }
    IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
    return parser;
  }
}

设计一个组织任务处理工厂

依据上述模式进行一些简单的模式实践.在工作中其实经常会遇到这种场景,就拿一个同步组织的实现来说吧。同步组织有统一的通用方法,但是不同的业务实现的时候有不同的实现方式:

抽象产品角色

public interface OrgHandler {
  void syncOrg(SettleContractInfo settleContractInfo);
  void createCompanyOrgFromBatch(SaasHrTreeDetail waitingCreateCompanyInfo);
  void createStoreOrgFromBatch(SaasHrTreeDetail waitingCreateStoreInfo);
}

具体产品角色

@Service
public class CommercialEstateOrgHandler implements OrgHandler{
  @PostConstruct
  public void registerToFactory() {
    OrgHandlerFactory.register(BizLineEnum.COMMERCIAL.getCode(), this);
  }
  @Override
  public void syncOrg(SettleContractInfo settleContractInfo) {
  }
  @Override
  public void createCompanyOrgFromBatch(SaasHrTreeDetail waitingCreateCompanyInfo) {
  }
  @Override
  public void createStoreOrgFromBatch(SaasHrTreeDetail waitingCreateStoreInfo) {
   }
}

另一个具体的产品角色:

@Service
@Slf4j
public class BeiJiaOrgHandler implements OrgHandler {
  @PostConstruct
  public void registerToFactory() {
    OrgHandlerFactory.register(BizLineEnum.TEST.getCode(), this);
  }
  @Override
  public void syncOrg(SettleContractInfo settleContractInfo) {
  }
  public void syncOrgBackDoor(SettleContractInfo settleContractInfo) {
  }
}

具体工厂方法

public class OrgHandlerFactory {
  private static final Map<Integer, OrgHandler> orgHandlerMap = new ConcurrentHashMap<>();
  public static OrgHandler getOrgHandler(Integer bizLineCode) {
    OrgHandler orgHandler = orgHandlerMap.get(bizLineCode);
    if (Objects.isNull(orgHandler)) {
      throw new BusinessRuntimeException(ErrorNo.PARAM_ERROR);
    }
    return orgHandler;
  }
  public static void register(Integer bizLineCode, OrgHandler orgHandler) {
    orgHandlerMap.put(bizLineCode, orgHandler);
  }
}

可以看到实际使用时我们会用Map来存储type类型分别使用,不使用僵化的case或者if else。调用时逻辑如下:

private void synchronizationCompany() {
    List<SaasHrTreeDetail> waitingConfirmCompanyInfo = saasHrTreeDetailBizService.getWaitingConfirmCompanyInfo();
    for (SaasHrTreeDetail saasHrTreeDetail : waitingConfirmCompanyInfo) {
      try {
        OrgHandler orgHandler = OrgHandlerFactory.getOrgHandler(saasHrTreeDetail.getBizLineCode());
        orgHandler.createCompanyOrgFromBatch(saasHrTreeDetail);
      } catch (Exception e) {
        LOGGER.error("syncCompanyOrg failed,saasHrTreeDetail:{}", saasHrTreeDetail, e);
      }
    }
  }

总结一下

一种产品实现有多种不同的呈现方式或表现形式时,比较适合使用工厂模式,而简单工厂其实能适配我们日常使用的大多数场景,依据我在工作中的体验,工厂模式中几乎只用到了简单工厂。在几种简单工厂的实现中,我觉得多例模式(单例+Map)是最合适的工厂实现模式,它解决了if else这个灾难性问题,下一篇一起来看看工厂方法模式是怎么工作的吧!

相关文章
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
22天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
1月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
37 4
|
1月前
|
设计模式 安全 Java
Kotlin - 改良设计模式 - 构建者模式
Kotlin - 改良设计模式 - 构建者模式
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
42 1
|
2月前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
52 0
[Java]23种设计模式
|
1月前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
7天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
37 6
|
22天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
下一篇
DataWorks