【设计模式】创建型模式其一: 简单工厂模式(Java版)

175 阅读7分钟

创建型模式其一:《简单工厂模式》

今天的主题是Java设计模式之工厂模式:

在讲解工厂模式之前,我们需要先了解创建型模式。 如果已经了解概念,可以直接往下翻。

创建型模式(Creational Pattern):关注对象的创建过程

  • 创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离,对用户隐藏了类的实例的创建细节

  • 创建型模式描述如何将对象的创建和使用分离,让用户在使用对象时无须关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展

    总之一句话,使对象与对象的使用分离。 目的呢?对于设计人员,代码更好修改与维护。对于用户: 只管使用,不管细节

创建型模式关注点:

  • 创建什么(What)
  • 由谁创建(Who)
  • 何时创建(When)

围绕这些关注点,衍生了很多创建型的设计模式(当然包括我们的工厂模式):

image.png

简单工厂模式

那什么是简单工厂模式呢?

我们通过一个工厂类来创建对象,而不是在代码中直接使用 new 关键字来实例化对象。这个工厂类根据客户端请求的不同,返回创建的不同对象实例。

一句话: 客户端要什么直接给工厂说,工厂看你要什么就生产什么

简单抽象工厂的具体组成对象:

  • 具体产品类:将需要创建的各种不同产品对象的相关代码封装到具体产品类中
  • 抽象产品类:将具体产品类公共的代码进行抽象和提取后封装在一个抽象产品类中
  • 工厂类:提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入参数的不同创建不同的具体产品对象
  • 客户端:只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象

文字不理解就看下面的图片

image.png

到这里之后,相信已经懂了什么是简单工厂模式了。

那我开始举具体的例子:
某软件公司要基于Java语言开发一套图表库,该图表库可以为应用系统提供多种不同外观的图表,例如柱状图(HistogramChart)、饼状图(PieChart)、折线图(LineChart)等。该软件公司图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,通过设置不同的参数即可得到不同类型的图表,而且可以较为方便地对图表库进行扩展,以便能够在将来增加一些新类型的图表。意思就是我想要哪个图,工厂就给我生产哪个图 现使用简单工厂模式来设计该图表库。

我们从产品类开始设计: 既然是产品,肯定要明确自己的功能,为了满足扩展性, 在具体产品类之前我可以设计一个抽象产品类(这里的抽象不一定要是抽象类,也可以是接口)

抽象图表接口,充当抽象产品类

public interface Chart {
   // 每个产品都拥有显示方法
   void display();
}

具体产品类: 我们会实现抽象接口,实现具体的产品类

//柱状图类,充当具体产品类
public class HistogramChart implements Chart {
   public HistogramChart() {
      System.out.println("创建柱状图!");
   }
   
   public void display() {
      System.out.println("显示柱状图!");
   }
}
//折线图类,充当具体产品类
public class LineChart implements Chart {
   public LineChart() {
      System.out.println("创建折线图!");
   }
   public void display() {
      System.out.println("显示折线图!");
   }
}

//饼状图类,充当具体产品类
public class PieChart implements Chart {
   public PieChart() {
      System.out.println("创建饼状图!");
   }
   
   public void display() {
      System.out.println("显示饼状图!");
   }
}

产品都有了,怎么能没有工厂呢?

因此定义工厂类: 大家想一想工厂生产哪个类,通过什么来判断?

通过用户的要求,因此我们工厂需要一个方法,根据接收的参数来判断生成的具体产品类型。

我们的结构必定是:
if(饼图) 创建饼图
else if(折线图) 创建折线图
else 创建柱状图

想法有了,设计代码:

工厂类代码

// 使用静态方法也是有讲究的, 可以不用新建很多工厂类,直接通过类名调用方法,节省资源。

public class ChartFactory {
   public static Chart getChart(String type) {
      Chart chart = null;
      if (type.equalsIgnoreCase("histogram")) {
         chart = new HistogramChart();
         System.out.println("创建了柱状图");
      }
      else if (type.equalsIgnoreCase("pie")) {
         chart = new PieChart();
         System.out.println("创建了饼图");
      }
      else if (type.equalsIgnoreCase("line")) {
         chart = new LineChart();
         System.out.println("创建了折图");
      }
      return chart;
   }
}

上面的设计思路确实是无比正确的,但是我们把要生产的类的选择权交给配置文件,我们修改时只用改改配置文件,而不用每次都去修改代码,这样有很多好处:

  1. 配置文件与代码分离:在软件系统中,配置文件与代码是分离的,这使得修改配置文件比修改代码更容易,更方便。
  2. 不需要重新编译:修改配置文件不需要重新编译整个软件系统,这可以节省时间和资源。而修改代码则需要重新编译、测试和部署整个系统,成本较高。
  3. 配置文件可读性好:配置文件通常采用简单的文本格式,易于阅读和理解。而代码则可能比较复杂,需要具备相应的编程知识和技能。
  4. 配置文件可扩展性好:在软件系统中,配置文件通常具有较好的可扩展性,可以方便地新增、删除或修改配置项,而不需要修改代码。这使得配置文件更加灵活和适应变化。

我们使用XML文件:(如果有人想要看其它的配置文件,自己也不想整理的话,评论区给我说我可以出一期

XML文件代码

<?xml version="1.0"?>
<config>
   <chartType>pie</chartType>
</config>

读取XML文件的工具类:

package designpatterns.simplefactory;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import java.io.*;

public class XMLUtil {
   //该方法用于从XML配置文件中提取图表类型,并返回类型名
    public static String getChartType() {
        try {
           //创建文档对象
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dFactory.newDocumentBuilder();
            // 创建文档对象,Document是一个接口,需要指向实现类
            Document doc;
            // 这里直接按文件读取就行
            doc = builder.parse(new File("./config.xml"));
            // 获取包含图表类型的文本结点(默认是返回结点列表,毕竟不清楚该类型的标签有多少个)
            NodeList nl = doc.getElementsByTagName("chartType");
            // 选择列表里的第一个
            Node classNode = nl.item(0).getFirstChild();
            String chartType = classNode.getNodeValue().trim();
            return chartType;
        }
        catch(Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

好了,万事俱备,只欠客户端来使用了:

客户端代码:

public class Client {
   public static void main(String args[]) {
      Chart chart;
      String type = XMLUtil.getChartType(); //读取配置文件中的参数
      chart = ChartFactory.getChart(type);  //创建产品对象

      chart.display();
   }
}

结果

创建饼状图!
创建了饼图
显示饼状图!

可能有小伙伴不喜欢XML文件,也可以直接创建工厂类,传入字符串参数
chart = ChartFactory.getChart("histogram"); //通过静态工厂方法创建产品,然后再调用也是一样的结果。

public class Client {
   public static void main(String args[]) {
      Chart chart;
      chart = ChartFactory.getChart("histogram"); //通过静态工厂方法创建产品
      chart.display();
   }
}

个人认为: 只有实践了才能知道该模式的好坏

模式优点:

  • 实现了对象创建和使用的分离
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类(注意: 只能是已经写好的产品),在一定程度上提高了系统的灵活性

模式缺点:

  • 工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响
  • 增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度
  • 系统扩展困难,一旦添加新产品不得不修改工厂逻辑
  • 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构,工厂类不能得到很好地扩展

下一篇: 创建型模式其二: 【一文通关】创建型模式其二: 工厂方法模式 - 掘金 (juejin.cn)