工厂模式的核心价值与基本思想
在常规的C#开发中,如果我们直接在业务代码里使用new关键字创建对象,比如要创建一个日志处理对象,可能会写var logger = new FileLogger();,这种写法的问题在于业务代码和具体的FileLogger类强耦合。如果后续需要把日志存储方式换成数据库存储,所有写了new FileLogger()的地方都要改成new DatabaseLogger(),不仅修改成本高,还容易遗漏修改点导致线上问题。
工厂模式就是为了解决这类对象创建耦合问题而出现的,它的核心思想是将对象的创建逻辑从业务代码中剥离,放到专门的工厂类中统一管理。调用方只需要告诉工厂自己需要什么类型的对象,工厂负责完成具体的实例化过程,调用方不需要知道对象的具体类名、构造参数等细节。这样的设计符合面向对象设计原则中的开闭原则,新增对象类型时只需要扩展工厂逻辑,不需要修改已有的业务调用代码。
从结构上看,工厂模式通常会包含三个核心角色:抽象产品角色,也就是所有需要创建的对象共同实现的接口或者继承的抽象类;具体产品角色,也就是实际要被创建的对象类,实现抽象产品定义的规范;工厂角色,负责根据调用方的需求返回对应的具体产品实例。这种分层结构让整个对象创建流程的职责划分非常清晰,也方便后续的维护和扩展。

