本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:工厂方法模式是一种常用的创建型设计模式,在C#中通过接口抽象产品的创建过程,将具体实例化操作延迟到子类,实现代码解耦与高扩展性。文章详细介绍了该模式的定义、核心角色、C#实现示例、模式优势以及适用场景,并通过一个形状绘制的完整代码示例演示了其使用方式。通过学习该模式,开发者可以提升软件设计的抽象能力,增强项目的可维护性与可扩展性。
工厂方法模式

1. 工厂方法模式简介

工厂方法模式(Factory Method Pattern)是一种常用的对象创建型设计模式,属于GoF(Gang of Four)设计模式之一。它通过定义一个用于创建对象的接口,将实际对象的创建延迟到子类中完成,从而实现对对象创建过程的解耦。

与简单工厂模式相比,工厂方法模式更具扩展性和灵活性。简单工厂模式通常使用一个静态方法根据参数决定创建哪种类实例,而工厂方法模式则通过接口或抽象类定义工厂方法,具体产品由子类决定,这更符合开放封闭原则。

2. 产品接口/抽象类定义

在工厂方法模式中,产品接口或抽象类的定义是整个架构的核心。它不仅决定了具体产品类的设计方向,还为工厂方法提供了创建对象的契约。良好的接口设计能够提升系统的扩展性、可维护性与解耦程度。本章将从设计原则、通用行为定义、C#中的实现方式以及实践构建接口四个方面深入探讨。

2.1 抽象产品类的设计原则

抽象产品类是工厂方法模式中产品契约的基础,它通过接口或抽象类的形式定义产品族的公共行为。选择合适的抽象机制对于系统的灵活性和扩展性至关重要。

2.1.1 接口与抽象类的选择依据

在C#中,接口( interface )和抽象类( abstract class )都可以用于定义产品的契约,但它们各自适用于不同的场景:

特性 接口 抽象类
多重继承 ✅ 支持 ❌ 不支持
默认实现 ❌ 不支持(C# 8.0起支持默认实现) ✅ 支持
成员字段 ❌ 不支持 ✅ 支持
构造函数 ❌ 不支持 ✅ 支持
状态维护 ❌ 不支持 ✅ 支持

选择建议:

  • 当产品需要具备多个行为契约(如同时具备可移动和可绘制的特性),使用接口更合适。
  • 如果产品之间有共享的状态或行为逻辑,使用抽象类更合适。
  • 若需提供默认实现但又不希望暴露具体实现细节,C# 8.0及以后版本的接口默认方法是不错的选择。

2.1.2 抽象方法与公共行为的定义

在定义抽象产品时,关键在于提取产品族的 公共行为 ,并将其抽象为方法。例如,在一个图形绘图系统中,不同图形(圆形、矩形、三角形)都需要实现“绘制”这一行为:

public interface IShape
{
    void Draw();
}

或使用抽象类:

public abstract class Shape
{
    public abstract void Draw();
}

抽象方法确保了子类必须实现该行为,而抽象类还可以包含非抽象方法,用于封装通用逻辑:

public abstract class Shape
{
    public abstract void Draw();

    public void LogCreation()
    {
        Console.WriteLine($"Created a {this.GetType().Name}");
    }
}

这样,具体产品类在实现 Draw 方法的同时,还可以复用 LogCreation 这样的通用行为。

2.2 定义产品的通用行为

为了提升产品的扩展性与复用性,抽象产品需要定义通用行为,包括方法签名的设计和默认实现的规划。

2.2.1 设计可扩展的方法签名

在定义产品行为时,方法签名的设计应具有前瞻性,以适应未来可能出现的扩展需求。例如:

public interface IDocument
{
    void Render(string format);
}

此处的 Render 方法接受一个 format 参数,表示文档的渲染格式(如PDF、HTML等),这样的设计使得新增格式支持时无需修改接口本身,符合 开放封闭原则

2.2.2 抽象类中的默认实现与扩展方法

C# 3.0引入了扩展方法,允许在不修改原始类的情况下为接口或类添加新方法。结合抽象类的默认实现,可以构建出更灵活的契约结构。

例如:

public static class DocumentExtensions
{
    public static void Print(this IDocument document)
    {
        document.Render("PDF");
        Console.WriteLine("Document printed.");
    }
}

这样,任何实现了 IDocument 接口的类都可以调用 Print() 方法,而无需在接口中定义它。

抽象类还可以定义默认实现:

public abstract class Document
{
    public virtual void Render(string format)
    {
        Console.WriteLine($"Rendering document in {format} format.");
    }
}

子类可以选择重写该方法,也可以直接使用默认实现,增强了灵活性。

2.3 在C#中使用接口或抽象类实现产品契约

C#作为面向对象语言,提供了接口和抽象类两种机制来定义产品契约。它们各有优劣,在实际开发中应根据需求选择。

2.3.1 接口的多重继承优势

C#中类不支持多重继承,但接口支持。这使得产品可以通过组合多个接口实现更灵活的功能组合:

public interface IDrawable
{
    void Draw();
}

public interface IResizable
{
    void Resize(int width, int height);
}

public class Rectangle : IDrawable, IResizable
{
    public void Draw()
    {
        Console.WriteLine("Drawing a rectangle.");
    }

    public void Resize(int width, int height)
    {
        Console.WriteLine($"Resizing to {width}x{height}");
    }
}

通过这种方式, Rectangle 类具备了“绘制”和“调整大小”两种行为,而这两个行为分别定义在不同的接口中。

2.3.2 实际开发中抽象类的使用场景

抽象类更适合用于以下场景:

  • 产品族之间存在共享的状态或实现逻辑。
  • 需要定义构造函数以初始化共享资源。
  • 需要对方法进行部分实现,子类只需补充特定逻辑。

例如:

public abstract class Vehicle
{
    protected string Name;

    protected Vehicle(string name)
    {
        Name = name;
    }

    public abstract void StartEngine();

    public void LogStart()
    {
        Console.WriteLine($"{Name} engine started.");
    }
}

子类继承该抽象类后,可以自动获得 LogStart() 方法:

public class Car : Vehicle
{
    public Car(string name) : base(name) { }

    public override void StartEngine()
    {
        Console.WriteLine("Car engine started.");
    }
}

2.4 实践:构建一个基础产品接口

本节将通过一个实际的代码示例,展示如何构建一个基础产品接口,并在具体产品类中实现它。

2.4.1 定义基本功能方法

我们以一个日志记录系统为例,定义一个 ILogger 接口:

public interface ILogger
{
    void Log(string message);
}

这个接口定义了日志记录的最基本功能:记录一条消息。

2.4.2 接口在实际代码中的使用方式

接下来,我们实现两个具体的日志记录器:控制台日志记录器和文件日志记录器。

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine($"[Console] {message}");
    }
}

