深入设计模式03---简单工厂模式(静态工厂模式)

举报
老司机张师傅 发表于 2022/07/26 23:15:23 2022/07/26
【摘要】 前言近期一直在抽空《设计模式》,进入深秋了,工作也主键忙碌起来,直到今天周末才有机会挤出时间更新博客,本篇简单工厂模式是学习23中常用设计模式的必备模式,可以说是引导入门设计模式,希望大家可以通过本篇博客理解简单工厂模式的作用及用法,更加理解设计模式。简单工厂模式是最简单的设计模式之一,虽然它不属于GoF(一个团队)提出的23种设计模式,但是应用也比较频繁,同时它是学习创建型设计模式的基础...

前言

近期一直在抽空《设计模式》,进入深秋了,工作也主键忙碌起来,直到今天周末才有机会挤出时间更新博客,本篇简单工厂模式是学习23中常用设计模式的必备模式,可以说是引导入门设计模式,希望大家可以通过本篇博客理解简单工厂模式的作用及用法,更加理解设计模式。
简单工厂模式是最简单的设计模式之一,虽然它不属于GoF(一个团队)提出的23种设计模式,但是应用也比较频繁,同时它是学习创建型设计模式的基础,学过了简单工厂模式会更加有助于理解创建型设计模式。在简单工厂模式中只需要记住一个简单的参数就可以获取所需要对象的实例,所有的工厂模式的思想都是实现对象的创建和使用分离,遵循单一职责原则。

正文

  • 创建型设计模式的定义

    创建型设计模式(Creational Pattern)关注对象的创建过程,是一类最常用的设计模式。创建型设计模式对类的实例化过程进行了抽象,能够将对象的创建和使用实现分离,对客户端隐藏类的实例创建的细节。用户在使用对象时不再需要关心对象的创建过程,从而可以降低系统的耦合度,让设计方案更易于修改和扩展。每一个创建型设计模式都采用不同的方案来回答三个问题:创建什么、由谁创建、何时创建。
    简单工厂模式通常通常作为学习其它创建型设计模式(工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式)的基础。

  • 概述

    定义一个工厂类,它可以根据不同的参数返回不同类的实例,被创建的实例通常具有某些相同的父类。
    简单工厂模式的设计思想如下:
    首先,需要一个抽象类将我们要具体生产的类似的产品类的公共属性提取出来,这个类作为抽象产品类,然后创建各种不同的产品对象类继承该抽象产品类,这些产品对象类被称为具体产品类,每一个具体产品类都是抽象产品类的子类;然后提供一个工厂类,在工厂类中提供一个方法用于根据不同的参数创建拥有共同父类的产品类对象,客户端只需要调用工厂类的方法就可以得到一个具体产品对象。
    因为简单工厂模式中工厂创建对象的方法是静态方法,因此简单工厂模式也被称为静态工厂模式。该模式的要点在于当用户需要某个类时,只需要传入一个正确的参数就可以获取所需要的对象。

  • 结构与实现

    • 模式结构
      • Factory(工厂角色): 简单工厂模式的核心,负责创建产品类对象,工厂类可以被外界直接调用,创建所需要的产品对象。
      • Product(抽象产品角色): 工厂类创建的所有对象类的父类,抽象产品类的引入将会大大提高系统的灵活度,并且使得工厂类只需要提供一个创建对象的方法就可以(因为所有的对象类都是抽象产品类的子类 )。
      • ConcreteProduct(具体产品类): 要被客户具体使用的具体对象类,具体产品类对应该继承抽象产品类,这样才能更灵活的被工厂创建出来。
    • 实现Demo

      使用简单工厂模式时首先需要对产品类进行重构,不能搞一个什么都包含的产品类,应当产品类重构成具有层次结构的产品,将他们公共的属性与方法抽取到抽象产品类中,然后在抽象产品类中定义一些抽象的方法来满足客户的功能需求。
      目录结构:
      在这里插入图片描述
      简单而典型且优雅的抽象产品类代码:

      public abstract class Product {
      
          // 所有产品类中的公共业务方法
          public void methodSame(){
              // 公共方法的实现
              System.out.println("Product methodSame");
          }
      
          // 抽象业务方法
          public abstract void methodDiff();
      
      }
      

      具体产品类: 具体产品类中实现抽象产品类中定义的抽象方法来做具体的业务逻辑操作。

      public class ConcreteProcuctA extends Product {
          @Override
          public void methodDiff() {
              System.out.println("ConcreteProcuctA methodDiff");
          }
      }
      
      
      public class ConcreteProcuctB extends Product {
          @Override
          public void methodDiff() {
              System.out.println("ConcreteProcuctB methodDiff");
          }
      }
      
      

      工厂类: 工厂类根据客户传入的不同参数来创建不同的对象。

      public class Factory {
      
          // 静态工厂方法
          public static Product getProduct(String arg){
              Product product = null;
      
              if ("A".equalsIgnoreCase(arg))
                  product = new ConcreteProcuctA();
                  // todo 初始化设置product属性
              else if ("B".equalsIgnoreCase(arg))
                  product = new ConcreteProcuctB();
                   // todo 初始化设置product属性
              return product;
          }
      }
      
      

      客户类: 模拟消费产品类

      public class Client {
      
          public static void main(String[] args) {
              Product product;
              product = Factory.getProduct("B");
              product.methodSame();
              product.methodDiff();
          }
      
      }
      

