建造者模式:优雅构建复杂对象

发布于:2025-06-04 ⋅ 阅读:(26) ⋅ 点赞:(0)

引言

在软件开发中,有时我们需要创建一个由多个部分组成的复杂对象,这些部分可能有不同的变体或配置。如果直接在一个构造函数中设置所有参数,代码会变得难以阅读和维护。当对象构建过程复杂,且需要多个步骤时,我们可能会陷入"伸缩式构造函数"的困境,或者创建大量的子类来处理各种组合。

建造者模式(Builder Pattern)提供了一种更优雅的解决方案,它将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。这种模式特别适用于需要分步骤创建复杂对象的场景,或者当对象的创建涉及大量参数时。

本文将深入探讨建造者模式的概念、实现方式、应用场景以及优缺点,并通过C#代码示例来展示其实际应用。

建造者模式定义

建造者模式(Builder Pattern)的定义是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

建造者模式属于创建型设计模式,它解决的核心问题包括:

  • 如何构建一个由多个部分组成的复杂对象
  • 如何避免"伸缩式构造函数"的问题
  • 如何允许对象有不同的表示形式
  • 如何分离对象的构建和表示

建造者模式的UML类图

以下是建造者模式的UML类图:

directs
creates
creates
Director
Construct(builder: Builder)
Builder
BuildPartA()
BuildPartB()
BuildPartC()
GetResult()
ConcreteBuilder1
product: Product
BuildPartA()
BuildPartB()
BuildPartC()
GetResult()
ConcreteBuilder2
product: Product
BuildPartA()
BuildPartB()
BuildPartC()
GetResult()
Product
partA: string
partB: string
partC: string
Show()

建造者模式的组成部分

建造者模式包含以下几个核心组件:

  1. 产品(Product)

    • 被构建的复杂对象
    • 包含多个组成部分
    • 具体建造者创建该产品的内部表示并定义装配过程
  2. 抽象建造者(Builder)

    • 声明创建产品各个部分的抽象接口
    • 定义构建产品各个部分的方法
    • 声明返回产品的方法
  3. 具体建造者(Concrete Builder)

    • 实现抽象建造者的接口
    • 定义并跟踪它所创建的产品
    • 提供检索产品的方法
    • 实现构建产品各个部分的细节
  4. 指挥者(Director)

    • 构造一个使用Builder接口的对象
    • 指导构建过程的顺序
    • 隐藏产品是如何组装的细节

客户端代码通常会创建一个具体建造者对象,将其传递给指挥者,然后通过指挥者开始构建过程。当构建过程完成后,客户端从建造者中获取产品。

建造者模式的实现方式

下面通过C#代码示例展示建造者模式的几种实现方式。

基本实现

首先,让我们看一个食品订单系统的例子,实现不同类型的餐点组合:

/// <summary>
/// 产品类 - 表示餐点
/// </summary>
public class Meal
{
    // 餐点的各个组成部分
    public string MainCourse { get; set; }
    public string SideDish { get; set; }
    public string Drink { get; set; }
    public string Dessert { get; set; }
    
    /// <summary>
    /// 显示餐点的完整信息
    /// </summary>
    public void ShowMeal()
    {
        Console.WriteLine("餐点信息:");
        Console.WriteLine($"- 主菜: {MainCourse ?? "无"}");
        Console.WriteLine($"- 配菜: {SideDish ?? "无"}");
        Console.WriteLine($"- 饮料: {Drink ?? "无"}");
        Console.WriteLine($"- 甜点: {Dessert ?? "无"}");
    }
}

/// <summary>
/// 抽象建造者 - 定义创建餐点各部分的方法
/// </summary>
public interface IMealBuilder
{
    void BuildMainCourse();
    void BuildSideDish();
    void BuildDrink();
    void BuildDessert();
    Meal GetMeal();
}

/// <summary>
/// 具体建造者 - 健康餐点建造者
/// </summary>
public class HealthyMealBuilder : IMealBuilder
{
    private Meal _meal = new Meal();
    
    public void BuildMainCourse()
    {
        _meal.MainCourse = "烤鸡胸肉";
    }
    
