C# 设计模式——工厂模式

工厂模式

在C#中,工厂设计模式(Factory Pattern) 是一种创建型设计模式,核心目的是封装对象的创建过程,将对象的"使用"与"创建"解耦,让客户端无需直接依赖具体类,而是通过"工厂"间接获取对象。这种模式能提高代码的灵活性、可维护性和扩展性,尤其适合需要频繁创建相似类型对象的场景。

工厂模式通常分为三种形式,从简单到复杂依次为:简单工厂模式工厂方法模式抽象工厂模式。下面分别详解:

一、简单工厂模式(Simple Factory)

核心思想 :由一个统一的工厂类根据输入参数,决定创建哪种具体产品的实例。

(注:简单工厂不算"标准"设计模式,但应用广泛,是理解其他工厂模式的基础。)

结构
  • 抽象产品(Product):定义所有具体产品的公共接口(或抽象类)。
  • 具体产品(Concrete Product):实现抽象产品接口,是工厂的创建目标。
  • 工厂(Factory):提供静态方法(或实例方法),根据参数创建并返回具体产品实例。
示例:创建不同类型的计算器

假设需要实现加法、减法计算器,用简单工厂统一创建:

csharp 复制代码
// 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)); // 8

        ICalculator subCalc = CalculatorFactory.CreateCalculator("-");
        Console.WriteLine(subCalc.Calculate(5, 3)); // 2
    }
}
优缺点
  • 优点:客户端无需知道具体产品类名,只需通过参数获取对象,简化了对象创建。
  • 缺点 :若新增产品(如乘法计算器),需修改工厂类的CreateCalculator方法,违反"开闭原则"(对扩展开放,对修改关闭),且工厂类职责过重,不易维护。

二、工厂方法模式(Factory Method)

核心思想:将工厂抽象化,每个具体产品对应一个具体工厂,由具体工厂负责创建对应的产品。客户端通过调用具体工厂的方法获取产品,新增产品时只需新增对应的工厂,无需修改原有代码。

结构
  • 抽象产品(Product):所有具体产品的公共接口。
  • 具体产品(Concrete Product):实现抽象产品。
  • 抽象工厂(Factory):定义创建产品的接口(抽象方法)。
  • 具体工厂(Concrete Factory):实现抽象工厂接口,创建对应的具体产品。
示例:日志记录器(文件日志、数据库日志)

假设需要支持"文件日志"和"数据库日志",且未来可能扩展更多日志类型:

csharp 复制代码
// 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)只需新增对应的ConsoleLoggerFactoryConsoleLogger,无需修改原有代码;客户端完全依赖抽象,降低耦合。
  • 缺点:每增加一个产品,需同时增加一个具体工厂,导致类数量增多,系统复杂度上升。

三、抽象工厂模式(Abstract Factory)

核心思想 :用于创建一系列相互关联或相互依赖的产品族(而非单一产品)。抽象工厂定义创建多个产品的接口,具体工厂实现该接口,负责创建对应系列的所有产品。

结构
  • 抽象产品族(多个Product接口):如"按钮""文本框"属于UI控件产品族。
  • 具体产品(Concrete Product):每个产品族下的具体实现(如"Windows按钮""Mac按钮")。
  • 抽象工厂(Abstract Factory):定义创建产品族中所有产品的接口(多个工厂方法)。
  • 具体工厂(Concrete Factory):实现抽象工厂,创建某一系列的所有产品(如"Windows控件工厂"创建Windows按钮和文本框)。
示例:跨平台UI控件(Windows和Mac系统的按钮、文本框)

假设需要为Windows和Mac系统创建配套的按钮和文本框(同一系统的控件风格一致):

csharp 复制代码
// 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)。

示例:通用日志工厂(创建不同类型的日志记录器)

假设需要创建FileLoggerDatabaseLoggerConsoleLogger等日志记录器,且所有日志都实现ILogger接口:

csharp 复制代码
// 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,完全符合"开闭原则"。

二、工厂方法 + 泛型:简化具体工厂

工厂方法模式的痛点是"每新增一个产品需对应新增一个具体工厂类",导致类数量爆炸。泛型可以通过一个泛型具体工厂替代多个具体工厂,大幅减少类数量。

示例:泛型日志工厂(替代多个具体工厂)
csharp 复制代码
// 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("数据库日志测试"); // [数据库日志] 数据库日志测试
    }
}
优势
  • 原来需要FileLoggerFactoryDatabaseLoggerFactory等多个类,现在通过一个GenericLoggerFactory<T>即可覆盖所有产品,减少类数量。
  • 新增产品时,只需创建产品类(如NetworkLogger),直接通过GenericLoggerFactory<NetworkLogger>使用,无需新增工厂类。

三、抽象工厂 + 泛型:处理产品族

抽象工厂用于创建"产品族"(如Windows控件族、Mac控件族),泛型可简化产品族的创建逻辑,避免为每个产品族编写大量重复的具体工厂代码。

示例:跨平台UI控件工厂(泛型处理控件族)

假设需要创建"按钮(IButton)"和"文本框(ITextBox)"组成的UI控件族,支持Windows和Mac平台:

csharp 复制代码
// 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>替代了WindowsUiFactoryMacUiFactory,无需为每个产品族编写单独的工厂类。
  • 产品族的扩展更灵活:若新增Linux控件族,只需创建LinuxButtonLinuxTextBox,直接通过GenericUiFactory<LinuxButton, LinuxTextBox>使用。

四、泛型工厂的进阶:解决"无参构造函数"限制

上面的示例依赖new()约束(要求产品有公共无参构造函数),但实际开发中产品可能需要带参构造(如DatabaseLogger需要连接字符串)。此时可通过泛型方法参数委托传递构造参数:

示例:支持带参构造的泛型工厂
csharp 复制代码
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)] 连接成功
    }
}

总结:泛型 + 工厂模式的核心价值

  1. 减少代码冗余:用泛型参数替代硬编码的具体类型,避免大量重复的工厂类或判断逻辑。
  2. 增强扩展性:新增产品时,只需实现抽象产品接口,无需修改工厂代码(符合开闭原则)。
  3. 类型安全 :通过泛型约束(where)确保创建的产品符合规范,编译时即可发现类型错误。
  4. 灵活性:支持无参/带参构造函数,适配更多场景。

实际开发中,泛型工厂尤其适合"产品类型多、创建逻辑相似"的场景(如工具类、数据访问层、插件系统等),结合依赖注入框架(如Autofac)可进一步提升易用性。

相关推荐
VB.Net4 小时前
VB.Net循序渐进(第二版)
开发语言·.net·vb.net
时光追逐者4 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 58 期(2025年10.13-10.19)
微软·开源·c#·.net·.netcore
埃伊蟹黄面4 小时前
深入理解STL关联容器:map/multimap与set/multiset全解析
开发语言·c++
21号 14 小时前
4.客户端(Redis)
开发语言·数据库·github
消失的旧时光-19435 小时前
MQTT主题架构的艺术:从字符串拼接走向设计模式
设计模式
青牛科技-Allen5 小时前
7个常见的DFM问题及其对PCB制造的影响
开发语言·单片机·制造·usb麦克风·立体声录音笔
「QT(C++)开发工程师」5 小时前
C++语言编程规范-风格
linux·开发语言·c++·qt
hello kitty w5 小时前
Python学习(10) ----- Python的继承
开发语言·python·学习
newxtc5 小时前
【广州公共资源交易-注册安全分析报告-无验证方式导致安全隐患】
开发语言·selenium·安全·yolo