三种工厂模式

一、简单工厂模式

1.1 概述

简单工厂模式并不属于GoF的23种经典模式的一种,但通常将它作为学习其他工厂模式的基础,而且在实际中,它也是运用最广泛的一种设计模式。其主要实现步骤是将需要创建的各种不同对象的代码封装到不同的具体产品类中,这些具体产品类的公共属性与方法提取放在一个抽象产品中,具体产品类继承抽象产品类,然后再在工厂类中提供一个创建产品的工厂方法,该方法根据所传不同的参数来创建不同的具体产品。

定义:定义一个工厂类,它可以根据参数的不同返回创建不同类的实例,被创建的实例通常都具有共同的父类。

由于简单工厂模式中用于创建实例的方法通常是静态方法,所以简单工厂模式又称为静态工厂方法模式,它是一种类创建型模式

1.2 模式结构

  1. Factory(工厂角色):工厂角色即工厂类,负责实现创建所有具体产品实例的内部逻辑,会提供一个静态的工厂方法factoryMethod,返回类型则是抽象产品类型Product类
  2. Product(抽象产品角色):所有具体工厂类的父类,封装了各种产品对象的共有方法。
  3. ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。

image-20240601000037917

1.3 具体实现

例如公司需要开发一套图表库,其中包括了不同外观的图表,例如柱状图(HistogramChart)、饼状图(PieChart)、折线图(LineChart)等。

  • Chart(Product):抽象图表接口,充当抽象产品类。
    public interface Chart
    {
        public void display();
    }
  • HistogramChart、PieChart、LineChart(ConcreteProduct): 具体产品类
//HistogramChart:柱状图类(具体产品类)  
    public class HistogramChart : Chart
    {
        public HistogramChart()
        {
            Console.WriteLine("创建柱状图!");
        }

        public void display()
        {
            Console.WriteLine("显示柱状图!");
        }
    }
// PieChart:饼状图类(具体产品类)
    public class PieChart : Chart
    {
        public PieChart()
        {
            Console.WriteLine("创建饼状图!");
        }
        public void display()
        {
            Console.WriteLine("显示饼状图!");
        }
    }
//LineChart:折线图类(具体产品类)
    public class LineChart : Chart
    {
        public LineChart()
        {
            Console.WriteLine("创建折线图!");
        }
        public void display()
        {
            Console.WriteLine("显示饼状图!");
        }
    }
  • ChartFactory(Factory):图标工厂类(工厂类)
    public class ChartFactory
    {
        public static Chart GetChart(string type) 
        {
            Chart chart = null;
            if (type.Equals("HistogramChart"))
            {
                chart = new HistogramChart();
            }
            else if (type.Equals("Pie"))
            {
                chart = new PieChart();
            }
            else if (type.Equals("Line"))
            {
                chart = new LineChart();
            }
            return chart;
        }
    }
  • Client测试类:
        static void Main(string[] args)
        {
            // 通过静态工厂方法创建产品
            Chart chart = ChartFactory.GetChart("HistogramChart");
            chart?.display();
            // 输出创建柱状图! 显示柱状图
        }

1.4 优缺点

  • 优点:
    1. 实现了对象创建和使用的分离,通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了。
    2. 提高了系统的灵活性,可以通过配置文件,在不修改任何客户端代码情况下更换和增加新的具体产品类。
  • 缺点:
    1. 由于工厂类集中了所有产品的创建逻辑,职责过重。
    2. 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,当产品过多时,会造成工厂逻辑复杂,不利用维护。

1.5 使用场景

  1. 当工厂类负责创建的对象比较少时,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  2. 客户只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心。

二、工厂方法模式

2.1 概述

工厂方法模式是简单工厂的延伸,它弥补了简单工厂模点,更好的符合了开闭原则的要求,在增加新的具体产品对象中不需要对已有系统进行任何修改。工厂方法模式引入了抽象工厂类和具体工厂类,具体工厂类继承于抽象工厂,具体工厂类用于生产具体产品类的实例,可以使不修改具体工厂类的情况下引进心新的产品类。

定义:抽象工厂定义一个创建对象的接口,但是让(具体工厂)子类决定哪一个(产品)类实例化。

工厂方法模式又简称为工厂模式,工厂方法模式是一种类创建型模式

