工厂模式是最常用的一类创建型设计模式。我们所说的工厂模式是指工厂方法模式,它也是使用频率最高的工厂模式。
简单工厂模式是工厂方法模式的小弟,它不属于GoF 23种设计模式,但是在软件开发中应用也颇为频繁,通常将它作为学习其它工厂模式的入门。
A科技公司计划使用C#语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,比如柱状图、饼状图、折线图等。A科技公司的图表库设计人员希望为应用系统开发人员提供一套灵活易用的图表库,而且可以比较方便的对图表库进行扩展,以便能够在将来增加一些新类型的图表。A科技公司的研发人员提出了一个初始化设计方案,将所有图表的实现代码封装在一个 Chart 类中,代码如下:
public class Chart
{
private string type; //图表类型
public Chart(object[][] data, string type)
{
this.type = type;
if (type.Equals("histogram", StringComparison.OrdinalIgnoreCase))
{
//初始化柱状图
}
else if (type.Equals("pie", StringComparison.OrdinalIgnoreCase))
{
//初始化饼状图
}
else if (type.Equals("line", StringComparison.OrdinalIgnoreCase))
{
//初始化折线图
}
}
public void Display()
{
if (this.type.Equals("histogram", StringComparison.OrdinalIgnoreCase))
{
//显示柱状图
}
else if (this.type.Equals("pie", StringComparison.OrdinalIgnoreCase))
{
//显示饼状图
}
else if (this.type.Equals("line", StringComparison.OrdinalIgnoreCase))
{
//显示折线图
}
}
}
客户端代码通过调用 Chart 类的构造函数来创建图表对象,根据参数 type 的不同可以得到不同类型的图表,然后再调用 Display() 方法来显示相应的图表。
但是 Chart 类是一个巨大的类,在该类的设计中存在以下几个问题:
首先简单工厂模式不属于GoF 23种经典设计模式,但通常将它作为学习其它工厂模式的基础,它的设计思想很简单,基本流程如下:
简单工厂模式定义如下:
简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的方法是静态方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。
简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无需知道其创建细节。
在简单工厂模式结构图中包含3个角色:
在简单工厂模式中,客户端通过工厂类来创建一个产品类的实例,而无须使用 new 关键字来创建对象,它是工厂模式中最简单的一员。
在使用简单工厂模式时,首先需要对产品类进行重构,将所有产品类中公共的代码转移到抽象产品类中,并在抽象产品类中声明一些抽象方法,以供不同的具体产品类来实现。
为了将 Chart 类的职责分离,同时将 Chart 对象的创建和使用分离,A科技公司开发人员决定使用简单工厂模式对图表库进行重构,重构后的图表库结构如下:
IChart 接口充当抽象产品类,其子类HistogramChart、LineChart、PieChart充当具体产品类,ChartFactory充当工厂类。完整代码如下:
/// <summary>
/// 抽象图表接口:抽象产品类
/// </summary>
public interface IChart
{
void Display();
}
/// <summary>
/// 柱状图类:具体产品类
/// </summary>
public class HistogramChart : IChart
{
public HistogramChart()
{
Console.WriteLine("创建柱状图!");
}
public void Display()
{
Console.WriteLine("显示柱状图!");
}
}
/// <summary>
/// 折线图类:具体产品类
/// </summary>
public class LineChart : IChart
{
public LineChart()
{
Console.WriteLine("创建折线图!");
}
public void Display()
{
Console.WriteLine("显示折线图!");
}
}
/// <summary>
/// 饼状图类:具体产品类
/// </summary>
public class PieChart : IChart
{
public PieChart()
{
Console.WriteLine("创建饼状图!");
}
public void Display()
{
Console.WriteLine("显示饼状图!");
}
}
/// <summary>
/// 图表工厂类:工厂类
/// </summary>
public class ChartFactory
{
/// <summary>
/// 静态工厂方法
/// </summary>
/// <param name="type">图表类型</param>
/// <returns></returns>
public static IChart GetChart(string type)
{
IChart chart = null;
if (type.Equals("histogram", StringComparison.OrdinalIgnoreCase))
{
chart = new HistogramChart();
Console.WriteLine("初始化设置柱状图");
}
else if (type.Equals("line", StringComparison.OrdinalIgnoreCase))
{
chart = new LineChart();
Console.WriteLine("初始化设置折线图");
}
else if (type.Equals("pie", StringComparison.OrdinalIgnoreCase))
{
chart = new PieChart();
Console.WriteLine("初始化设置饼状图");
}
return chart;
}
}
编写客户端测试代码:
class Program
{
static void Main(string[] args)
{
IChart chart = ChartFactory.GetChart("line");
chart.Display();
}
}
编译运行输出,结果如下:
在客户端的测试代码中,我们可以看到使用工厂类的静态工厂方法来创建具体产品对象。如果后期需要更换产品,只需要修改静态工厂方法的入参即可。例如,我们需要将折线图改成饼状图,只需要将代码:IChart chart = ChartFactory.GetChart("line");
中的 line
改为 pie
即可。
一切进行的很顺利,但是A科技公司的开发人员发现,在创建具体的 Chat 对象时,每次更换具体的 Chart 对象类型都需要修改静态工厂方法中的入参,然后还需要重新编译客户端代码,这对客户端来说**违反了开闭原则。**那有没有一种方法可以在不修改代码的前提下就能更换具体的产品对象呢?当然有!在C#中我们可以将静态工厂方法中的入参配置到config文件中,这样每次要替换具体的产品类,我们只需要修改配置文件即可。比如:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="chartType" value="line"/>
</appSettings>
</configuration>
客户端代码如下:
class Program
{
static void Main(string[] args)
{
//获取配置文件中的chartType的值
string type = System.Configuration.ConfigurationManager.AppSettings["chartType"];
IChart chart = ChartFactory.GetChart(type);
chart.Display();
}
}
从上边的代码可以看出,客户端代码中不包含任何与具体图表相关的信息。如果要更换具体图表对象,只需要修改config配置文件中的配置即可,无需修改任何代码,符合开闭原则。
补充:
在ASP.NET Core中配置文件通常使用的是json文件,比如appsettings.json,另外还有很多种配置文件类型,比如:ini、环境变量、用户机密等
如果一个类即要负责创建引用的对象,又要使用使用引用对象的方法,这样就会使创建对象和使用对象的职责耦合在一起,这样会导致一个很严重的问题,那就是违反了开闭原则。那怎么解决这个问题呢?
最常用的一种方法就是将创建对象的职责移除,并交由其它类来负责创建。由谁创建呢?答案是:工厂类。通过引入工厂类,客户类不涉及对象的创建,对象的创建者也不会涉及对象的使用。
所有的工厂模式都强调一点:两个类A和B之间的关系应该仅仅是A创建B或者是A使用B,而不能两种关系都有。将对象的创建和使用分离,也使得系统更加符合单一职责原则,有利于对功能的复用和系统的维护。
将创建对象和使用对象分离还有一个好处:防止用来实例化一个类的数据和代码在多个类中到处都是。
也不是说将每一个类都配置一个工厂类,而是要具体问题具体分析,对于那些产品类很简单,而且也不存在太多变数,构造过程也很简单的类,就无需为其提供工厂类,直接在使用的时候实例化即可。
简单工厂模式提供了专门的工厂类用于创建对象,将对象的创建和对象的使用分离开来。
https://github.com/crazyliuxp/DesignPattern.Simples.CSharp