【C#设计模式】简单工厂模式
前言
众所周知,C#是一种面向对象的语言,而其中封装,继承,多态是面向对象的三大重要特征,简单工厂的设计模式则可以完全体现这些特征。要彻底理解这个模式,必须要先将封装(访问修饰符的应用)、继承(基类和派生类的概念)、多态(重载,虚方法,重写,抽象方法的概念)全部搞懂,并能灵活运用。
简单工厂模式是最简单的设计模式之一,它虽然属于GoF的23种设计模式,但是应用也较为频繁,同时它也是学习其他创建型模式的基础。在简单工厂模式中,只需要记住一个简单的参数即可获得所需的对象实例,它提供专门的核心工厂类来负责对象的创建,实现对象的创建和使用分离。
创建型模式对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离,对用户隐藏了类的实例创建细节。
每一个创建型模式都通过采用不同的解决方案来回答3个问题:创建什么(What), 由谁创建(Who)和何时创建(When)。
一、简单工厂模式定义
简单工厂模式简单来说就是创建一个工厂类,通过输入的参数创建对象赋值给基类,完成对想要的派生类的调用,从而达成目标,具体的操作均在子类中完成,工厂类只负责运算逻辑和给基类赋值。该模式有三部分:
- 工厂类:实现创建所有实例的选择类型,被外界调用的接口。
- 抽象类:所要创建的类的基类,描述类所有实例所共有的公共接口(方法),可以是抽象类也可是接口类型(interface),本例是抽象类。
- 具体类:所有要创建的具体实例对象。
简单工厂模式(Simple Factory Patter): 定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
由于在简单工厂模式中用于创建实例的方法通常是静态方法,所以简单工厂模式又被称为静态工厂方法模式,它是一类创建型模式。
概述【进一步加深理解】:
- 首先将需要创建的各种不同产品对象的相关代码封装到不同的类中,这些类称为具体产品类。
- 而将它们公共的代码进行抽象和提取后封装在一个抽象产品类中,每一个具体产品类都是抽象产品类的子类;
- 然后提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入的参数的不同创建不同的具体产品对象;
- 客户端只需要调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象。
简单工厂模式结构图如下:
简单工厂模式包含以下3个角色:
- Factory(工厂角色): 工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建所有产品实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象;在工厂类中提供了静态的工厂方法FactoryMethod(),它返回的类型为抽象产品类型Product。
- Product(抽象产品角色): 它是工厂类所创建的所有对象的父类,封装了各种产品对象的公共方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法,因为所有创建的具体产品对象都是其子类的对象。
- ConcreteProduce(具体产品角色): 它是简单工厂模式的创建目标,所有被创建的对象 都充当 这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角色,需要实现在抽象产品中的声明的抽象方法。
二、实例
using System;
namespace ConsoleApp1
{
// 【01】抽象工厂类
public abstract class Product
{
// 所有产品类的公共业务方法
public void MethodSame()
{
Console.WriteLine("处理公共业务方法");
}
// 声明抽象业务方法
public abstract void MethodDiff();
}
// 【02】具体工厂类A
public class ConcreteProduceA : Product
{
// 实现业务方法
public override void MethodDiff()
{
Console.WriteLine("具体产品A处理业务方法");
}
}
//【03】具体工厂类B
public class ConcreteProduceB : Product
{
// 实现业务方法
public override void MethodDiff()
{
Console.WriteLine("具体产品B处理业务方法");
}
}
//【04】工厂类【简单工厂模式的核心】
public static class Factory
{
// 静态工厂方法
public static Product GetProduct(string arg)
{
Product product = null;
switch (arg)
{
case "A":
product = new ConcreteProduceA();
break;
case "B":
product = new ConcreteProduceB();
break;
default:
throw new ArgumentException(message: "Invalid arg value");//If the arg is not "A" or "B",throw an exception.
}
return product;
}
}
//【05】客户端调用
class Program
{
static void Main(string[] args)
{
//实例化产品A,并调用
try
{
Product productA = Factory.GetProduct("A");
//var productA = Factory.GetProduct("A");
productA.MethodSame();
productA.MethodDiff();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//实例化产品B,并调用
try
{
Product productB = Factory.GetProduct("B");
//var productB = Factory.GetProduct("B");
productB.MethodSame();
productB.MethodDiff();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadLine();
}
}
}
运行结果:
三、进一步研究【违反开闭原则的解决方法】
开闭原则:一个软件实体应当对扩展开放,对修改封闭
上边的实例,必须通过修改客户端代码中静态方法的参数来更换具体产品对象,客户端代码需要重新编译,这对于客户端而言,违背了开闭原则。
下面介绍一种常用的解决方案,利用App.config配置文件的方式,提高系统的灵活性。只需要添加配置文件并更改相关参数即可,不要重新编译程序。
需要通过Nuget添加如下引用:
App.config:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<appSettings>
<add key="Product" value="B" />
</appSettings>
</configuration>
调用方法:
using System;
using System.Configuration;//必须添加的引用
namespace ConsoleApp1
{
//其余代码省略,同上
class Program
{
static void Main(string[] args)
{
//客户端调用
var type = ConfigurationManager.AppSettings["product"];
Product productA = Factory.GetProduct(type);
//var productA = Factory.GetProduct("A");
productA.MethodSame();
productA.MethodDiff();
Console.ReadLine();
}
}
}
运行结果:
通过更改App.config中如下行的代码,即可调用不同的产品。
<add key="Product" value="B" />
四、简化简单工厂模式【将抽象产品类和工厂类合并】
有时为了简化简单工厂模式,可以将抽象产品类和工厂类合并,将静态工厂方法移到抽象产品类中,根据不同的参数创建不同类型的产品子类对象,这种方法在很多类库和框架中也广泛存在。
实例【配置文件 + 合共抽象产品类和工厂类】:
using System;
using System.Configuration;
namespace ConsoleApp1
{
/// <summary>
/// 具体工厂类A
/// </summary>
public class ConcreteProduceA : Product
{
// 实现业务方法
public override void MethodDiff()
{
Console.WriteLine("具体产品A处理业务方法");
}
}
/// <summary>
/// 具体工厂类B
/// </summary>
public class ConcreteProduceB : Product
{
// 实现业务方法
public override void MethodDiff()
{
Console.WriteLine("具体产品B处理业务方法");
}
}
/// <summary>
/// 合共抽象产品类和工厂类
/// </summary>
public abstract class Product
{
// 静态工厂方法
public static Product GetProduct(string arg)
{
Product product = null;
switch (arg)
{
case "A":
product = new ConcreteProduceA();
break;
case "B":
product = new ConcreteProduceB();
break;
default:
throw new ArgumentException(message: "Invalid arg value");//If the arg is not "A" or "B",throw an exception.
}
return product;
}
// 所有产品类的公共业务方法
public void MethodSame()
{
Console.WriteLine("处理公共业务方法");
}
// 声明抽象业务方法
public abstract void MethodDiff();
}
class Program
{
static void Main(string[] args)
{
//客户端调用
var type = ConfigurationManager.AppSettings["product"];
Product productA = Product.GetProduct(type);
productA.MethodSame();
productA.MethodDiff();
Console.ReadLine();
}
}
}
以上的核心代码:
/// <summary>
/// 合共抽象产品类和工厂类
/// </summary>
public abstract class Product
{
// 静态工厂方法
public static Product GetProduct(string arg)
{
Product product = null;
switch (arg)
{
case "A":
product = new ConcreteProduceA();
break;
case "B":
product = new ConcreteProduceB();
break;
default:
throw new ArgumentException(message: "Invalid arg value");//If the arg is not "A" or "B",throw an exception.
}
return product;
}
// 所有产品类的公共业务方法
public void MethodSame()
{
Console.WriteLine("处理公共业务方法");
}
// 声明抽象业务方法
public abstract void MethodDiff();
}
五、简单工厂模式的优缺点与适用环境
简单工厂的优点
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
- 客户端无须知道所创建的具体产品类的类名,只需知道具体产品类所对应的参数即可。
- 通过引入配置文件,可以在不修改任何客户端代码的情况下,更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
简单工厂模式的缺点
- 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
- 使用简单工厂模式势必会增加系统中类的个数,增加了系统的复杂度和理解难度。
- 系统扩展难度,一旦添加新产品不得不修改工厂逻辑,在产品类型加多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展与维护。
- 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。
简单工厂模式的适用环境【主要知道!】
- 工厂类负责创建的对象比较少。
- 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。
参考文章:
https://blog.51cto.com/u_11071029/5072340
https://blog.csdn.net/baidu_35536188/article/details/109575787
https://www.cnblogs.com/PatrickLiu/p/7551373.html