简单工厂模式的实现与适用场景
简单工厂模式是工厂模式中最基础的实现形式,它通常会在一个工厂类中通过条件判断(比如switch或者if-else)来决定返回哪一种具体产品实例。这种模式适合产品类型数量固定、不会频繁新增的场景,实现起来简单直接,不需要定义过多的接口和类。
我们可以先定义一个日志的抽象产品接口,所有的日志类都实现这个接口:
// 抽象产品:日志接口
public interface ILogger
{
void Log(string message);
}
// 具体产品:文件日志
public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"写入文件日志:{message}");
}
}
// 具体产品:控制台日志
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"输出控制台日志:{message}");
}
}
接下来实现简单工厂类,通过一个参数来区分要创建的产品类型:
// 简单工厂类
public class SimpleLoggerFactory
{
public static ILogger CreateLogger(string loggerType)
{
switch (loggerType.ToLower())
{
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
default:
throw new ArgumentException("不支持的日志类型");
}
}
}
调用方的使用方式会非常简洁,不需要关心具体的日志类实现:
class Program
{
static void Main(string[] args)
{
ILogger logger = SimpleLoggerFactory.CreateLogger("file");
logger.Log("这是一条测试日志");
}
}
简单工厂模式的优点是结构非常简单,对于小型项目或者产品类型固定的场景来说,开发成本很低,调用方使用起来也很方便。但它也有明显的缺点:如果后续要新增一个数据库日志类型,就需要修改SimpleLoggerFactory里的switch逻辑,违反了开闭原则,而且当产品类型越来越多时,工厂类里的条件判断会越来越臃肿,可维护性会下降。
工厂方法模式的实现与扩展优势
工厂方法模式是对简单工厂模式的改进,它不再把所有的对象创建逻辑都放到一个工厂类里,而是定义一个抽象的工厂接口,每个具体产品都对应一个具体的工厂类,每个具体工厂只负责创建对应的产品实例。这样当需要新增产品类型时,只需要新增对应的具体产品类和具体工厂类,不需要修改已有的工厂逻辑,完全符合开闭原则。
首先还是保留之前的ILogger抽象产品接口和对应的具体产品类,然后定义抽象的工厂接口:
// 抽象工厂接口
public interface ILoggerFactory
{
ILogger CreateLogger();
}
// 文件日志对应的具体工厂
public class FileLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
return new FileLogger();
}
}
// 控制台日志对应的具体工厂
public class ConsoleLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
return new ConsoleLogger();
}
}
调用方在使用的时候,需要先确定要使用的工厂类型,再通过工厂创建对象:
class Program
{
static void Main(string[] args)
{
// 使用文件日志工厂
ILoggerFactory factory = new FileLoggerFactory();
ILogger logger = factory.CreateLogger();
logger.Log("工厂方法模式测试日志");
}
}
如果后续要新增数据库日志类型,只需要新增DatabaseLogger类和DatabaseLoggerFactory类,不需要修改任何已有的工厂和产品代码:
// 新增具体产品
public class DatabaseLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"写入数据库日志:{message}");
}
}
// 新增具体工厂
public class DatabaseLoggerFactory : ILoggerFactory
{
public ILogger CreateLogger()
{
return new DatabaseLogger();
}
}
工厂方法模式的优点是扩展性非常好,完全符合开闭原则,每个工厂类的职责单一,符合单一职责原则,代码结构也更清晰。缺点是需要定义的类会变多,每增加一个产品类型就要增加一对产品和工厂类,对于产品类型很少的场景来说,会比简单工厂模式更繁琐。这种模式适合产品类型会频繁扩展、需要长期维护的项目场景。
抽象工厂模式的实现与产品族场景适配
抽象工厂模式是工厂模式中最高级的实现形式,它主要用来应对产品族的创建场景。所谓产品族,就是一组相关的产品,比如不同操作系统下的UI组件,Windows系统下有Windows按钮、Windows文本框,Mac系统下有Mac按钮、Mac文本框,按钮和文本框就属于同一个产品族的不同产品。抽象工厂会定义创建产品族中所有产品的抽象方法,每个具体工厂负责创建一个产品族下的所有产品。
我们以一个跨平台的UI组件创建场景为例,先定义两个抽象产品:按钮和文本框:
// 抽象产品:按钮
public interface IButton
{
void Render();
}
// 抽象产品:文本框
public interface ITextBox
{
void Render();
}
// Windows系统的具体按钮
public class WindowsButton : IButton
{
public void Render()
{
Console.WriteLine("渲染Windows风格按钮");
}
}
// Windows系统的具体文本框
public class WindowsTextBox : ITextBox
{
public void Render()
{
Console.WriteLine("渲染Windows风格文本框");
}
}
// Mac系统的具体按钮
public class MacButton : IButton
{
public void Render()
{
Console.WriteLine("渲染Mac风格按钮");
}
}
// Mac系统的具体文本框
public class MacTextBox : ITextBox
{
public void Render()
{
Console.WriteLine("渲染Mac风格文本框");
}
}
接下来定义抽象工厂接口,里面包含创建产品族中所有产品的抽象方法:
// 抽象工厂接口,定义创建产品族所有产品的方法
public interface IGUIFactory
{
IButton CreateButton();
ITextBox CreateTextBox();
}
// Windows系统对应的具体工厂,创建Windows产品族的所有产品
public class WindowsGUIFactory : IGUIFactory
{
public IButton CreateButton()
{
return new WindowsButton();
}
public ITextBox CreateTextBox()
{
return new WindowsTextBox();
}
}
// Mac系统对应的具体工厂,创建Mac产品族的所有产品
public class MacGUIFactory : IGUIFactory
{
public IButton CreateButton()
{
return new MacButton();
}
public ITextBox CreateTextBox()
{
return new MacTextBox();
}
}
调用方只需要选择对应的工厂,就可以创建出同一产品族下的所有组件,不需要关心具体产品的实现:
class Program
{
static void Main(string[] args)
{
// 假设当前运行在Windows环境,使用Windows工厂
IGUIFactory guiFactory = new WindowsGUIFactory();
IButton button = guiFactory.CreateButton();
ITextBox textBox = guiFactory.CreateTextBox();
button.Render();
textBox.Render();
}
}
抽象工厂模式的优点是可以保证创建出来的产品都属于同一个产品族,不会出现混搭的问题,比如不会出现Windows按钮搭配Mac文本框的情况,对于需要创建一组相关对象的场景来说非常合适。它的缺点是如果需要在产品族中新增一个产品类型,比如新增一个下拉框IComboBox,就需要修改抽象工厂接口和所有具体工厂类,违反了开闭原则,扩展性相对工厂方法模式会弱一些。这种模式适合产品族结构固定、不需要频繁新增产品类型的场景。
三种工厂模式的选择建议
在实际的C#项目开发中,我们不需要盲目追求复杂的实现,而是要根据具体的业务场景选择合适的工厂模式。如果项目规模很小,产品类型固定且不会扩展,简单工厂模式是最优选择,开发效率高,代码量少。如果项目需要长期维护,产品类型会不断新增,那么工厂方法模式更合适,它的扩展性更好,也符合面向对象的设计原则。
如果业务场景是需要创建一组相关的产品,也就是存在产品族的概念,那么抽象工厂模式就是最合适的选择,它可以保证产品的一致性,避免不同产品族的组件混用导致的问题。另外需要注意,工厂模式并不是越多越好,如果对象创建逻辑非常简单,而且不会发生变化,直接使用new关键字实例化反而更清晰,不需要为了用设计模式而用设计模式,过度设计反而会增加代码的复杂度。
在实际落地的时候,我们还可以结合依赖注入容器来使用工厂模式,比如把工厂类注册到.NET Core的依赖注入容器中,调用方通过构造函数注入的方式获取工厂实例,这样可以进一步降低代码之间的耦合度,也让整个对象的创建和管理流程更加统一规范。很多成熟的.NET框架也都内置了类似工厂模式的对象创建机制,开发者可以在理解原理的基础上灵活运用,提升项目的代码质量。