C#工厂方法模式详解与项目实战
简介:工厂方法模式是一种常用的创建型设计模式,在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.");
}
}
代码逻辑分析:
ILogger接口定义了统一的日志记录行为。ConsoleLogger和FileLogger分别实现了接口,提供不同的日志记录方式。ILoggerFactory接口定义了创建日志器的工厂方法。- 具体工厂类如
ConsoleLoggerFactory负责返回对应的具体产品实例。 - 客户端通过工厂创建日志器,完全解耦了具体产品类的实现。
参数说明:
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 如何在实际开发中推广使用该模式
- 封装复杂构造逻辑 :将构造参数、依赖注入、配置读取等封装在工厂内部。
- 提高可测试性 :通过接口隔离创建逻辑,便于单元测试。
- 结合配置中心 :通过配置决定创建哪个具体类,提高系统的灵活性。
- 文档与规范 :制定统一的工厂接口规范,便于团队协作与维护。
工厂方法模式不仅是一个创建型设计模式,更是实现系统解耦、提升可维护性的关键工具。在真实项目中合理运用,能够显著提升系统的可扩展性和健壮性。
简介:工厂方法模式是一种常用的创建型设计模式,在C#中通过接口抽象产品的创建过程,将具体实例化操作延迟到子类,实现代码解耦与高扩展性。文章详细介绍了该模式的定义、核心角色、C#实现示例、模式优势以及适用场景,并通过一个形状绘制的完整代码示例演示了其使用方式。通过学习该模式,开发者可以提升软件设计的抽象能力,增强项目的可维护性与可扩展性。
更多推荐



所有评论(0)