public class FileLogger : ILogger
{
    public void Log(string message)
    {
        File.AppendAllText("logfile.txt", $"[File] {message}\n");
    }
}

然后,我们可以通过工厂方法模式来创建这些具体的产品实例:

public interface ILoggerFactory
{
    ILogger CreateLogger();
}

public class ConsoleLoggerFactory : ILoggerFactory
{
    public ILogger CreateLogger()
    {
        return new ConsoleLogger();
    }
}

public class FileLoggerFactory : ILoggerFactory
{
    public ILogger CreateLogger()
    {
        return new FileLogger();
    }
}

最后,客户端代码如下:

class Program
{
    static void Main(string[] args)
    {
        ILoggerFactory factory = new ConsoleLoggerFactory();
        ILogger logger = factory.CreateLogger();
        logger.Log("This is a test log message.");
    }
}
代码逻辑分析:
  1. ILogger 接口定义了统一的日志记录行为。
  2. ConsoleLogger FileLogger 分别实现了接口,提供不同的日志记录方式。
  3. ILoggerFactory 接口定义了创建日志器的工厂方法。
  4. 具体工厂类如 ConsoleLoggerFactory 负责返回对应的具体产品实例。
  5. 客户端通过工厂创建日志器,完全解耦了具体产品类的实现。
参数说明:
  • message :日志内容,字符串类型。
  • 工厂方法返回的 ILogger 实例,其具体类型由工厂类决定。
扩展性说明:
  • 若需新增日志记录方式(如数据库日志),只需新增一个 ILogger 的实现类和对应的工厂类,无需修改已有代码。
  • 工厂方法的可扩展性保证了系统对“开放封闭原则”的遵循。

小结

本章从抽象产品类的设计原则入手,详细探讨了接口与抽象类的选择依据、通用行为的设计方式、C#中的实现机制以及一个完整的接口构建实践。通过合理的接口设计与抽象类的使用,我们可以为工厂方法模式构建出稳定、灵活、可扩展的产品契约体系,为后续的工厂实现与产品管理打下坚实基础。

下一章将继续深入,探讨具体产品类的实现方式及其扩展性设计策略。

3. 具体产品类实现

在工厂方法模式中,具体产品类是整个模式的核心组成部分之一。它们实现了由抽象产品类或接口定义的契约,并通过多态机制,为系统提供灵活的行为扩展能力。本章将深入探讨具体产品类的实现方式、设计原则、以及在实际项目中如何构建和管理多个具体产品类。

3.1 实现具体产品类

具体产品类负责实现抽象产品类或接口中定义的行为。它们是工厂方法模式中“产品”的最终体现,决定了系统运行时的行为表现。

3.1.1 实现接口或继承抽象类

在C#中,具体产品类可以 实现接口 ,也可以 继承抽象类 。两者的选择取决于具体的业务需求和设计意图。

实现方式 特点 适用场景
实现接口 多重继承、行为契约清晰 产品行为定义较为独立,多个类共享行为
继承抽象类 可以包含默认实现、状态共享 产品之间有共享逻辑或状态

例如,假设我们定义了一个产品接口 IProduct

public interface IProduct
{
    string GetName();
    void Execute();
}

我们可以创建一个具体产品类 ConcreteProductA 来实现它:

public class ConcreteProductA : IProduct
{
    public string GetName()
    {
        return "Product A";
    }

    public void Execute()
    {
        Console.WriteLine("Executing Product A");
    }
}

代码解析
- ConcreteProductA 实现了 IProduct 接口。
- GetName 方法返回产品名称。
- Execute 方法定义了该产品的具体行为。

3.1.2 各类产品的行为差异化设计

具体产品类之间的差异化设计是工厂方法模式灵活性的体现。不同产品类可以实现完全不同的行为逻辑,但都遵循统一的接口或抽象类。

例如,再定义一个 ConcreteProductB

public class ConcreteProductB : IProduct
{
    public string GetName()
    {
        return "Product B";
    }

    public void Execute()
    {
        Console.WriteLine("Executing Product B with enhanced features.");
    }
}

逻辑分析
- 虽然 ConcreteProductB ConcreteProductA 实现相同的接口,但其 Execute 方法实现了不同的行为。
- 这种差异性使得系统可以根据运行时需求选择合适的产品类。

通过这种方式,系统可以轻松地添加新的产品类,而不会影响已有代码的稳定性。

3.2 产品类的扩展性与可维护性

良好的设计应遵循 开放封闭原则(Open-Closed Principle) ,即对扩展开放,对修改关闭。具体产品类的设计也应遵循这一原则,确保系统在新增产品时无需修改已有代码。

3.2.1 产品类的开放封闭原则应用

我们可以通过接口或抽象类来定义产品契约,这样在新增产品时只需添加新的具体类,而无需修改已有工厂或客户端代码。

例如,新增一个 ConcreteProductC

public class ConcreteProductC : IProduct
{
    public string GetName()
    {
        return "Product C";
    }

