探索高级创建型设计模式:C# 中的抽象工厂(Abstract Factory)、建造者(Builder)和原型(Prototype)模式

162 阅读8分钟

在本系列的第一部分中,我们探讨了两种基础的创建型设计模式:单例模式(Singleton)工厂方法模式(Factory Method)。这些模式帮助管理 C# 中的对象创建,提供了将对象创建过程与业务逻辑解耦的方法。

现在,让我们深入探讨三种更高级的创建型设计模式:抽象工厂模式(Abstract Factory)建造者模式(Builder)原型模式(Prototype),这些模式解决了更复杂的对象创建场景。


抽象工厂模式(Abstract Factory)

什么是抽象工厂?

抽象工厂模式为创建一组相关或依赖的对象提供了一个接口,而无需指定它们的具体类。它允许客户端使用工厂方法创建对象,但具体的实例化过程由子类决定。

现实生活类比

想象一个工厂生产不同风格的家具,比如现代风格维多利亚风格。每种风格都有一系列相关的产品,例如椅子沙发桌子。抽象工厂将定义一个家具产品家族,而具体工厂(如 ModernFactory 和 VictorianFactory)则负责根据选择的风格创建正确的家具类型。

它解决了什么问题?

在系统需要创建一组相关且必须协同工作的对象时,抽象工厂模式非常有用。例如,UI 工具包通常需要创建特定平台的控件(如按钮和窗口),这些控件需要彼此兼容。

如何在 C# 中实现

以下是一个使用抽象工厂模式的 C# 示例,展示如何为不同平台创建按钮和窗口的产品家族:

// 抽象产品
public interface IButton
{
    void Render();
}

public interface IWindow
{
    void Render();
}

// Windows 的具体产品
public class WindowsButton : IButton
{
    public void Render() => Console.WriteLine("渲染 Windows 按钮");
}

public class WindowsWindow : IWindow
{
    public void Render() => Console.WriteLine("渲染 Windows 窗口");
}

// MacOS 的具体产品
public class MacButton : IButton
{
    public void Render() => Console.WriteLine("渲染 Mac 按钮");
}

public class MacWindow : IWindow
{
    public void Render() => Console.WriteLine("渲染 Mac 窗口");
}

// 抽象工厂
public interface IGUIFactory
{
    IButton CreateButton();
    IWindow CreateWindow();
}

// 具体工厂
public class WindowsFactory : IGUIFactory
{
    public IButton CreateButton() => new WindowsButton();
    public IWindow CreateWindow() => new WindowsWindow();
}

public class MacFactory : IGUIFactory
{
    public IButton CreateButton() => new MacButton();
    public IWindow CreateWindow() => new MacWindow();
}

// 客户端代码
public class Application
{
    private IButton _button;
    private IWindow _window;

    public Application(IGUIFactory factory)
    {
        _button = factory.CreateButton();
        _window = factory.CreateWindow();
    }

    public void Render()
    {
        _button.Render();
        _window.Render();
    }
}

// 使用示例
var windowsApp = new Application(new WindowsFactory());
windowsApp.Render();

var macApp = new Application(new MacFactory());
macApp.Render();

何时使用

抽象工厂模式非常适合需要创建一组相关对象的场景,比如 UI 组件,这些组件必须能够无缝协同工作。

常见陷阱

  • 复杂性:在拥有多个产品家族的大型系统中,随着越来越多的工厂和产品需要维护,这种模式可能会变得繁琐。

现实案例

跨平台 UI 开发通常使用抽象工厂模式,以确保像按钮和窗口这样的组件与它们的环境(例如 Windows 或 macOS)兼容。


建造者模式(Builder)

什么是建造者模式?

建造者模式用于一步步构建复杂对象。与其他创建型模式不同,建造者不要求产品具有通用接口,因此可以灵活地构建不同的对象表现形式。

现实生活类比

想象一下建造一座房子。房子由许多部分组成(例如,地基、墙壁、屋顶),不同类型的房子可能需要以不同方式组装这些部分。建造者模式允许逐步构建对象,提供了灵活性来控制对象的创建过程。

它解决了什么问题?

建造者模式简化了需要多个步骤才能构建的复杂对象。与其通过一个包含大量参数的构造函数来创建对象,建造者模式使构造过程更具可读性和可管理性。

如何在 C# 中实现

以下是一个使用建造者模式来构建房子的 C# 示例:

// 产品类
public class House
{
    public string Walls { get; set; }
    public string Roof { get; set; }
    public string Foundation { get; set; }
    
    public void ShowDetails()
    {
        Console.WriteLine($"这是一座有 {Walls}, {Roof}, 和 {Foundation} 的房子");
    }
}

// 生成器接口
public interface IHouseBuilder
{
    void BuildWalls();
    void BuildRoof();
    void BuildFoundation();
    House GetHouse();
}

// 石头房子的具体生成器
public class StoneHouseBuilder : IHouseBuilder
{
    private House _house = new House();

    public void BuildWalls() => _house.Walls = "石墙";
    public void BuildRoof() => _house.Roof = "石屋顶";
    public void BuildFoundation() => _house.Foundation = "混凝土地基";

    public House GetHouse() => _house;
}

// 导演类
public class HouseDirector
{
    private IHouseBuilder _builder;

    public HouseDirector(IHouseBuilder builder)
    {
        _builder = builder;
    }