相信大家看了demo对简单工厂模式已经有了一个简单的了解,接下来我们用一个真实的案例来进一步的深入理解下简单工厂模式。

  • 超真实案例

    需求: 根据Java开发一套图表库,用以提供不同外观的图表;
    例如: 柱状图(HistogramChart)、饼状图(Piechart)、折线图(LinChart)等;
    需求分析:使用工厂模式开发一套灵活的可用的图表库,通过设置不同的参数可以获得不同的图表,使用简单工厂模式设计。
    结构: 需要Chart抽象类(也可以是接口)作为抽象产品类,各种图库实现该接口通 过不同的方式提供不同图表,工厂类用于生产这些图表,
    客户端通过传入不同的参数获取不同类型的图表

    上代码

    目录结构:
    在这里插入图片描述

    首先是图表抽象类: 它需要一个展示图表的方法。

    public abstract class Chart {
    
        // 显示图表的方法
        public abstract void display();
    }
    

    然后是具体的实现类: 需要实现抽象产品类中的方法(真实环境中需要巨多的业务逻辑以及属性,由于我们是学习环境,搞得太复杂了不好理解,这里简化下)

    public class HistogramChart extends Chart {
    
        public HistogramChart() {
            System.out.println("真的创建了柱状图");
        }
    
        @Override
        public void display() {
            System.out.println("展示了柱状图");
        }
    
    }
    
    public class LineChart extends Chart {
    
        public LineChart() {
            System.out.println("真的创建折线图");
        }
    
        @Override
        public void display() {
            System.out.println("展示了折线图");
        }
    }
    
    public class PieChart extends Chart {
    
        public PieChart() {
            System.out.println("真的饼状图");
        }
    
        @Override
        public void display() {
            System.out.println("展示了饼状图");
        }
    }
    

    工厂类: 用于创建不同的图表类

    public class ChartFactory {
    
        public static Chart getChart(String type) {
            Chart chart = null;
            if ("histogram".equalsIgnoreCase(type))
                chart = new HistogramChart();
            else if ("line".equalsIgnoreCase(type))
                chart = new LineChart();
            else if ("pie".equalsIgnoreCase(type))
                chart = new PieChart();
    
            return chart;
        }
    
    }
    

    客户端模拟消费:

    public class Client {
    
    
        /**
         * 简单工厂模式案例:
         *  需求: 根据Java开发一套图表库,用以提供不同外观的图表;
         *      例如: 柱状图(HistogramChart)、饼状图(Piechart)、折线图(LinChart)等;
         *  需求分析:使用工厂模式开发一套灵活的可用的图表库,通过设置不同的参数可以获得不同的图表,使用简单工厂模式设计
         *      结构: 需要Chart抽象类(也可以是接口)作为抽象产品类,各种图库实现该接口通过不同的方式提供不同图表,工厂类用于生产这些图表,
         *             客户端通过传入不同的参数获取不同类型的图表
         *
         * @param args
         */
        public static void main(String[] args) {
            testMethod1();
        }
        private static void testMethod1() {
            Chart chart;
            chart = ChartFactory.getChart("line");
            chart.display();
        }
    
    }
    

    大家应该可以发现,在这样的设计下我们每次想要更换创建的图表时都需要修改客户端的代码来改变传入工厂的参数,这样违背了开闭原则(详见面向对象七大设计原则),我们可以对项目进行适当的更改来解决此问题。
    优化1:
    引入config.xml配置文件:

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

    创建一个用于读取xml配置文件的工具类:

    public class XMLUtil {
    
        // 该方法用于从图标类中获取配置的类型名
        public static String getChartType(){
            try {
                DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder documentBuilder = dFactory.newDocumentBuilder();
                Document doc = documentBuilder.parse(new File("SimpleFactory/src/com/xz/realdemo/config.xml"));
    
                // 获取包含图表配置的文本
                NodeList chartType = doc.getElementsByTagName("chartType");
                Node classNode = chartType.item(0).getFirstChild();
                String chart = classNode.getNodeValue().trim();
                return chart;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    }
    

    修改下客户端的代码:

    public class Client {
    
    
        /**
         * 简单工厂模式案例:
         *  需求: 根据Java开发一套图表库,用以提供不同外观的图表;
         *      例如: 柱状图(HistogramChart)、饼状图(Piechart)、折线图(LinChart)等;
         *  需求分析:使用工厂模式开发一套灵活的可用的图表库,通过设置不同的参数可以获得不同的图表,使用简单工厂模式设计
         *      结构: 需要Chart抽象类(也可以是接口)作为抽象产品类,各种图库实现该接口通过不同的方式提供不同图表,工厂类用于生产这些图表,
         *             客户端通过传入不同的参数获取不同类型的图表
         *
         * @param args
         */
        public static void main(String[] args) {
    
            //testMethod1();
    
            /*
            由于Method1在想要修改图表类型时我们需要改客户端的代码,这违背了开闭原则,因此我们将类型提取到config.xml配置文件中,
            通过读取配置文件来获取需要创建的图表类型,这样在修改图表类型时我们只需要对配置文件进行修改即可(在Web项目中我们也可以通过前端传入该参数,
           在Spring框架中我们可以通过properties配置文件来实现)
             */
            testMethod2();
    
        }
    
        private static void testMethod3() {
            Chart chart;
            chart = Chart.getChart(XMLUtil.getChartType());
            chart.display();
        }
    
        private static void testMethod2() {
            Chart chart;
            chart = ChartFactory.getChart(XMLUtil.getChartType());
            chart.display();
        }
    
        private static void testMethod1() {
            Chart chart;
            chart = ChartFactory.getChart("line");
            chart.display();
        }
    
    }
    
    

    这样在修改要产生的图表类型时,我们就不需要对已有的代码进行更改,只需修改配置文件即可,这更加符合开闭原则。
    优化2: 在大多数情况下,为了简化系统,我们都可以将工厂类与抽象产品类合并起来,即在抽象产品类中直接提供工厂类的优化方法。
    修改完的抽象产品类:

     ```
     public abstract class Chart {
     
         // 显示图表的方法
         public abstract void display();
     
         public static Chart getChart(String type) {
             Chart chart = null;
             if ("histogram".equalsIgnoreCase(type))
                 chart = new HistogramChart();
             else if ("line".equalsIgnoreCase(type))
                 chart = new LineChart();
             else if ("pie".equalsIgnoreCase(type))
                 chart = new PieChart();
     
             return chart;
         }
     
     }
     ```
    

    客户端类可以直接这样调用:

    public class Client {
    
    
        /**
         * 简单工厂模式案例:
         *  需求: 根据Java开发一套图表库,用以提供不同外观的图表;
         *      例如: 柱状图(HistogramChart)、饼状图(Piechart)、折线图(LinChart)等;
         *  需求分析:使用工厂模式开发一套灵活的可用的图表库,通过设置不同的参数可以获得不同的图表,使用简单工厂模式设计
         *      结构: 需要Chart抽象类(也可以是接口)作为抽象产品类,各种图库实现该接口通过不同的方式提供不同图表,工厂类用于生产这些图表,
         *             客户端通过传入不同的参数获取不同类型的图表
         *
         * @param args
         */
        public static void main(String[] args) {
    
            //testMethod1();
    
            /*
            由于Method1在想要修改图表类型时我们需要改客户端的代码,这违背了开闭原则,因此我们将类型提取到config.xml配置文件中,
            通过读取配置文件来获取需要创建的图表类型,这样在修改图表类型时我们只需要对配置文件进行修改即可(在Web项目中我们也可以通过前端传入该参数,
           在Spring框架中我们可以通过properties配置文件来实现)
             */
            //testMethod2();
    
            // 可以将简单工厂模式简化,将抽象产品类和工厂类合并
            testMethod3();
        }
    
        private static void testMethod3() {
            Chart chart;
            chart = Chart.getChart(XMLUtil.getChartType());
            chart.display();
        }
    
        private static void testMethod2() {
            Chart chart;
            chart = ChartFactory.getChart(XMLUtil.getChartType());
            chart.display();
        }
    
        private static void testMethod1() {
            Chart chart;
            chart = ChartFactory.getChart("line");
            chart.display();
        }
    
    }
    

    相比较于前两种方法更加的简单而优雅。

  • 优缺点分析

    • 优点
      • 客户端不再需要关心对象的创建,创建交给工厂来完成,客户只需要使用即可,这实现了对象的创建和使用分离,符合单一职责原则。
      • 客户端无需知道对象的类名,只需要知道要使用对象的“代号”,对于一些复杂的类名可以通过这种方法简化。
      • 通过引入配置文件,可以在不修改任何代码的情况下更换具体的对象,一定程度上提高了代码的灵活度。
    • 缺点
      • 工厂类集中了所有的一类产品的创建,职责过于繁重,一旦出现问题不能正常工作,整个系统都会收到影响;
      • 势必会增加系统引入类的个数,一定程度上增加了系统的复杂度;
      • 一旦添加新的产品就不得不修改工厂类,如果产品类型较多可能会造成工厂类方法逻辑过于复杂,不利于系统的扩展和维护;
      • 由于工厂使用使用静态方法,会造成工厂角色无法被继承,无法形成基于继承的等级结构。
    • 适用环境
      在以下情况下可以考虑使用简单工厂模式:
      1. 工厂类负责创建的对象较少,由于创建的对象少,不会造成工厂方法的业务逻辑过分复杂;
      2. 客户端只知道传入工厂类的参数,对于如何创建对象不关心或者我们需要对客户隐藏对象的创建过程。
  • 自练习习题

    • 使用简单工厂模式模拟女娲造人,如果向女娲传入参数M,则返回一个男人,如果传入W,则返回一个女人,使用Java语言模拟实现该场景。现需要增加一个机器人,传入参数R则创建机器人,修改项目,并注意女娲(工厂类)的变化。
    • 设计一个可以创建几个不同几何形状的绘图工具类,如:圆形、矩形、三角形等,每个图像都有绘制和擦除两个方法,要求绘制不支持的图形时抛出一个UnsupportedShapeException异常,试设计该功能并用Java语言实现。
      请大家像我一样认真完成哦。

    半原创博客,用以记录学习,希望可以帮到您,不喜可喷。
    在这里插入图片描述

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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