    public void Execute()
    {
        Console.WriteLine("Executing Product C - High performance mode.");
    }
}

扩展说明
- 添加新类 ConcreteProductC ,无需修改任何已有类。
- 工厂方法可以根据配置或运行时参数动态创建该类的实例。

3.2.2 产品类之间的继承与组合策略

在复杂系统中,产品类之间可能存在继承关系或组合结构。例如,我们可以设计一个基础产品类,其他具体产品继承它并扩展功能。

public abstract class BaseProduct : IProduct
{
    public abstract string GetName();
    public virtual void Execute()
    {
        Console.WriteLine("Base product execution.");
    }
}

public class EnhancedProduct : BaseProduct
{
    public override string GetName()
    {
        return "Enhanced Product";
    }

    public override void Execute()
    {
        Console.WriteLine("Enhanced execution logic.");
    }
}

分析
- BaseProduct 提供了默认的 Execute 实现。
- EnhancedProduct 可以选择重写或使用默认实现。
- 这种方式适用于需要共享部分行为逻辑的产品。

此外,组合策略可以用于构建更复杂的产品结构,例如:

public class CompositeProduct : IProduct
{
    private List<IProduct> _products = new List<IProduct>();

    public void AddProduct(IProduct product)
    {
        _products.Add(product);
    }

    public string GetName()
    {
        return "Composite Product";
    }

    public void Execute()
    {
        foreach (var product in _products)
        {
            product.Execute();
        }
    }
}

逻辑说明
- CompositeProduct 组合多个 IProduct 对象。
- Execute 方法依次调用每个产品的执行逻辑。
- 这种策略适用于需要组合多个产品行为的场景。

3.3 实践:多个具体产品的实现案例

为了更好地理解具体产品类的设计与实现,我们通过一个模拟示例来展示多个产品的实现方式。

3.3.1 模拟不同类型产品的实现

我们模拟一个“数据处理器”系统,不同产品代表不同类型的处理器:

public interface IDataProcessor
{
    string Process(string input);
}

public class JsonProcessor : IDataProcessor
{
    public string Process(string input)
    {
        return $"JSON processed: {input.ToUpper()}";
    }
}

public class XmlProcessor : IDataProcessor
{
    public string Process(string input)
    {
        return $"<data>{input}</data>";
    }
}

public class TextProcessor : IDataProcessor
{
    public string Process(string input)
    {
        return input.Trim();
    }
}

逐行解析
- IDataProcessor 定义了统一的数据处理接口。
- JsonProcessor 实现 JSON 风格的处理。
- XmlProcessor 返回 XML 格式的结果。
- TextProcessor 进行文本清理。

3.3.2 产品类与业务逻辑的解耦设计

通过接口统一调用,业务逻辑无需关心具体实现,从而实现解耦。

例如,客户端调用方式如下:

public class DataClient
{
    private IDataProcessor _processor;

    public DataClient(IDataProcessor processor)
    {
        _processor = processor;
    }

    public void SetProcessor(IDataProcessor processor)
    {
        _processor = processor;
    }

    public string ProcessData(string input)
    {
        return _processor.Process(input);
    }
}

优势分析
- 客户端通过接口调用方法,不依赖具体类。
- 可以在运行时动态切换处理器。
- 符合依赖倒置原则(DIP)和控制反转(IoC)。

3.4 多态在产品类中的体现

多态是面向对象编程中的核心特性之一,也是工厂方法模式能够实现灵活性和扩展性的关键所在。

3.4.1 不同产品通过统一接口调用

具体产品类虽然实现方式不同,但它们都实现了相同的接口或继承了相同的抽象类,因此可以通过统一接口进行调用。

例如:

List<IDataProcessor> processors = new List<IDataProcessor>
{
    new JsonProcessor(),
    new XmlProcessor(),
    new TextProcessor()
};

foreach (var p in processors)
{
    Console.WriteLine(p.Process("test data"));
}

执行说明
- 所有处理器都实现了 IDataProcessor 接口。
- Process 方法在运行时根据实际类型动态绑定。

3.4.2 运行时动态绑定的实现机制

C# 使用 虚方法表(vtable) 机制来实现多态调用。当一个类继承并重写父类的虚方法时,运行时会根据对象的实际类型调用相应的方法。

例如:

classDiagram
    class IDataProcessor {
        <<interface>>
        +Process(string input) string
    }
    class JsonProcessor {
        +Process(string input) string
    }
    class XmlProcessor {
        +Process(string input) string
    }
    class TextProcessor {
        +Process(string input) string
    }

    IDataProcessor <|-- JsonProcessor
    IDataProcessor <|-- XmlProcessor
    IDataProcessor <|-- TextProcessor

流程说明
- IDataProcessor 是接口。
- 每个具体处理器实现接口方法。
- 在运行时,CLR 根据实际对象类型决定调用哪个 Process 方法。

这种机制使得系统在面对新增产品时具备极高的灵活性,无需修改调用逻辑即可支持新行为。

本章深入讲解了具体产品类的实现机制、设计原则、扩展方式以及多态在其中的作用。通过接口或抽象类实现产品契约,结合继承与组合策略,具体产品类能够在保证系统稳定性的前提下,实现灵活的行为扩展。同时,通过多态机制,系统可以在运行时动态选择产品行为,提升整体的可维护性和可扩展性。

4. 工厂接口定义

工厂接口是工厂方法模式的核心组成部分之一。通过定义统一的工厂接口,我们可以实现对产品创建逻辑的抽象与封装,使得客户端代码无需关心具体产品的实现细节,仅需通过工厂接口来获取所需的产品实例。本章将深入探讨工厂接口的设计思路、职责划分以及在C#中的具体实现方式,并通过代码示例展示如何构建一个可扩展、易于维护的工厂接口。

4.1 工厂接口的设计思路

4.1.1 工厂方法模式的核心接口设计