    public void BuildSideDish()
    {
        _meal.SideDish = "蒸蔬菜";
    }
    
    public void BuildDrink()
    {
        _meal.Drink = "矿泉水";
    }
    
    public void BuildDessert()
    {
        _meal.Dessert = "水果沙拉";
    }
    
    public Meal GetMeal()
    {
        return _meal;
    }
}

/// <summary>
/// 具体建造者 - 快餐建造者
/// </summary>
public class FastFoodMealBuilder : IMealBuilder
{
    private Meal _meal = new Meal();
    
    public void BuildMainCourse()
    {
        _meal.MainCourse = "汉堡";
    }
    
    public void BuildSideDish()
    {
        _meal.SideDish = "炸薯条";
    }
    
    public void BuildDrink()
    {
        _meal.Drink = "可乐";
    }
    
    public void BuildDessert()
    {
        _meal.Dessert = "冰淇淋";
    }
    
    public Meal GetMeal()
    {
        return _meal;
    }
}

/// <summary>
/// 指挥者 - 负责构建过程
/// </summary>
public class MealDirector
{
    /// <summary>
    /// 构建完整餐点
    /// </summary>
    /// <param name="builder">餐点建造者</param>
    /// <returns>构建好的餐点</returns>
    public Meal ConstructFullMeal(IMealBuilder builder)
    {
        builder.BuildMainCourse();
        builder.BuildSideDish();
        builder.BuildDrink();
        builder.BuildDessert();
        return builder.GetMeal();
    }
    
    /// <summary>
    /// 构建没有甜点的餐点
    /// </summary>
    /// <param name="builder">餐点建造者</param>
    /// <returns>构建好的餐点</returns>
    public Meal ConstructMealWithoutDessert(IMealBuilder builder)
    {
        builder.BuildMainCourse();
        builder.BuildSideDish();
        builder.BuildDrink();
        return builder.GetMeal();
    }
}

/// <summary>
/// 客户端代码
/// </summary>
public class Client
{
    public static void Run()
    {
        // 创建指挥者
        var director = new MealDirector();
        
        // 使用健康餐点建造者
        var healthyBuilder = new HealthyMealBuilder();
        var healthyMeal = director.ConstructFullMeal(healthyBuilder);
        
        Console.WriteLine("健康餐点:");
        healthyMeal.ShowMeal();
        
        // 使用快餐建造者,不含甜点
        var fastFoodBuilder = new FastFoodMealBuilder();
        var fastFoodMeal = director.ConstructMealWithoutDessert(fastFoodBuilder);
        
        Console.WriteLine("\n快餐餐点(无甜点):");
        fastFoodMeal.ShowMeal();
    }
}

// 执行客户端代码
Client.Run();

/* 输出结果:
健康餐点:
餐点信息:
- 主菜: 烤鸡胸肉
- 配菜: 蒸蔬菜
- 饮料: 矿泉水
- 甜点: 水果沙拉

快餐餐点(无甜点):
餐点信息:
- 主菜: 汉堡
- 配菜: 炸薯条
- 饮料: 可乐
- 甜点: 无
*/

在这个基本实现中:

  • Meal 是被构建的产品
  • IMealBuilder 是抽象建造者,定义了构建餐点各部分的方法
  • HealthyMealBuilderFastFoodMealBuilder 是具体建造者,实现了不同风格的餐点创建
  • MealDirector 是指挥者,控制构建过程的顺序
  • 客户端可以选择不同的建造者来创建不同类型的餐点,以及选择不同的构建方式(完整或无甜点)

使用链式调用的流畅接口

建造者模式的一个常见变体是使用方法链(链式调用)创建流畅接口,这在C#中特别常见:

/// <summary>
/// 产品类 - 电子邮件
/// </summary>
public class Email
{
    // 电子邮件的各个组成部分
    public string From { get; set; }
    public string To { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public List<string> Attachments { get; set; } = new List<string>();
    public bool IsHtml { get; set; }
    public string Cc { get; set; }
    public string Bcc { get; set; }
    
    /// <summary>
    /// 显示电子邮件的内容
    /// </summary>
    public void Display()
    {
        Console.WriteLine("电子邮件详情:");
        Console.WriteLine($"发件人: {From}");
        Console.WriteLine($"收件人: {To}");
        if (!string.IsNullOrEmpty(Cc)) Console.WriteLine($"抄送: {Cc}");
        if (!string.IsNullOrEmpty(Bcc)) Console.WriteLine($"密送: {Bcc}");
        Console.WriteLine($"主题: {Subject}");
        Console.WriteLine($"正文: {Body}");
        Console.WriteLine($"格式: {(IsHtml ? "HTML" : "纯文本")}");
        
        if (Attachments.Count > 0)
        {
            Console.WriteLine("附件:");
            foreach (var attachment in Attachments)
            {
                Console.WriteLine($"- {attachment}");
            }
        }
    }
}

/// <summary>
/// 流畅接口建造者 - 电子邮件建造者
/// </summary>
public class EmailBuilder
{
    private readonly Email _email = new Email();
    
    /// <summary>
    /// 设置发件人地址
    /// </summary>
    /// <param name="from">发件人邮箱地址</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder From(string from)
    {
        _email.From = from;
        return this;
    }
    
    /// <summary>
    /// 设置收件人地址
    /// </summary>
    /// <param name="to">收件人邮箱地址</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder To(string to)
    {
        _email.To = to;
        return this;
    }
    
    /// <summary>
    /// 设置邮件主题
    /// </summary>
    /// <param name="subject">邮件主题</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder WithSubject(string subject)
    {
        _email.Subject = subject;
        return this;
    }
    
    /// <summary>
    /// 设置邮件正文内容
    /// </summary>
    /// <param name="body">邮件内容</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder WithBody(string body)
    {
        _email.Body = body;
        return this;
    }
    
    /// <summary>
    /// 添加附件
    /// </summary>
    /// <param name="attachment">附件路径</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder WithAttachment(string attachment)
    {
        _email.Attachments.Add(attachment);
        return this;
    }
    
    /// <summary>
    /// 设置邮件为HTML格式
    /// </summary>
    /// <param name="isHtml">是否是HTML格式</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder AsHtml(bool isHtml = true)
    {
        _email.IsHtml = isHtml;
        return this;
    }
    
    /// <summary>
    /// 设置抄送地址
    /// </summary>
    /// <param name="cc">抄送邮箱地址</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder WithCc(string cc)
    {
        _email.Cc = cc;
        return this;
    }
    
    /// <summary>
    /// 设置密送地址
    /// </summary>
    /// <param name="bcc">密送邮箱地址</param>
    /// <returns>当前建造者实例以支持链式调用</returns>
    public EmailBuilder WithBcc(string bcc)
    {
        _email.Bcc = bcc;
        return this;
    }
    
    /// <summary>
    /// 构建电子邮件对象
    /// </summary>
    /// <returns>构建好的电子邮件</returns>
    public Email Build()
    {
        // 这里可以添加验证逻辑,例如检查必填字段
        if (string.IsNullOrEmpty(_email.From))
            throw new InvalidOperationException("发件人地址不能为空");
            
        if (string.IsNullOrEmpty(_email.To))
            throw new InvalidOperationException("收件人地址不能为空");
            
        return _email;
    }
}

// 客户端代码
public class FluentClient
{
    public static void Run()
    {
        // 使用流畅接口创建一封简单邮件
        var simpleEmail = new EmailBuilder()
            .From("sender@example.com")
            .To("recipient@example.com")
            .WithSubject("问候邮件")
            .WithBody("您好!这是一封测试邮件。")
            .Build();
            
        Console.WriteLine("简单邮件:");
        simpleEmail.Display();
        
        // 使用流畅接口创建一封复杂邮件
        var complexEmail = new EmailBuilder()
            .From("manager@company.com")
            .To("team@company.com")
            .WithCc("supervisor@company.com")
            .WithBcc("records@company.com")
            .WithSubject("项目进度报告")
            .WithBody("<h1>项目进度</h1><p>团队成员们,请查看附件中的项目进度报告。</p>")
            .AsHtml()
            .WithAttachment("progress_report.pdf")
            .WithAttachment("schedule.xlsx")
            .Build();
            
        Console.WriteLine("\n复杂邮件:");
        complexEmail.Display();
    }
}

// 执行客户端代码
FluentClient.Run();

/* 输出结果:
简单邮件:
电子邮件详情:
发件人: sender@example.com
收件人: recipient@example.com
主题: 问候邮件
正文: 您好!这是一封测试邮件。
格式: 纯文本

复杂邮件:
电子邮件详情:
发件人: manager@company.com
收件人: team@company.com
抄送: supervisor@company.com
密送: records@company.com
主题: 项目进度报告
正文: <h1>项目进度</h1><p>团队成员们,请查看附件中的项目进度报告。</p>
格式: HTML
附件:
- progress_report.pdf
- schedule.xlsx
*/

在这个流畅接口实现中:

  • 每个设置方法返回建造者本身,这使得可以链式调用方法
  • 提供了一种更加简洁、可读性更高的方式来构建复杂对象
  • 允许按需设置各个属性,支持可选参数
  • 在最后调用Build()方法来获取构建好的对象,同时可以进行必要的验证

使用指挥者角色

在一些更复杂的场景中,指挥者(Director)角色可以更好地封装不同的构建过程。下面是一个使用指挥者角色的计算机配置示例:

/// <summary>
/// 产品类 - 计算机
/// </summary>
public class Computer
{
    // 计算机的各个组件
    public string CPU { get; set; }
    public string RAM { get; set; }
    public string Storage { get; set; }
    public string GraphicsCard { get; set; }
    public string Monitor { get; set; }
    public string OperatingSystem { get; set; }
    
    /// <summary>
    /// 显示计算机配置
    /// </summary>
    public void ShowConfiguration()
    {
        Console.WriteLine("计算机配置:");
        Console.WriteLine($"CPU: {CPU ?? "未指定"}");
        Console.WriteLine($"内存: {RAM ?? "未指定"}");
        Console.WriteLine($"存储: {Storage ?? "未指定"}");
        Console.WriteLine($"显卡: {GraphicsCard ?? "未指定"}");
        Console.WriteLine($"显示器: {Monitor ?? "未指定"}");
        Console.WriteLine($"操作系统: {OperatingSystem ?? "未指定"}");
    }
}

/// <summary>
/// 抽象建造者 - 计算机建造者
/// </summary>
public interface IComputerBuilder
{
    void BuildCPU();
    void BuildRAM();
    void BuildStorage();
    void BuildGraphicsCard();
    void BuildMonitor();
    void InstallOperatingSystem();
    Computer GetComputer();
}

/// <summary>
/// 具体建造者 - 游戏计算机建造者
/// </summary>
public class GamingComputerBuilder : IComputerBuilder
{
    private Computer _computer = new Computer();
    
    public void BuildCPU()
    {
        _computer.CPU = "Intel Core i9-11900K";
    }
    
    public void BuildRAM()
    {
        _computer.RAM = "32GB DDR4 3600MHz";
    }
    
    public void BuildStorage()
    {
        _computer.Storage = "2TB NVMe SSD";
    }
    
    public void BuildGraphicsCard()
    {
        _computer.GraphicsCard = "NVIDIA GeForce RTX 3080";
    }
    
    public void BuildMonitor()
    {
        _computer.Monitor = "27英寸 4K 144Hz 游戏显示器";
    }
    
    public void InstallOperatingSystem()
    {
        _computer.OperatingSystem = "Windows 11 Pro";
    }
    
    public Computer GetComputer()
    {
        return _computer;
    }
}

/// <summary>
/// 具体建造者 - 办公计算机建造者
/// </summary>
public class OfficeComputerBuilder : IComputerBuilder
{
    private Computer _computer = new Computer();
    
    public void BuildCPU()
    {
        _computer.CPU = "Intel Core i5-11400";
    }
    
    public void BuildRAM()
    {
        _computer.RAM = "16GB DDR4 2666MHz";
    }
    
    public void BuildStorage()
    {
        _computer.Storage = "512GB SSD";
    }
    
    public void BuildGraphicsCard()
    {
        _computer.GraphicsCard = "集成显卡";
    }
    