2.2 模式结构

  1. Factory(抽象工厂):声明一个工厂方法(FactoryMethod),用于返回一个产品。
  2. ConcreteFactory(具体工厂):它是抽象工厂的子类,实现了抽象工厂中声明的工厂方法,并由Client调用,返回一个具体产品类的实例。
  3. Product(抽象产品):定义产品的接口,也就是具体产品类的公共父类
  4. ConcreteProduct(具体产品):实现了抽象产品的接口,具体产品由具体工厂类创建,具体产品和具体工厂之间一一对应。

image-20240603003223354

2.3 具体实现

例如公司需要运行日志记录器(Logger)可以通过多种途径保存系统的运行日志,例如通过文件记录或者数据库记录,用户可以通过修改配置文件灵活地更换日志记录方式。

  • LoggerFactory:日志记录器工厂接口(抽象工厂)

    // 日志记录器工厂接口: 抽象工厂
    public interface LoggerFactory
    {
      public Logger CreateLogger();
    }
  • DataBaseLoggerFactory、FileLoggerFactory: 数据库/文件日志记录器工厂类(具体工厂类)

    // 数据库日志记录器工厂类: 具体工厂类
    public class DataBaseLoggerFactory : LoggerFactory
    {
      public Logger CreateLogger()
      {
          Logger logger = new DataBaseLogger();
          return logger;
      }
    }
    // 文件日志记录器工厂类: 具体工厂类
    public class FileLoggerFactory : LoggerFactory
    {
      public Logger CreateLogger()
      {
          Logger logger = new FileLogger();
          return logger;
      }
    }
  • Logger:日志记录接口(抽象产品)

    // 日志记录接口(抽象产品)
    public interface Logger
    {
        public void WriteLog();
    }
  • DataBaseLogger、FileLogger:数据库/文件记录器(具体产品)

      // 数据库记录器(具体产品)
      public class DataBaseLogger : Logger
      {
          public void WriteLog()
          {
              Console.WriteLine("数据库日志记录。");
          }
      }
      // 文件记录器(具体产品)
      public class FileLogger : Logger
      {
          public void WriteLog()
          {
              Console.WriteLine("文件日志记录。");
          }
      }
  • Client测试类:

          public static void Main(string[] args)
          {
              // 创建数据库工厂实例
              LoggerFactory loggerFactory = new DataBaseLoggerFactory();
              // 通过工厂创建具体的产品
              Logger logger = loggerFactory.CreateLogger();
              // 具体日志产品打印
              logger.WriteLog();
          }
  • 注意:由于具体工厂类和具体产品类之间的关系为一一对应,有时候为了简化客户端使用,可以对客户端隐藏工厂方法

      public abstract class LoggerFactory
      {
          public void WriteLogger()
          {
              Logger logger = this.CreateLogger();
              logger.WriteLog();
          }
    
          public abstract Logger CreateLogger();
      }

2.4 优缺点

  • 优点:

    1. 符合开闭原则:当在系统中增加新的产品时无须修改抽象工厂和抽象产品提供的接口,只需要添加一个具体工厂和具体产品类即可,
    2. 多态性设计:工厂方法模式可以利用多态性设计,让工厂能够自主确定创建何种对象,且创建该对象的细节完全封装在具体工厂内部。
  • 缺点:

    1. 系统过于复杂:当需要添加新的产品时,需要添加新的具体产品类和具体工厂类,类的个数成对增加,增加了系统的复杂性
    2. 过于抽象性:在客户端中均使用了抽象层进行定义,增加了系统的抽象性和理解难度。

2.5 使用场景

客户端不需要知道所需要的对象的类(具体产品类的类名),可通过配置文件去创建。

三、抽象工厂模式

3.1 概述

在工厂方法模式中每一个具体工厂只需要生产一种具体产品,但在抽象工厂模式中一个具体工厂可以生产一组相关的具体产品,这一组具体产品成为产品族产品族的每一个产品都分属于某一个产品继承等级结构

定义:提供一个创建一系列相关或者相互依赖对象的接口,而无须指定它们的具体的类。

抽象工厂又称为工厂(Kit)模式,是一种对象创建型模式。

3.2 模式结构

在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于生产多种不同类型的产品,这些产品构成一个产品族

  1. AbstractFactory(抽象工厂):声明了一组用于创建一族产品的方法,每个方法对应一种产品。
  2. ConcreteFactory(具体工厂):实现抽象工厂中声明的创建产品的方法,生成一组具体产品。
  3. AbstractProduct(抽象产品):它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  4. ConcreteProduct(具体产品):定义了具体工厂生产的具体产品对象,实现了抽象产品接口中声明的业务方法。