在工厂方法模式中, 工厂接口(Factory Interface) 是整个模式的基础抽象层,它定义了用于创建产品的方法,但并不负责具体实现。具体的产品创建逻辑由实现了该接口的具体工厂类完成。

这种设计模式的关键在于: 将对象的创建延迟到子类中进行 。这样做的好处是:客户端与具体产品之间实现了低耦合,同时为未来的扩展预留了接口。

在C#中,我们通常使用接口( interface )来定义工厂接口,也可以使用抽象类( abstract class ),但推荐使用接口以获得更高的灵活性和解耦能力。

示例:基础工厂接口定义
public interface IProductFactory
{
    IProduct CreateProduct();
}

上述代码定义了一个名为 IProductFactory 的工厂接口,其中包含一个 CreateProduct 方法,用于创建实现了 IProduct 接口的产品实例。

4.1.2 工厂接口与产品接口的协作关系

工厂接口和产品接口之间存在一种协作关系。工厂接口负责定义创建产品的契约,而产品接口则定义产品行为的契约。

  • 产品接口 定义产品应具备的行为,例如 IProduct 可能包含 GetName() Execute() 等方法。
  • 工厂接口 则定义创建产品的方法,通常返回一个实现了产品接口的对象。

这种设计模式实现了两个接口之间的松耦合:

  • 客户端代码只需依赖 IProductFactory IProduct ,而不需要知道具体的工厂或产品实现。
  • 当新增产品时,只需新增对应的工厂实现类,而无需修改已有代码。

4.2 工厂接口的职责划分

4.2.1 创建对象的统一入口

工厂接口的首要职责是提供一个 统一的对象创建入口 。通过这个入口,客户端可以以一致的方式获取不同种类的产品实例,而无需关心具体的创建逻辑。

classDiagram
    class IProductFactory {
        <<interface>>
        +CreateProduct(): IProduct
    }
    class ConcreteFactoryA {
        +CreateProduct(): IProduct
    }
    class ConcreteFactoryB {
        +CreateProduct(): IProduct
    }
    class IProduct {
        <<interface>>
        +GetName(): string
        +Execute(): void
    }

    IProductFactory <|.. ConcreteFactoryA
    IProductFactory <|.. ConcreteFactoryB
    ConcreteFactoryA --> IProduct : 返回 ProductA
    ConcreteFactoryB --> IProduct : 返回 ProductB

上述类图展示了工厂接口与具体工厂类、产品接口之间的关系。所有具体工厂类都实现了 IProductFactory 接口,并通过其 CreateProduct() 方法返回实现了 IProduct 的具体产品实例。

4.2.2 工厂接口的扩展与维护策略

工厂接口的设计应遵循 开放封闭原则 (Open-Closed Principle),即对扩展开放,对修改关闭。

扩展策略:
  • 新增产品类型时 ,无需修改已有工厂接口,只需添加新的工厂实现类即可。
  • 支持泛型工厂接口 ,提高代码复用性。
示例:泛型工厂接口定义
public interface IProductFactory<T> where T : IProduct
{
    T CreateProduct();
}

该接口通过泛型约束 T 必须实现 IProduct 接口,从而实现类型安全的工厂接口设计。

维护策略:
  • 避免接口污染 :工厂接口应保持简洁,仅定义创建产品的核心方法。
  • 使用依赖注入 :将具体工厂注入到使用方,避免硬编码依赖。

4.3 实践:设计工厂接口并定义创建方法

4.3.1 方法命名与返回类型设计

在设计工厂接口时,方法的命名和返回类型设计至关重要,它们直接影响代码的可读性和可维护性。

命名建议:
  • 使用动词+名词的组合,如 CreateProduct() MakeCar()
  • 若创建逻辑复杂,可考虑命名更具体的如 CreatePremiumProduct()
返回类型建议:
  • 返回类型应为抽象类型(接口或抽象类),而非具体类。
  • 推荐使用泛型接口以提升类型安全性。
示例:具体工厂接口命名与返回类型
public interface ICarFactory
{
    ICar CreateCar(); // 返回抽象类型
}

4.3.2 工厂接口在C#中的具体实现方式

在C#中,工厂接口的实现方式灵活多样,可以结合接口、抽象类、泛型、依赖注入等多种技术手段。

示例:完整工厂接口与实现类
// 产品接口
public interface IProduct
{
    string GetName();
    void Execute();
}

// 工厂接口
public interface IProductFactory
{
    IProduct CreateProduct();
}

// 具体产品类
public class ProductA : IProduct
{
    public string GetName() => "ProductA";

    public void Execute()
    {
        Console.WriteLine("Executing ProductA");
    }
}

// 具体工厂类
public class ProductAFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new ProductA();
    }
}
代码逻辑逐行分析:
  • 第1~5行 :定义产品接口 IProduct ,包含获取名称和执行操作的方法。
  • 第7~10行 :定义工厂接口 IProductFactory ,仅有一个创建产品的方法。
  • 第12~20行 :具体产品类 ProductA 实现了 IProduct 接口。
  • 第22~26行 :具体工厂类 ProductAFactory 实现了 IProductFactory 接口,并在 CreateProduct() 方法中返回 ProductA 实例。
客户端调用方式:
var factory = new ProductAFactory();
var product = factory.CreateProduct();
product.Execute(); // 输出:Executing ProductA
参数说明:
  • factory :工厂接口的实现类实例,用于创建产品。
  • product :通过工厂接口创建的具体产品实例,其类型为 IProduct
  • Execute() :调用产品接口定义的方法,运行时根据实际类型执行具体逻辑(多态体现)。

表格:工厂接口设计要素对比

设计要素 接口方式 抽象类方式 泛型接口方式
定义方式 interface abstract class interface<T>
多继承支持 ✅ 支持 ❌ 不支持 ✅ 支持
默认实现 ❌ 不支持 ✅ 支持 ❌ 不支持
类型安全性 ⚠️ 较弱 ⚠️ 较弱 ✅ 强
扩展性 ✅ 高 ⚠️ 中等 ✅ 高
适用场景 标准工厂接口设计 需要共享默认实现 需要类型安全的工厂设计