    public House ConstructHouse()
    {
        _builder.BuildWalls();
        _builder.BuildRoof();
        _builder.BuildFoundation();
        return _builder.GetHouse();
    }
}

// 使用示例
var stoneBuilder = new StoneHouseBuilder();
var director = new HouseDirector(stoneBuilder);
var house = director.ConstructHouse();
house.ShowDetails();

何时使用

当创建一个对象需要多个步骤或配置时,建造者模式特别有用,使构造过程更易于管理。

常见陷阱

  • 过度设计:如果对象的创建过程较为简单,使用建造者模式可能会增加不必要的复杂性。

现实案例

建造者模式通常用于构建复杂的配置,例如软件中的文档或大型表单。


原型模式(Prototype)

什么是原型模式?

原型模式通过复制现有对象(即原型)来创建新对象。这个模式在对象创建成本高或过程复杂时特别有用,避免了重复创建新实例的麻烦。

现实生活类比

克隆生物是原型模式的一个恰当类比。与其从头开始创建一个新生物,不如通过克隆现有生物来生成一个相似的个体。

它解决了什么问题?

原型模式解决了对象创建成本昂贵或不合适直接实例化的场景,通过复制现有对象快速生成新实例。

如何在 C# 中实现

以下是一个使用原型模式进行浅拷贝和深拷贝的 C# 示例:

public class PrototypeExample
{
    public string Name { get; set; }
    public List<string> Data { get; set; }

    public PrototypeExample ShallowCopy() => (PrototypeExample)this.MemberwiseClone();

    public PrototypeExample DeepCopy()
    {
        var clone = (PrototypeExample)this.MemberwiseClone();
        clone.Data = new List<string>(this.Data);
        return clone;
    }
}

与其在每个类中手动实现 ShallowCopyDeepCopy 方法,我们还可以使用一个通用的工具类(例如 CloneUtility)来处理任何类的复制操作。该工具类提供了两个方法:

  1. 浅拷贝:使用反射调用 MemberwiseClone() 方法,执行对象的浅拷贝。
  2. 深拷贝:使用 JSON 序列化来创建深拷贝,确保所有引用类型都能递归地复制。

以下是 CloneUtility 类的实现:

using System;
using System.Reflection;
using Newtonsoft.Json;

public static class CloneUtility
{
    // 适用于任意对象的浅拷贝
    public static T ShallowCopy<T>(T source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        // 使用反射调用受保护的 MemberwiseClone 方法
        var cloneMethod = typeof(T).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);
        if (cloneMethod == null)
            throw new InvalidOperationException("未找到 MemberwiseClone 方法。");

        return (T)cloneMethod.Invoke(source, null);
    }

    // 使用 JSON 序列化进行深拷贝
    public static T DeepCopy<T>(T source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        // 将对象序列化为 JSON,并反序列化来创建深拷贝
        var serialized = JsonConvert.SerializeObject(source);
        return JsonConvert.DeserializeObject<T>(serialized);
    }
}

现在,您可以应用该工具类来复制任意类,而无需在类本身中编写特定的复制方法。以下是一个示例:

// 使用示例
var original = new PrototypeExample { Name = "Original", Data = new List<string> { "Item1", "Item2" } };

// 浅拷贝
var shallowCopy = CloneUtility.ShallowCopy(original);

// 深拷贝
var deepCopy = CloneUtility.DeepCopy(original);

在这个示例中:

  • 浅拷贝:复制 PrototypeExample 的字段,但对引用类型(如 List<string>)的任何修改都会影响原对象和拷贝对象。
  • 深拷贝:创建对象的全新副本,包括所有嵌套的引用类型,确保对拷贝版本的更改不会影响原对象。

通过使用 CloneUtility 类,您可以轻松处理不同类的浅拷贝和深拷贝,而无需为每个类手动实现复制逻辑。这样可以减少重复代码,并使复制对象的过程在 C# 项目中更加简洁高效。

何时使用

当对象创建成本较高,或者频繁需要基于现有对象创建新对象时,适合使用原型模式。

常见陷阱

  • 处理深拷贝与浅拷贝:处理拷贝不当,尤其是复杂对象时,可能会导致意外结果。

现实案例

在游戏开发中,原型模式经常用于克隆游戏实体,例如 NPC 或敌人,而不需要从头开始重新创建它们。


创建模式的比较

模式使用案例示例
单例确保一个类只有一个实例。日志服务,配置
工厂方法将对象创建与业务逻辑解耦。创建动物(狗、猫等)
抽象工厂提供创建相关对象家族的接口。跨平台UI元素
建造者逐步构建复杂对象。建造房屋或配置
原型通过复制现有对象来创建新对象。游戏中的克隆实体

创建模式的常见陷阱

  • 过度使用:在简单对象上使用创建模式可能会引入不必要的复杂性。
  • 副本管理不当(原型):深拷贝和浅拷贝的管理可能导致意外错误。
  • 性能考虑:某些模式,如单例,可能引入线程安全问题,而其他模式,如原型,可能由于对象复制而带来性能开销。

结论

选择合适的创建模式对于在C#中创建灵活、可维护和可扩展的应用程序至关重要。了解每种模式的优缺点将帮助您在项目中做出明智的决定。尝试不同的模式,以掌握其在实际场景中的使用。


参考文献与进一步阅读