C# 设计模式——工厂模式
工厂模式
在C#中,工厂设计模式(Factory Pattern) 是一种创建型设计模式,核心目的是封装对象的创建过程,将对象的“使用”与“创建”解耦,让客户端无需直接依赖具体类,而是通过“工厂”间接获取对象。这种模式能提高代码的灵活性、可维护性和扩展性,尤其适合需要频繁创建相似类型对象的场景。
工厂模式通常分为三种形式,从简单到复杂依次为:简单工厂模式、工厂方法模式、抽象工厂模式。下面分别详解:
一、简单工厂模式(Simple Factory)
核心思想:由一个统一的工厂类根据输入参数,决定创建哪种具体产品的实例。
(注:简单工厂不算“标准”设计模式,但应用广泛,是理解其他工厂模式的基础。)
结构
- 抽象产品(Product):定义所有具体产品的公共接口(或抽象类)。
- 具体产品(Concrete Product):实现抽象产品接口,是工厂的创建目标。
- 工厂(Factory):提供静态方法(或实例方法),根据参数创建并返回具体产品实例。
示例:创建不同类型的计算器
假设需要实现加法、减法计算器,用简单工厂统一创建:
// 1. 抽象产品:计算器接口(定义计算方法)
public interface ICalculator
{int Calculate(int a, int b);
}// 2. 具体产品1:加法计算器
public class AddCalculator : ICalculator
{public int Calculate(int a, int b) => a + b;
}// 3. 具体产品2:减法计算器
public class SubtractCalculator : ICalculator
{public int Calculate(int a, int b) => a - b;
}// 4. 工厂类:根据参数创建具体计算器
public static class CalculatorFactory
{// 静态方法:输入操作类型,返回对应计算器public static ICalculator CreateCalculator(string operation){return operation.ToLower() switch{"+" => new AddCalculator(),"-" => new SubtractCalculator(),_ => throw new ArgumentException("无效的操作符")};}
}// 客户端使用
class Program
{static void Main(string[] args){// 客户端不直接new具体类,而是通过工厂获取ICalculator addCalc = CalculatorFactory.CreateCalculator("+");Console.WriteLine(addCalc.Calculate(5, 3)); // 8ICalculator subCalc = CalculatorFactory.CreateCalculator("-");Console.WriteLine(subCalc.Calculate(5, 3)); // 2}
}
优缺点
- 优点:客户端无需知道具体产品类名,只需通过参数获取对象,简化了对象创建。
- 缺点:若新增产品(如乘法计算器),需修改工厂类的
CreateCalculator
方法,违反“开闭原则”(对扩展开放,对修改关闭),且工厂类职责过重,不易维护。
二、工厂方法模式(Factory Method)
核心思想:将工厂抽象化,每个具体产品对应一个具体工厂,由具体工厂负责创建对应的产品。客户端通过调用具体工厂的方法获取产品,新增产品时只需新增对应的工厂,无需修改原有代码。
结构
- 抽象产品(Product):所有具体产品的公共接口。
- 具体产品(Concrete Product):实现抽象产品。
- 抽象工厂(Factory):定义创建产品的接口(抽象方法)。
- 具体工厂(Concrete Factory):实现抽象工厂接口,创建对应的具体产品。
示例:日志记录器(文件日志、数据库日志)
假设需要支持“文件日志”和“数据库日志”,且未来可能扩展更多日志类型:
// 1. 抽象产品:日志记录器接口
public interface ILogger
{void Log(string message);
}// 2. 具体产品1:文件日志
public class FileLogger : ILogger
{public void Log(string message){Console.WriteLine($"文件日志:{message}");}
}// 3. 具体产品2:数据库日志
public class DatabaseLogger : ILogger
{public void Log(string message){Console.WriteLine($"数据库日志:{message}");}
}// 4. 抽象工厂:日志工厂接口(定义创建日志的方法)
public interface ILoggerFactory
{ILogger CreateLogger(); // 工厂方法:返回日志实例
}// 5. 具体工厂1:文件日志工厂(创建FileLogger)
public class FileLoggerFactory : ILoggerFactory
{public ILogger CreateLogger() => new FileLogger();
}// 6. 具体工厂2:数据库日志工厂(创建DatabaseLogger)
public class DatabaseLoggerFactory : ILoggerFactory
{public ILogger CreateLogger() => new DatabaseLogger();
}// 客户端使用
class Program
{static void Main(string[] args){// 客户端依赖抽象工厂和抽象产品,不直接依赖具体类ILoggerFactory factory1 = new FileLoggerFactory();ILogger fileLogger = factory1.CreateLogger();fileLogger.Log("系统启动成功"); // 输出:文件日志:系统启动成功ILoggerFactory factory2 = new DatabaseLoggerFactory();ILogger dbLogger = factory2.CreateLogger();dbLogger.Log("用户登录"); // 输出:数据库日志:用户登录}
}
优缺点
- 优点:符合“开闭原则”,新增产品(如
ConsoleLogger
)只需新增对应的ConsoleLoggerFactory
和ConsoleLogger
,无需修改原有代码;客户端完全依赖抽象,降低耦合。 - 缺点:每增加一个产品,需同时增加一个具体工厂,导致类数量增多,系统复杂度上升。
三、抽象工厂模式(Abstract Factory)
核心思想:用于创建一系列相互关联或相互依赖的产品族(而非单一产品)。抽象工厂定义创建多个产品的接口,具体工厂实现该接口,负责创建对应系列的所有产品。
结构
- 抽象产品族(多个Product接口):如“按钮”“文本框”属于UI控件产品族。
- 具体产品(Concrete Product):每个产品族下的具体实现(如“Windows按钮”“Mac按钮”)。
- 抽象工厂(Abstract Factory):定义创建产品族中所有产品的接口(多个工厂方法)。
- 具体工厂(Concrete Factory):实现抽象工厂,创建某一系列的所有产品(如“Windows控件工厂”创建Windows按钮和文本框)。
示例:跨平台UI控件(Windows和Mac系统的按钮、文本框)
假设需要为Windows和Mac系统创建配套的按钮和文本框(同一系统的控件风格一致):
// 1. 抽象产品1:按钮接口
public interface IButton
{void Render();
}// 2. 抽象产品2:文本框接口
public interface ITextBox
{void Render();
}// 3. 具体产品1:Windows按钮
public class WindowsButton : IButton
{public void Render() => Console.WriteLine("渲染Windows风格按钮");
}// 4. 具体产品2:Windows文本框
public class WindowsTextBox : ITextBox
{public void Render() => Console.WriteLine("渲染Windows风格文本框");
}// 5. 具体产品3:Mac按钮
public class MacButton : IButton
{public void Render() => Console.WriteLine("渲染Mac风格按钮");
}// 6. 具体产品4:Mac文本框
public class MacTextBox : ITextBox
{public void Render() => Console.WriteLine("渲染Mac风格文本框");
}// 7. 抽象工厂:UI控件工厂(定义创建按钮和文本框的方法)
public interface IUiFactory
{IButton CreateButton();ITextBox CreateTextBox();
}// 8. 具体工厂1:Windows控件工厂(创建Windows系列控件)
public class WindowsUiFactory : IUiFactory
{public IButton CreateButton() => new WindowsButton();public ITextBox CreateTextBox() => new WindowsTextBox();
}// 9. 具体工厂2:Mac控件工厂(创建Mac系列控件)
public class MacUiFactory : IUiFactory
{public IButton CreateButton() => new MacButton();public ITextBox CreateTextBox() => new MacTextBox();
}// 客户端使用
class Program
{static void Main(string[] args){// 根据系统选择对应的工厂(实际场景可能从配置文件读取)IUiFactory factory = new WindowsUiFactory(); // 切换为MacUiFactory即可切换全系列控件// 创建配套控件IButton button = factory.CreateButton();ITextBox textBox = factory.CreateTextBox();// 渲染button.Render(); // 渲染Windows风格按钮textBox.Render(); // 渲染Windows风格文本框}
}
优缺点
- 优点:保证同一工厂创建的产品都是“配套”的(如Windows控件风格统一);客户端无需知道具体产品,只需依赖抽象工厂和产品接口。
- 缺点:扩展产品族困难(如新增“下拉框”控件),需修改抽象工厂和所有具体工厂的接口,违反“开闭原则”。
三种工厂模式的对比与选择
模式 | 核心场景 | 优点 | 缺点 |
---|---|---|---|
简单工厂 | 产品类型少且稳定,无需频繁扩展 | 实现简单,客户端调用方便 | 新增产品需修改工厂,违反开闭原则 |
工厂方法 | 产品类型可能扩展,每个产品独立创建 | 符合开闭原则,扩展灵活 | 类数量增多,系统复杂度上升 |
抽象工厂 | 需要创建一系列相互关联的产品族(如跨平台组件) | 保证产品一致性,适合产品族场景 | 扩展产品族困难,修改成本高 |
总结
工厂模式的核心是**“封装对象创建”**,通过引入工厂层隔离客户端与具体产品的依赖,使系统更易扩展和维护。在C#开发中:
- 简单场景(如工具类创建)用简单工厂;
- 单一产品扩展(如日志、计算器)用工厂方法;
- 多产品族场景(如跨平台组件、数据库驱动套件)用抽象工厂。
泛型+工厂
在C#中,将工厂模式与泛型结合,可以进一步简化代码、提高灵活性,并增强类型安全。泛型的“类型参数化”特性能够避免工厂模式中大量重复的具体工厂类,同时让工厂更通用——无需硬编码具体产品类型,而是通过泛型参数动态指定要创建的产品。
核心思路
泛型工厂的核心是:通过泛型类型参数替代硬编码的具体产品类型,利用泛型约束(where
)限制产品必须符合抽象产品的规范,最终通过反射(如Activator.CreateInstance
)或委托动态创建产品实例。
这种结合既能保留工厂模式“解耦创建与使用”的优势,又能减少代码冗余(无需为每个产品编写单独的工厂类)。
一、简单工厂 + 泛型:通用产品创建器
简单工厂的痛点是“新增产品需修改工厂逻辑”,而泛型可以通过类型参数动态指定产品,彻底避免硬编码判断(如switch
/if
)。
示例:通用日志工厂(创建不同类型的日志记录器)
假设需要创建FileLogger
、DatabaseLogger
、ConsoleLogger
等日志记录器,且所有日志都实现ILogger
接口:
// 1. 抽象产品:日志接口
public interface ILogger
{void Log(string message);
}// 2. 具体产品:文件日志
public class FileLogger : ILogger
{public void Log(string message) => Console.WriteLine($"[文件日志] {message}");
}// 3. 具体产品:数据库日志
public class DatabaseLogger : ILogger
{public void Log(string message) => Console.WriteLine($"[数据库日志] {message}");
}// 4. 具体产品:控制台日志
public class ConsoleLogger : ILogger
{public void Log(string message) => Console.WriteLine($"[控制台日志] {message}");
}// 5. 泛型简单工厂:通过泛型参数创建具体产品
public static class GenericLoggerFactory
{// 泛型方法:创建T类型的日志实例(T必须实现ILogger,且有公共无参构造函数)public static T CreateLogger<T>() where T : ILogger, new(){// 利用new()约束直接创建实例(无需反射)return new T();}
}// 客户端使用
class Program
{static void Main(string[] args){// 通过泛型参数指定要创建的日志类型ILogger fileLogger = GenericLoggerFactory.CreateLogger<FileLogger>();fileLogger.Log("系统启动"); // [文件日志] 系统启动ILogger consoleLogger = GenericLoggerFactory.CreateLogger<ConsoleLogger>();consoleLogger.Log("用户操作"); // [控制台日志] 用户操作}
}
关键点解析
- 泛型约束:
where T : ILogger, new()
确保:T
必须是ILogger
的实现类(保证产品符合抽象规范);T
有公共无参构造函数(允许通过new T()
创建实例)。
- 扩展性:新增日志类型(如
NetworkLogger
)时,只需让其实现ILogger
并提供无参构造函数,无需修改GenericLoggerFactory
,完全符合“开闭原则”。
二、工厂方法 + 泛型:简化具体工厂
工厂方法模式的痛点是“每新增一个产品需对应新增一个具体工厂类”,导致类数量爆炸。泛型可以通过一个泛型具体工厂替代多个具体工厂,大幅减少类数量。
示例:泛型日志工厂(替代多个具体工厂)
// 1. 抽象产品:ILogger(同上)
// 2. 具体产品:FileLogger、DatabaseLogger等(同上)// 3. 抽象工厂:定义创建日志的接口
public interface ILoggerFactory
{ILogger CreateLogger();
}// 4. 泛型具体工厂:通过泛型参数指定产品类型,实现抽象工厂
public class GenericLoggerFactory<T> : ILoggerFactory where T : ILogger, new()
{public ILogger CreateLogger(){return new T(); // 创建T类型的日志实例}
}// 客户端使用
class Program
{static void Main(string[] args){// 泛型工厂替代了FileLoggerFactory、DatabaseLoggerFactory等ILoggerFactory fileFactory = new GenericLoggerFactory<FileLogger>();ILogger fileLogger = fileFactory.CreateLogger();fileLogger.Log("文件日志测试"); // [文件日志] 文件日志测试ILoggerFactory dbFactory = new GenericLoggerFactory<DatabaseLogger>();ILogger dbLogger = dbFactory.CreateLogger();dbLogger.Log("数据库日志测试"); // [数据库日志] 数据库日志测试}
}
优势
- 原来需要
FileLoggerFactory
、DatabaseLoggerFactory
等多个类,现在通过一个GenericLoggerFactory<T>
即可覆盖所有产品,减少类数量。 - 新增产品时,只需创建产品类(如
NetworkLogger
),直接通过GenericLoggerFactory<NetworkLogger>
使用,无需新增工厂类。
三、抽象工厂 + 泛型:处理产品族
抽象工厂用于创建“产品族”(如Windows控件族、Mac控件族),泛型可简化产品族的创建逻辑,避免为每个产品族编写大量重复的具体工厂代码。
示例:跨平台UI控件工厂(泛型处理控件族)
假设需要创建“按钮(IButton)”和“文本框(ITextBox)”组成的UI控件族,支持Windows和Mac平台:
// 1. 抽象产品1:按钮
public interface IButton { void Render(); }// 2. 抽象产品2:文本框
public interface ITextBox { void Render(); }// 3. Windows产品族
public class WindowsButton : IButton { public void Render() => Console.WriteLine("Windows按钮"); }
public class WindowsTextBox : ITextBox { public void Render() => Console.WriteLine("Windows文本框"); }// 4. Mac产品族
public class MacButton : IButton { public void Render() => Console.WriteLine("Mac按钮"); }
public class MacTextBox : ITextBox { public void Render() => Console.WriteLine("Mac文本框"); }// 5. 抽象工厂:定义创建产品族的接口
public interface IUiFactory
{IButton CreateButton();ITextBox CreateTextBox();
}// 6. 泛型具体工厂:通过两个泛型参数指定按钮和文本框类型(同属一个产品族)
public class GenericUiFactory<TButton, TTextBox> : IUiFactory where TButton : IButton, new()where TTextBox : ITextBox, new()
{public IButton CreateButton() => new TButton();public ITextBox CreateTextBox() => new TTextBox();
}// 客户端使用
class Program
{static void Main(string[] args){// Windows控件族工厂(按钮=WindowsButton,文本框=WindowsTextBox)IUiFactory windowsFactory = new GenericUiFactory<WindowsButton, WindowsTextBox>();windowsFactory.CreateButton().Render(); // Windows按钮windowsFactory.CreateTextBox().Render(); // Windows文本框// Mac控件族工厂(按钮=MacButton,文本框=MacTextBox)IUiFactory macFactory = new GenericUiFactory<MacButton, MacTextBox>();macFactory.CreateButton().Render(); // Mac按钮macFactory.CreateTextBox().Render(); // Mac文本框}
}
优势
- 用
GenericUiFactory<TButton, TTextBox>
替代了WindowsUiFactory
和MacUiFactory
,无需为每个产品族编写单独的工厂类。 - 产品族的扩展更灵活:若新增Linux控件族,只需创建
LinuxButton
和LinuxTextBox
,直接通过GenericUiFactory<LinuxButton, LinuxTextBox>
使用。
四、泛型工厂的进阶:解决“无参构造函数”限制
上面的示例依赖new()
约束(要求产品有公共无参构造函数),但实际开发中产品可能需要带参构造(如DatabaseLogger
需要连接字符串)。此时可通过泛型方法参数或委托传递构造参数:
示例:支持带参构造的泛型工厂
public class DatabaseLogger : ILogger
{private string _connectionString;// 带参构造函数(需要连接字符串)public DatabaseLogger(string connectionString){_connectionString = connectionString;}public void Log(string message) => Console.WriteLine($"[数据库日志({_connectionString})] {message}");
}// 支持带参构造的泛型工厂
public static class GenericLoggerFactory
{// 泛型方法:通过参数传递构造函数参数public static T CreateLogger<T>(params object[] parameters) where T : ILogger{// 用反射创建带参实例(绕过new()约束)return (T)Activator.CreateInstance(typeof(T), parameters);}
}// 客户端使用
class Program
{static void Main(string[] args){// 传递构造参数(连接字符串)ILogger dbLogger = GenericLoggerFactory.CreateLogger<DatabaseLogger>("Server=localhost;DB=LogDB");dbLogger.Log("连接成功"); // [数据库日志(Server=localhost;DB=LogDB)] 连接成功}
}
总结:泛型 + 工厂模式的核心价值
- 减少代码冗余:用泛型参数替代硬编码的具体类型,避免大量重复的工厂类或判断逻辑。
- 增强扩展性:新增产品时,只需实现抽象产品接口,无需修改工厂代码(符合开闭原则)。
- 类型安全:通过泛型约束(
where
)确保创建的产品符合规范,编译时即可发现类型错误。 - 灵活性:支持无参/带参构造函数,适配更多场景。
实际开发中,泛型工厂尤其适合“产品类型多、创建逻辑相似”的场景(如工具类、数据访问层、插件系统等),结合依赖注入框架(如Autofac)可进一步提升易用性。