通过本章内容的详细分析与实践演示,我们不仅了解了工厂接口的设计思路和职责划分,还掌握了如何在C#中构建一个结构清晰、可扩展性强的工厂接口。下一章将继续深入讲解具体工厂类的实现机制与优化策略。

5. 具体工厂类实现

在工厂方法模式中, 具体工厂类 是实现工厂接口并负责创建具体产品对象的核心组件。这一章将深入探讨具体工厂类的实现机制、设计灵活性以及在实际项目中的应用方式。我们将从接口实现、动态创建、多工厂管理等角度进行分析,并结合 C# 的语法特性展示如何构建高效、可扩展的工厂类。

5.1 工厂类的实现机制

具体工厂类是工厂接口的具体实现,它承担着创建特定产品实例的职责。通过实现工厂接口,具体工厂类与产品类之间建立了一种松耦合的关系,使得系统具备良好的扩展性和维护性。

5.1.1 实现工厂接口并创建具体产品

在 C# 中,具体工厂类通过实现工厂接口来定义其行为。以下是一个示例:

// 工厂接口定义
public interface IProductFactory
{
    IProduct CreateProduct();
}

// 具体工厂类
public class ConcreteProductAFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductA();
    }
}

public class ConcreteProductBFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductB();
    }
}
代码分析:
  • IProductFactory 是一个通用的工厂接口,定义了 CreateProduct 方法,用于创建产品对象。
  • ConcreteProductAFactory ConcreteProductBFactory 是具体工厂类,分别用于创建不同的产品实例。
  • CreateProduct() 方法返回的是 IProduct 接口类型,实现了多态行为,允许调用者以统一方式操作不同产品。
参数说明:
  • IProduct :是抽象产品接口,所有具体产品都必须实现该接口。
  • ConcreteProductA ConcreteProductB :是具体产品类,实现了 IProduct 接口中的方法。

5.1.2 工厂类与产品类的绑定方式

具体工厂类与其创建的产品类之间存在绑定关系,这种绑定可以通过以下几种方式实现:

绑定方式 描述 适用场景
静态绑定 工厂类在编译时就确定了创建的产品类型 固定产品结构,不需运行时扩展
动态绑定(反射) 工厂类在运行时根据配置或参数动态创建产品 需要灵活扩展,支持插件式架构
工厂注册机制 多个工厂类注册到工厂集合中,由调用者选择 多产品线、多版本管理

5.2 工厂类的灵活性与可配置性

为了提高系统的可维护性和可扩展性,工厂类需要具备一定的灵活性和可配置性。这可以通过配置化、反射机制等方式实现。

5.2.1 配置化产品创建逻辑

通过读取配置文件(如 JSON、XML 或 appsettings.json),可以动态决定由哪个工厂创建哪个产品。以下是一个使用 JSON 配置的示例:

{
  "ProductType": "ConcreteProductA"
}
public class ConfigurableProductFactory : IProductFactory
{
    private readonly string _productType;

    public ConfigurableProductFactory(string productType)
    {
        _productType = productType;
    }

    public IProduct CreateProduct()
    {
        switch (_productType)
        {
            case "ConcreteProductA":
                return new ConcreteProductA();
            case "ConcreteProductB":
                return new ConcreteProductB();
            default:
                throw new ArgumentException("Unknown product type");
        }
    }
}
代码逻辑分析:
  • 通过构造函数传入配置的 productType ,决定创建哪种产品。
  • 使用 switch 判断创建具体产品实例。
  • 优点是灵活,缺点是耦合了配置与工厂类。

5.2.2 使用反射实现动态工厂

反射机制允许在运行时动态加载程序集并创建对象,从而实现高度解耦的工厂类。例如:

public class ReflectionProductFactory : IProductFactory
{
    private readonly string _typeName;

    public ReflectionProductFactory(string typeName)
    {
        _typeName = typeName;
    }

    public IProduct CreateProduct()
    {
        Type type = Type.GetType(_typeName);
        if (type == null || !typeof(IProduct).IsAssignableFrom(type))
        {
            throw new InvalidOperationException("Invalid product type");
        }

        return (IProduct)Activator.CreateInstance(type);
    }
}
代码逻辑分析:
  • Type.GetType :根据类型名称获取类型信息。
  • typeof(IProduct).IsAssignableFrom(type) :验证该类型是否实现了 IProduct 接口。
  • Activator.CreateInstance :动态创建实例。
  • 优点:支持运行时动态扩展,无需重新编译代码。
  • 缺点:性能稍差,类型检查需严谨。

5.3 实践:编写多个具体工厂类

在实际项目中,通常会有多个具体工厂类,用于创建不同系列的产品。这在构建 产品族 产品线 时尤为重要。

5.3.1 不同工厂对应不同产品系列

以一个电商系统为例,假设有两个产品系列: 国内商品 国际商品 ,各自有不同的工厂类:

// 国内商品工厂
public class DomesticProductFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new DomesticProduct();
    }
}

// 国际商品工厂
public class InternationalProductFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new InternationalProduct();
    }
}
产品类定义:
public class DomesticProduct : IProduct
{
    public void Display()
    {
        Console.WriteLine("This is a domestic product.");
    }
}

public class InternationalProduct : IProduct
{
    public void Display()
    {
        Console.WriteLine("This is an international product.");
    }
}
客户端调用:
public class Client
{
    private readonly IProductFactory _factory;

    public Client(IProductFactory factory)
    {
        _factory = factory;
    }

    public void Run()
    {
        IProduct product = _factory.CreateProduct();
        product.Display();
    }
}
代码执行流程图(mermaid):
graph TD
    A[Client] --> B[调用Run方法]
    B --> C[调用工厂CreateProduct]
    C --> D{工厂类型}
    D -->|DomesticProductFactory| E[创建DomesticProduct]
    D -->|InternationalProductFactory| F[创建InternationalProduct]
    E --> G[调用Display方法]
    F --> G
    G --> H[输出产品类型]