image-20240603223540388

3.3 具体实现

例如公司需要开发一套界面皮肤库,不同的皮肤将提供视觉效果不同的按钮、文本框、组合框等界面元素。例如春天(Spring)提供一套绿色的皮肤,夏天则提供一套浅蓝色的皮肤。示意图如下:

image-20240603224335867

  • SkinFactory:界面皮肤工厂接口(抽象工厂)

      // 界面皮肤工厂接口(抽象工厂)
      public interface SkinFactory
      {
          public Button CreateButton();
          public TextFiled CreateTextFiled();
          public ComboBox CreateComboBox();
      }
    
  • SpringSkinFactory、SummerSkinFactory:春天/夏天皮肤工厂(具体工厂)

    // 春天皮肤工厂(具体工厂)
    public class SpringSkinFactory : SkinFactory
    {
       public Button CreateButton()
       {
           return new SpringButton();
       }
    
       public ComboBox CreateComboBox()
       {
           return new SpringComboBox();
       }
    
       public TextFiled CreateTextFiled()
       {
           return new SpringTextFiled();
       }
    }
    // 夏天皮肤工厂(具体工厂)
    public class SummerSkinFactory : SkinFactory
    {
       public Button CreateButton()
       {
           return new SummerButton();
       }
    
       public ComboBox CreateComboBox()
       {
           return new SummerComboBox();
       }
    
       public TextFiled CreateTextFiled()
       {
           return new SummerTextFiled();
       }
    }
  • Button、TextFiled、ComboBox:按钮/文本框/边框组件(抽象产品)

    public interface Button
    {
      public void Display();
    }
    
    public interface TextFiled
    {
      public void Display();
    }
    
    public interface ComboBox
    {
      public void Display();
    }
  • SpringButton、SpringTextField、SpringComboBox、SummerButton、SummerTextField、SummerComboBox:具体产品类

    public class SpringButton : Button
    {
      public void Display()
      {
          Console.WriteLine("显示浅绿色按钮");
      }
    }
    
    public class SpringTextFiled : TextFiled
    {
      public void Display()
      {
          Console.WriteLine("显示浅绿色文本框");
      }
    }
    
    public class SpringComboBox : ComboBox
    {
      public void Display()
      {
          Console.WriteLine("显示浅绿色边框组合框");
      }
    }
    
    public class SummerButton : Button
    {
      public void Display()
      {
          Console.WriteLine("显示浅蓝色按钮");
      }
    }
    
    public class SummerTextFiled : TextFiled
    {
      public void Display()
      {
          Console.WriteLine("显示浅蓝色文本框");
      }
    }
    
    public class SummerComboBox : ComboBox
    {
      public void Display()
      {
          Console.WriteLine("显示浅蓝色边框组合框");
      }
    }
  • Client测试类:

      public class Client
      {
          public static void Main(string[] args)
          {
              SkinFactory skinFactory = new SpringSkinFactory();
              Button button = skinFactory.CreateButton();
              TextFiled textFiled = skinFactory.CreateTextFiled();
              ComboBox comboBox = skinFactory.CreateComboBox();
              button.Display();
              textFiled.Display();
              comboBox.Display();
          }
      }

3.4 优缺点

  • 优点:
    1. 符合开闭原则,增加新的产品族时,无须修改系统已有系统
    2. 方便切换系统的产品族行为
  • 缺点:新增新的产品等级需要对原有的系统进行修改,所有的产品族都要进行修改。

3.5 使用环境

  1. 用户无须关系对象的创建过程,将对象的创建和使用进行解耦。
  2. 当系统有多个产品族,且每次只使用其中某一个产品族。
  3. 产品等级结构稳定,当设计完成后不需要再向系统添加新的产品等级结构或者删除已有的产品等级结构。

四、总结

在游戏开发中,目前我遇到的大部分都是简单工厂模式,直接去工厂中获得产品。简单工厂模式(添加一个抽象工厂)-> 工厂方法模式(将工厂与产品一一对应扩展到产品族)-> 抽象工厂模式。简单工厂违反了开闭原则,工厂方法虽然符合开闭原则,但是每次添加产品都需要成对添加,会导致系统过于复杂,抽象工厂解决了工厂方法的系统过于复杂,但是当产品等级需要修改时,它又违反了开闭原则,当产品等级修改时,所有的具体产品也都需要随着修改,每次修改就会对系统进行一次大修改,所以每一种工厂模式都有弊端,也有好处,要结合实际去运用它。