    public void BuildMonitor()
    {
        _computer.Monitor = "24英寸 1080p 办公显示器";
    }
    
    public void InstallOperatingSystem()
    {
        _computer.OperatingSystem = "Windows 10 专业版";
    }
    
    public Computer GetComputer()
    {
        return _computer;
    }
}

/// <summary>
/// 具体建造者 - 开发计算机建造者
/// </summary>
public class DeveloperComputerBuilder : IComputerBuilder
{
    private Computer _computer = new Computer();
    
    public void BuildCPU()
    {
        _computer.CPU = "AMD Ryzen 9 5900X";
    }
    
    public void BuildRAM()
    {
        _computer.RAM = "64GB DDR4 3200MHz";
    }
    
    public void BuildStorage()
    {
        _computer.Storage = "1TB NVMe SSD + 2TB HDD";
    }
    
    public void BuildGraphicsCard()
    {
        _computer.GraphicsCard = "NVIDIA GeForce RTX 3060";
    }
    
    public void BuildMonitor()
    {
        _computer.Monitor = "双 27英寸 4K 专业显示器";
    }
    
    public void InstallOperatingSystem()
    {
        _computer.OperatingSystem = "Ubuntu 22.04 LTS";
    }
    
    public Computer GetComputer()
    {
        return _computer;
    }
}

/// <summary>
/// 指挥者 - 计算机装配指挥者
/// </summary>
public class ComputerDirector
{
    /// <summary>
    /// 构建完整计算机
    /// </summary>
    /// <param name="builder">计算机建造者</param>
    /// <returns>构建好的计算机</returns>
    public Computer BuildFullComputer(IComputerBuilder builder)
    {
        builder.BuildCPU();
        builder.BuildRAM();
        builder.BuildStorage();
        builder.BuildGraphicsCard();
        builder.BuildMonitor();
        builder.InstallOperatingSystem();
        return builder.GetComputer();
    }
    
    /// <summary>
    /// 构建基本计算机(无显卡、基本显示器)
    /// </summary>
    /// <param name="builder">计算机建造者</param>
    /// <returns>构建好的计算机</returns>
    public Computer BuildBasicComputer(IComputerBuilder builder)
    {
        builder.BuildCPU();
        builder.BuildRAM();
        builder.BuildStorage();
        builder.InstallOperatingSystem();
        return builder.GetComputer();
    }
    
    /// <summary>
    /// 构建无操作系统的计算机
    /// </summary>
    /// <param name="builder">计算机建造者</param>
    /// <returns>构建好的计算机</returns>
    public Computer BuildComputerWithoutOS(IComputerBuilder builder)
    {
        builder.BuildCPU();
        builder.BuildRAM();
        builder.BuildStorage();
        builder.BuildGraphicsCard();
        builder.BuildMonitor();
        return builder.GetComputer();
    }
}

/// <summary>
/// 客户端代码
/// </summary>
public class DirectorClient
{
    public static void Run()
    {
        // 创建指挥者
        var director = new ComputerDirector();
        
        // 构建游戏计算机(完整配置)
        var gamingBuilder = new GamingComputerBuilder();
        var gamingComputer = director.BuildFullComputer(gamingBuilder);
        
        Console.WriteLine("游戏计算机配置:");
        gamingComputer.ShowConfiguration();
        
        // 构建基本办公计算机
        var officeBuilder = new OfficeComputerBuilder();
        var officeComputer = director.BuildBasicComputer(officeBuilder);
        
        Console.WriteLine("\n办公计算机配置:");
        officeComputer.ShowConfiguration();
        
        // 构建无操作系统的开发计算机
        var developerBuilder = new DeveloperComputerBuilder();
        var developerComputer = director.BuildComputerWithoutOS(developerBuilder);
        
        Console.WriteLine("\n开发计算机配置(无操作系统):");
        developerComputer.ShowConfiguration();
    }
}

// 执行客户端代码
DirectorClient.Run();

/* 输出结果:
游戏计算机配置:
计算机配置:
CPU: Intel Core i9-11900K
内存: 32GB DDR4 3600MHz
存储: 2TB NVMe SSD
显卡: NVIDIA GeForce RTX 3080
显示器: 27英寸 4K 144Hz 游戏显示器
操作系统: Windows 11 Pro

办公计算机配置:
计算机配置:
CPU: Intel Core i5-11400
内存: 16GB DDR4 2666MHz
存储: 512GB SSD
显卡: 未指定
显示器: 未指定
操作系统: Windows 10 专业版

开发计算机配置(无操作系统):
计算机配置:
CPU: AMD Ryzen 9 5900X
内存: 64GB DDR4 3200MHz
存储: 1TB NVMe SSD + 2TB HDD
显卡: NVIDIA GeForce RTX 3060
显示器: 双 27英寸 4K 专业显示器
操作系统: 未指定
*/

在这个实现中:

  • 指挥者(ComputerDirector)定义了多种不同的构建过程(完整、基本、无操作系统)
  • 具体建造者(GamingComputerBuilderOfficeComputerBuilderDeveloperComputerBuilder)提供了不同的实现方式
  • 客户端可以选择不同的建造者和构建过程的组合,以获得不同配置的计算机
  • 指挥者封装了构建过程,使得客户端代码更加简洁

建造者模式的应用场景

建造者模式适用于以下场景:

建造者模式应用场景
复杂对象的创建
参数众多的构造函数
不同表示的构建过程
分步构建对象
不变性对象的创建

复杂对象的创建

当对象包含多个部分,或者构建需要多个步骤时,建造者模式能够提供清晰的创建过程:

  • 文档生成器(如HTML、PDF、Word文档)
  • 复杂的图形用户界面构建
  • 配置对象的创建
  • 复杂的数据转换和导出

参数众多的构造函数

在需要处理大量可选参数时,建造者模式可以避免"伸缩式构造函数"问题:

  • 避免了使用多个重载构造函数
  • 避免了使用大量参数的构造函数
  • 允许仅设置需要的参数
  • 代码可读性和可维护性更高

不同表示的构建过程

当同一个构建过程可以创建不同表示的对象时,建造者模式特别有用:

  • 不同格式的报告生成
  • 不同风格的UI组件创建
  • 不同平台的代码生成器

建造者模式的优缺点

优点

  1. 分离构建和表示:建造者模式将对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。

  2. 封装变化:产品的内部结构可以变化,而不会影响客户端代码,因为客户端只与抽象接口交互。

  3. 更好的控制构建过程:建造者模式允许对对象的构建过程进行更精细的控制,特别是在复杂对象的构建中。

  4. 隐藏产品的内部结构:产品的具体细节对客户端是不可见的,客户端只需要与建造者交互。

  5. 提高代码可读性:特别是在使用流畅接口时,构建过程清晰易读。

缺点

  1. 增加类的数量:建造者模式引入了多个新的类,这可能增加系统的复杂性。

  2. 与特定类型绑定:每个具体建造者与特定的产品类型绑定,限制了灵活性。

  3. 不适合简单对象:对于简单对象,使用建造者模式可能是过度设计。

  4. 指挥者可能不总是必要的:在一些实现中,特别是使用流畅接口时,指挥者角色可能是多余的。

  5. 可能导致代码重复:不同的具体建造者可能包含相似的代码。

总结

建造者模式是一种功能强大的创建型设计模式,它允许我们分步骤创建复杂对象,并且可以使用相同的构建过程创建不同的表示。这种模式特别适合处理具有多个组成部分或需要复杂构建过程的对象。

在C#中,建造者模式有多种实现方式,从传统的实现(使用抽象建造者、具体建造者和指挥者),到更现代的流畅接口实现。流畅接口特别适合C#,它提供了一种简洁、可读性强的方式来构建对象。

建造者模式与其他创建型模式(如工厂方法和抽象工厂)相比,更加关注对象的分步构建过程,而不仅仅是简单的实例化。它为控制复杂对象的创建过程提供了更细粒度的方法。

虽然建造者模式增加了代码的复杂性,但在处理复杂对象创建、避免构造函数参数过多或需要不同表示的场景中,这种复杂性是值得的。通过合理应用建造者模式,我们可以创建更加灵活、可维护和可扩展的代码。

相关学习资源

网站资源:

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到