5.3.2 工厂类与客户端代码的交互方式

工厂类与客户端之间的交互方式决定了系统的解耦程度和扩展性。常见的交互方式包括:

方式 描述 优势
工厂注入 客户端通过依赖注入获取工厂实例 松耦合,易于测试和替换
工厂注册中心 所有工厂注册到中心容器中,由统一接口获取 统一管理,便于扩展
工厂策略模式组合 使用策略模式动态选择不同工厂 支持运行时动态切换

例如,使用策略模式组合工厂类:

public interface IFactoryStrategy
{
    IProductFactory GetFactory();
}

public class DomesticStrategy : IFactoryStrategy
{
    public IProductFactory GetFactory()
    {
        return new DomesticProductFactory();
    }
}

public class InternationalStrategy : IFactoryStrategy
{
    public IProductFactory GetFactory()
    {
        return new InternationalProductFactory();
    }
}

客户端使用策略选择工厂:

public class StrategyClient
{
    private readonly IFactoryStrategy _strategy;

    public StrategyClient(IFactoryStrategy strategy)
    {
        _strategy = strategy;
    }

    public void Run()
    {
        IProduct product = _strategy.GetFactory().CreateProduct();
        product.Display();
    }
}

小结

本章系统地讲解了 具体工厂类的实现机制 ,包括如何实现工厂接口、如何绑定产品类、如何提升工厂类的灵活性与可配置性。我们还通过多个实践案例,展示了如何构建多个具体工厂类,并与客户端进行交互。通过使用配置化、反射、策略模式等手段,可以显著提升工厂类的扩展性和灵活性,使其适应更复杂的业务场景。

在下一章中,我们将基于前面章节的内容,提供一个完整的 C# 工厂方法模式示例,涵盖从接口定义到完整实现的全过程。

6. C#中工厂方法模式完整示例代码

本章将围绕 工厂方法模式 在C#语言中的完整实现进行演示和讲解,涵盖从项目结构设计、类图依赖关系、接口定义到具体类实现的全流程。我们将通过一个控制台应用程序的完整代码示例,展示如何通过工厂方法模式实现对象创建的解耦,并通过代码分析与重构建议提升代码的可维护性与可读性。

6.1 示例项目结构与设计思路

在开始编写代码之前,我们需要明确项目的模块划分和各个类之间的依赖关系。

6.1.1 项目模块划分

我们的示例项目是一个控制台应用程序,主要包含以下模块:

  • IProduct.cs :定义产品的公共接口
  • ConcreteProductA.cs ConcreteProductB.cs :具体产品类,实现 IProduct 接口
  • IProductFactory.cs :定义工厂接口
  • ConcreteProductAFactory.cs ConcreteProductBFactory.cs :具体工厂类,实现 IProductFactory
  • Program.cs :程序入口,使用工厂创建产品并调用其方法

6.1.2 类之间的依赖关系图解

我们通过 mermaid 流程图来展示类之间的依赖关系:

classDiagram
    IProduct <|-- ConcreteProductA
    IProduct <|-- ConcreteProductB
    IProductFactory <|-- ConcreteProductAFactory
    IProductFactory <|-- ConcreteProductBFactory
    ConcreteProductAFactory --> ConcreteProductA : 创建
    ConcreteProductBFactory --> ConcreteProductB : 创建
    class IProduct {
        <<interface>>
        +void Use()
    }
    class ConcreteProductA {
        +void Use()
    }
    class ConcreteProductB {
        +void Use()
    }
    class IProductFactory {
        <<interface>>
        +IProduct CreateProduct()
    }
    class ConcreteProductAFactory {
        +IProduct CreateProduct()
    }
    class ConcreteProductBFactory {
        +IProduct CreateProduct()
    }

该图清晰地展示了:

  • IProduct 是产品的抽象接口;
  • ConcreteProductA ConcreteProductB 是具体产品类;
  • IProductFactory 是工厂接口;
  • ConcreteProductAFactory ConcreteProductBFactory 是具体工厂类;
  • 工厂类通过 CreateProduct() 方法创建对应的具体产品。

6.2 完整代码实现与运行演示

本节将逐步展示从接口定义到具体类实现的完整代码,并提供控制台输出示例。

6.2.1 从接口定义到具体实现的完整流程

IProduct.cs:产品接口定义
// IProduct.cs
public interface IProduct
{
    void Use();
}

逻辑分析:

  • 定义了一个 IProduct 接口,其中包含一个抽象方法 Use() ,用于表示产品的使用行为。
ConcreteProductA.cs:具体产品A
// ConcreteProductA.cs
public class ConcreteProductA : IProduct
{
    public void Use()
    {
        Console.WriteLine("Using Product A.");
    }
}

逻辑分析:

  • ConcreteProductA 实现了 IProduct 接口;
  • Use() 方法中输出了具体产品A的使用信息。
ConcreteProductB.cs:具体产品B
// ConcreteProductB.cs
public class ConcreteProductB : IProduct
{
    public void Use()
    {
        Console.WriteLine("Using Product B.");
    }
}

逻辑分析:

  • ConcreteProductB 实现了 IProduct 接口;
  • Use() 方法中输出了具体产品B的使用信息。
IProductFactory.cs:工厂接口定义
// IProductFactory.cs
public interface IProductFactory
{
    IProduct CreateProduct();
}

逻辑分析:

  • 定义了一个工厂接口 IProductFactory ,包含一个用于创建产品的方法 CreateProduct()
  • 该接口为工厂方法模式的核心部分,封装了对象的创建逻辑。
ConcreteProductAFactory.cs:具体工厂A
// ConcreteProductAFactory.cs
public class ConcreteProductAFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductA();
    }
}

逻辑分析:

  • ConcreteProductAFactory 实现了 IProductFactory 接口;
  • CreateProduct() 方法返回一个 ConcreteProductA 实例;
  • 通过这种方式,将产品A的创建逻辑封装在工厂中。
ConcreteProductBFactory.cs:具体工厂B
// ConcreteProductBFactory.cs
public class ConcreteProductBFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductB();
    }
}

逻辑分析:

  • ConcreteProductBFactory 实现了 IProductFactory 接口;
  • CreateProduct() 方法返回一个 ConcreteProductB 实例;
  • 将产品B的创建逻辑封装在工厂中。
Program.cs:主程序入口
// Program.cs
class Program
{
    static void Main(string[] args)
    {
        IProductFactory factoryA = new ConcreteProductAFactory();
        IProduct productA = factoryA.CreateProduct();
        productA.Use();  // 输出:Using Product A.

        IProductFactory factoryB = new ConcreteProductBFactory();
        IProduct productB = factoryB.CreateProduct();
        productB.Use();  // 输出:Using Product B.
    }
}

逻辑分析:

  • 主程序中,分别创建了两个具体工厂 ConcreteProductAFactory ConcreteProductBFactory
  • 通过工厂接口调用 CreateProduct() 方法,创建出对应的产品;
  • 产品通过统一的 Use() 接口执行各自的行为;
  • 客户端无需关心具体产品的实现细节,只依赖于工厂接口和产品接口,实现了解耦。

6.2.2 控制台输出示例与调试说明

输出结果:

Using Product A.
Using Product B.

调试说明:

  • 在调试器中可以观察到:
  • factoryA ConcreteProductAFactory 的实例;
  • productA ConcreteProductA 的实例;
  • factoryB ConcreteProductBFactory 的实例;
  • productB ConcreteProductB 的实例;
  • 每个产品实例调用的 Use() 方法都是其自身实现的版本;
  • 这体现了 多态 工厂方法模式 在运行时动态绑定的特性。

6.3 代码分析与重构建议

本节将分析上述示例代码中存在的潜在问题,并提出重构建议,以提升代码的可读性和可维护性。

6.3.1 常见代码问题与优化点

问题 说明 建议
类名重复 类名 ConcreteProductAFactory 等冗长,不易维护 使用更简洁的命名方式,如 ProductAFactory
硬编码工厂选择 示例中工厂的创建是硬编码的,不利于扩展 引入配置文件或反射机制实现工厂动态创建
缺乏异常处理 未对创建失败或空引用进行处理 添加空值判断和异常捕获机制
缺乏日志记录 缺少日志记录,不利于调试 引入日志框架(如 Serilog、NLog)记录关键信息

6.3.2 如何提升代码的可读性和可维护性

1. 重构命名,提高可读性

原始类名如 ConcreteProductAFactory 可以简化为:

public class ProductAFactory : IProductFactory
{
    public IProduct CreateProduct()
    {
        return new ProductA();
    }
}

这样更符合实际语义,也更简洁。

2. 引入配置化或反射机制,提升灵活性

可以将工厂的选择逻辑抽象为配置项,例如:

string factoryType = ConfigurationManager.AppSettings["ProductFactory"];
IProductFactory factory = (IProductFactory)Activator.CreateInstance(Type.GetType(factoryType));

这样,通过配置即可切换工厂类型,无需修改代码。

3. 添加日志记录功能

可以使用日志库(如 Microsoft.Extensions.Logging )增强代码的可观测性:

private readonly ILogger _logger;

public ProductAFactory(ILogger<ProductAFactory> logger)
{
    _logger = logger;
}

public IProduct CreateProduct()
{
    _logger.LogInformation("Creating Product A.");
    return new ProductA();
}
4. 异常处理机制

在创建产品时添加空值判断和异常处理:

public IProduct CreateProduct()
{
    try
    {
        IProduct product = new ProductA();
        if (product == null)
        {
            throw new InvalidOperationException("Product creation failed.");
        }
        return product;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error creating product.");
        throw;
    }
}
5. 使用依赖注入(DI)提升解耦性

在 ASP.NET Core 或其他支持 DI 的环境中,可以将工厂注册为服务:

services.AddSingleton<IProductFactory, ProductAFactory>();

然后在使用处通过构造函数注入:

public class ProductService
{
    private readonly IProductFactory _factory;

    public ProductService(IProductFactory factory)
    {
        _factory = factory;
    }

    public void Run()
    {
        var product = _factory.CreateProduct();
        product.Use();
    }
}

通过本章的完整示例,我们展示了工厂方法模式在 C# 中的实现方式,并通过类图、代码结构、运行演示及重构建议,深入分析了其应用场景与优化方向。下一章将继续探讨如何将工厂方法模式与其他设计模式结合使用,以应对更复杂的软件设计挑战。

7. 封装对象创建逻辑实战技巧

7.1 封装创建逻辑的设计思想

工厂方法模式的核心价值之一在于其能够 隐藏对象创建的复杂性 。在大型系统中,对象的创建往往涉及多个参数、依赖注入、配置读取、甚至跨平台资源加载。通过将这些复杂逻辑封装在工厂类中,客户端代码无需关心具体实现,只需通过统一接口获取所需对象。

public interface IProduct
{
    void Use();
}

public class ConcreteProductA : IProduct
{
    public void Use()
    {
        Console.WriteLine("Using Product A");
    }
}

public class ConcreteProductB : IProduct
{
    public void Use()
    {
        Console.WriteLine("Using Product B");
    }
}

如上代码, ConcreteProductA ConcreteProductB 是两个具体产品类,客户端如果直接实例化它们,将导致代码耦合。而通过封装到工厂中:

public interface IFactory
{
    IProduct CreateProduct();
}

public class ProductAFactory : IFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductA();
    }
}

public class ProductBFactory : IFactory
{
    public IProduct CreateProduct()
    {
        return new ConcreteProductB();
    }
}

客户端代码通过 IFactory 接口调用,实现了解耦,如下所示:

class Program
{
    static void Main(string[] args)
    {
        IFactory factory = new ProductAFactory();
        IProduct product = factory.CreateProduct();
        product.Use();  // 输出:Using Product A
    }
}

这种方式不仅提高了代码的可维护性,还为后续扩展预留了空间。

7.2 工厂方法模式与其他设计模式的结合

7.2.1 工厂方法与策略模式的融合

策略模式允许在运行时切换算法行为,而工厂方法可以动态创建不同的策略类实例,两者结合可以实现灵活的算法切换机制。

例如:

public interface IPaymentStrategy
{
    void Pay(decimal amount);
}

public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount} via Credit Card");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount} via PayPal");
    }
}

结合工厂方法:

public interface IPaymentFactory
{
    IPaymentStrategy CreatePayment();
}

public class CreditCardFactory : IPaymentFactory
{
    public IPaymentStrategy CreatePayment()
    {
        return new CreditCardPayment();
    }
}

public class PayPalFactory : IPaymentFactory
{
    public IPaymentStrategy CreatePayment()
    {
        return new PayPalPayment();
    }
}

客户端使用方式:

class PaymentContext
{
    private IPaymentStrategy _strategy;

    public PaymentContext(IPaymentFactory factory)
    {
        _strategy = factory.CreatePayment();
    }

    public void ExecutePayment(decimal amount)
    {
        _strategy.Pay(amount);
    }
}

// 使用示例
var context = new PaymentContext(new PayPalFactory());
context.ExecutePayment(100.0m);  // 输出:Paid 100.0 via PayPal

7.2.2 工厂方法与依赖注入的协同使用

在现代C#项目中,依赖注入(DI)广泛应用于解耦服务与实现。工厂方法可以作为 DI 容器的补充,用于创建复杂对象或动态决定实例类型。

以 ASP.NET Core 为例,在 Startup.cs 中注册工厂接口:

services.AddTransient<IFactory, ProductAFactory>();

然后在控制器中注入使用:

[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
    private readonly IFactory _factory;

    public ProductController(IFactory factory)
    {
        _factory = factory;
    }

    [HttpGet]
    public IActionResult Get()
    {
        var product = _factory.CreateProduct();
        product.Use();
        return Ok();
    }
}

这样不仅保持了代码的整洁,还利用了框架的能力,实现了高度解耦。

7.3 高级技巧:动态工厂与插件式架构

7.3.1 利用反射实现运行时动态加载

在一些插件化系统中,具体的产品类可能在运行时动态加载,这时可以使用反射机制动态创建实例。

public class DynamicFactory : IFactory
{
    private readonly string _typeName;

    public DynamicFactory(string typeName)
    {
        _typeName = typeName;
    }

    public IProduct CreateProduct()
    {
        Type type = Type.GetType(_typeName);
        if (type == null)
            throw new ArgumentException("Invalid type name");

        return (IProduct)Activator.CreateInstance(type);
    }
}

客户端调用:

string typeName = "MyNamespace.ConcreteProductB, MyAssembly";
var factory = new DynamicFactory(typeName);
var product = factory.CreateProduct();
product.Use();  // 输出:Using Product B

这种方式支持在不重新编译主程序的情况下,动态加载和使用新的产品类,非常适合插件架构。

7.3.2 构建支持插件的工厂架构

结合配置文件与反射,可以构建一个支持插件的工厂架构。例如,在 appsettings.json 中配置产品类型:

{
  "ProductFactory": {
    "DefaultProductType": "MyNamespace.ConcreteProductB, MyAssembly"
  }
}

在代码中读取并创建:

public class ConfigurableFactory : IFactory
{
    private readonly IConfiguration _config;

    public ConfigurableFactory(IConfiguration config)
    {
        _config = config;
    }

    public IProduct CreateProduct()
    {
        string typeName = _config["ProductFactory:DefaultProductType"];
        Type type = Type.GetType(typeName);
        return (IProduct)Activator.CreateInstance(type);
    }
}

这种方式为系统提供了高度的灵活性和可扩展性。

7.4 工厂方法模式在真实项目中的应用案例

7.4.1 工厂方法在大型系统中的实践

在企业级系统中,例如电商平台,订单的创建可能涉及不同支付方式、物流方式、发票类型等。使用工厂方法可以统一创建订单对象,屏蔽底层实现差异。

例如:

public interface IOrderFactory
{
    IOrder CreateOrder();
}

public class DomesticOrderFactory : IOrderFactory
{
    public IOrder CreateOrder()
    {
        return new DomesticOrder();
    }
}

public class InternationalOrderFactory : IOrderFactory
{
    public IOrder CreateOrder()
    {
        return new InternationalOrder();
    }
}

每种订单类型可以封装不同的业务逻辑,比如税费计算、物流配送等。

7.4.2 如何在实际开发中推广使用该模式

  • 封装复杂构造逻辑 :将构造参数、依赖注入、配置读取等封装在工厂内部。
  • 提高可测试性 :通过接口隔离创建逻辑,便于单元测试。
  • 结合配置中心 :通过配置决定创建哪个具体类,提高系统的灵活性。
  • 文档与规范 :制定统一的工厂接口规范,便于团队协作与维护。

工厂方法模式不仅是一个创建型设计模式,更是实现系统解耦、提升可维护性的关键工具。在真实项目中合理运用,能够显著提升系统的可扩展性和健壮性。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:工厂方法模式是一种常用的创建型设计模式,在C#中通过接口抽象产品的创建过程,将具体实例化操作延迟到子类,实现代码解耦与高扩展性。文章详细介绍了该模式的定义、核心角色、C#实现示例、模式优势以及适用场景,并通过一个形状绘制的完整代码示例演示了其使用方式。通过学习该模式,开发者可以提升软件设计的抽象能力,增强项目的可维护性与可扩展性。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