编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 面向对象设计

    • README
    • 面向对象设计思想
    • 面向对象特性思考
    • 接口vs抽象类比较
    • 接口而非实现编程
    • 多用组合和少继承
    • 设计原则的全景图
      • 1.先回答上篇思考题
        • 1.1 上篇遗留三道题
        • 1.2 8000行上帝类现场
        • 1.3 五次重构全头处
        • 1.4 灵魂五连问
      • 2.从砖到房子
        • 2.1 已有的砖
        • 2.2 缺什么
        • 2.3 SOLID 总览
      • 3.单一职责 SRP
        • 7.1 一句话理解
        • 3.2 反例与正例
        • 3.3 边界判断
      • 4.开闭原则 OCP
        • 7.1 一句话理解
        • 3.2 案例对照
        • 3.3 实现路径
      • 5.里氏替换 LSP
        • 7.1 一句话理解
        • 5.2 经典反例
        • 5.3 契约不变
      • 6.接口隔离 ISP
        • 7.1 一句话理解
        • 6.2 胖接口拆解
        • 6.3 与 SRP 的差异
      • 7.依赖倒置 DIP
        • 7.1 一句话理解
        • 6.2 倒置的方向
        • 6.3 IoC 与 DI
      • 8.原则与模式
        • 8.1 原则是骨
        • 8.2 模式是肉
        • 8.3 23 种模式分类
      • 9.总结与下一步
      • 10.综合实战案例
        • 10.1 订单总馆需求变迁
        • 10.2 不用原则的崩塌路径
        • 10.3 五条原则逐个介入
        • 10.4 类图与原则映射
        • 10.5 留下三道思考题
      • 11.认知跃迁总结
    • SOLID原则案例汇
    • 反模式与坏味道
    • 重构十二式的实战
    • 可测试性实战设计
    • DDD与战术的建模
    • 综合实战图片框架
  • 常见设计原则

  • 巧学设计模式

  • 系统架构设计

  • 编程
  • 面向对象设计
杨充
2024-07-02
目录

设计原则的全景图

# 第一卷第6章:设计原则全景图

# 目录介绍

  • 1.先回答上篇思考题
    • 1.1 上篇遗留三道题
    • 1.2 8000行上帝类现场
    • 1.3 五次重构全头处
    • 1.4 灵魂五连问
  • 2.从砖到房子
    • 2.1 已有的砖
    • 2.2 缺什么
    • 2.3 SOLID 总览
  • 3.单一职责 SRP
    • 3.1 一句话理解
    • 3.2 反例与正例
    • 3.3 边界判断
  • 4.开闭原则 OCP
    • 4.1 一句话理解
    • 4.2 案例对照
    • 4.3 实现路径
  • 5.里氏替换 LSP
    • 5.1 一句话理解
    • 5.2 经典反例
    • 5.3 契约不变
  • 6.接口隔离 ISP
    • 6.1 一句话理解
    • 6.2 胖接口拆解
    • 6.3 与 SRP 的差异
  • 7.依赖倒置 DIP
    • 7.1 一句话理解
    • 7.2 倒置的方向
    • 7.3 IoC 与 DI
  • 8.原则与模式
    • 8.1 原则是骨
    • 8.2 模式是肉
    • 8.3 23 种模式分类
  • 9.总结与下一步
  • 10.综合实战案例
    • 10.1 订单总馆需求变迁
    • 10.2 不用原则的崩塌路径
    • 10.3 五条原则逐个介入
    • 9.4 类图与原则映射
    • 9.5 留下三道思考题
  • 10.认知跃迁总结

# 1.先回答上篇思考题

# 1.1 上篇遗留三道题

上一篇 05.多用组合和少继承 末尾留下了三道题:

  • 🟢 List<Capability> vs Map<Class, Capability> vs Set 差别?
  • 🟡 运行期动态加能力与不变量怎么平衡?
  • 🔴 product.is(Shippable.class) 这种类型检查是不是设计问题?
题 本篇答案
🟢 List=有序可重复(适合中间件链)、Set=不允许重复能力、Map<Class,Capability>=以类型为主键快查(适合本例)。都能跑,但语义完全不同
🟡 「不变量」不是「字段不变」,是「业务规则始终成立」。动态加能力 → 产生新的不可变 Product 快照,原实例不动
🔴 是设计问题!is(...) 是「多态的退化」。本篇§04 LSP 会告诉你:避免 instanceof 是 LSP 的隱性要求

三道题同时指向本篇的主题:如何从「可以这样写」走到「该这样写」——这正是 SOLID 的职责。

# 1.2 8000行上帝类现场

某中台企业系统 OrderManager.java:

2019-08  init: OrderManager 247 行
2020-04  +推送逻辑、+退款逻辑 → 1132 行
2020-12  +发票、+争议、+邀请返佣 → 2891 行
2021-09  +跨境、+多货币 → 4760 行
2022-08  +会员体系、+积分 → 6204 行
2023-11  +带货主播抽佣、+社区团购 → 8194 行 ← 谁也不敢动
1
2
3
4
5
6

它看上去违反了「单一职责」——但你能一眼看出它到底违反了 SOLID 里哪几条吗?详细联调后,答案是【同时违反了五条】:

flowchart LR
    OM[OrderManager 8194行] --问题 1--> SRP[职责多达 47 个]
    OM --问题 2--> OCP[加业务总要改 OrderManager]
    OM --问题 3--> LSP[跨境、社团「伪子类」不能代替使用]
    OM --问题 4--> ISP[调用方静静依赖了几十个用不到的方法]
    OM --问题 5--> DIP[高层业务依赖底层 ORM/HTTP 实现]
1
2
3
4
5
6

这种「五条原则同时违反」的上帝类,**为什么会出现?**不是工程师不努力,是在「代码越东辣、越不敢动」的状况下,选择「加个 if-else 还能发版」是本能。这种本能选择之所以能出现,就是因为背后没有 SOLID 这件「骨架」在约束肣体生长。

本篇的意义:让你从今天开始,身上背五条反躺肣背心。

# 1.3 五次重构全头处

面对 8000 行上帝类,某团队连动 5 次重构,每次都仅能提起1条原则。这反过来成为了本篇的「介绍顺序」:

  • 重构 1:拆 47 个职责为独立服务 → SRP生效,代码量从 8000 降到 5500
  • 重构 2:抽象出「业务能力」插拔点 → OCP生效,加 1 个业务只加 1 个插件
  • 重构 3:为「跨境」「社团」等「伪子类」重设并保证可替换 → LSP生效
  • 重构 4:给调用方以「只要能力」的接口,别塮事以万能接口 → ISP 生效
  • 重构 5:反转高低层依赖、引入 IoC 容器 → DIP 生效

最后这五条原则不是「五个独立眼镜」,是「五股同时拉着的肣体」。什么叫「同时拉着」?就是:SRP 不是为了拆小是为了代码能遵守 OCP,潜变后 ISP 才能遵守,LSP 保证不拆坏,DIP 为五条提供装配。五条原则互为因果。

# 1.4 灵魂五连问

Q1 ── SOLID 那么各是什么关系?依顺序还是同时生效?
       └─→ §01.3 / §10 原则互为因果
Q2 ── SRP 的「一」到底多「一」?
       └─→ §02.3 边界判断Q3 ── OCP 中「对修改关闭」是不是谈什么都不能动?
       └─→ §03.3 实现路径
Q4 ── LSP 的“可替换”是只考编译能过么?
       └─→ §04 契约不变Q5 ── 为什么 23 种设计模式本质上都是「原则的结晶」?
       └─→ §07 原则与模式
1
2
3
4
5
6
7
8

# 2.从砖到房子

# 2.1 已有的砖

走过前五篇,你已经掌握:

  • 对象与过程的范式之别(01)
  • 四大特性的原理与落地(02)
  • 接口与抽象类的取舍(03)
  • 面向接口而非实现(04)
  • 多用组合少用继承(05)

这些是砖——单点技能。

# 2.2 缺什么

砌墙还需要图纸:什么样的代码算"好"?什么样的拆分是"合理"?什么时候加一个抽象层是"过度设计"?

flowchart LR
    砖[语法/特性] --> 图纸[设计原则<br/>SOLID]
    图纸 --> 房子[设计模式<br/>23 种]
    房子 --> 城市[架构风格<br/>DDD/微服务]
1
2
3
4

设计原则是判断尺:写代码时随时拿来对照,发现"违反"就提示重构。

# 2.3 SOLID 总览

缩写 全称 一句话
S Single Responsibility 一个类只做一件事
O Open/Closed 对扩展开放,对修改关闭
L Liskov Substitution 子类必须能替换父类
I Interface Segregation 接口要小而专
D Dependency Inversion 依赖抽象,不依赖具体
flowchart TB
    SOLID --> S[SRP<br/>职责单一]
    SOLID --> O[OCP<br/>开闭]
    SOLID --> L[LSP<br/>里氏替换]
    SOLID --> I[ISP<br/>接口隔离]
    SOLID --> D[DIP<br/>依赖倒置]
    S -.支撑.-> O
    L -.支撑.-> O
    I -.支撑.-> D
1
2
3
4
5
6
7
8
9

# 3.单一职责 SRP

# 7.1 一句话理解

一个类应当只有一个引起它变化的理由。

"变化的理由"通常对应一个角色/一种业务方向:财务部门改不动技术部门的代码。

# 3.2 反例与正例

// ❌ Order 类既算钱又发邮件又写库
class Order {
    Money total() { /* 计算 */ }
    void save() { /* 写库 */ }
    void sendConfirmEmail() { /* 发邮件 */ }
}

// ✓ 拆成三个职责
class Order { Money total() { /* … */ } }
class OrderRepository { void save(Order o) { /* … */ } }
class EmailNotifier { void sendConfirm(Order o) { /* … */ } }
1
2
3
4
5
6
7
8
9
10
11

任意改一个,编译只动一处——这就是 SRP 的工程价值。

# 3.3 边界判断

不要走极端——不是"一个类一个方法"。判断边界的实操技巧:

flowchart LR
    问1[这个改动<br/>是不是同一个理由] -->|是| 同一类
    问1 -->|不是| 拆出去
1
2
3

例子:Order.calcDiscount 和 Order.calcTax 都是为"算钱"服务 → 同一类; Order.save(持久化)和 Order.calcTax(业务)→ 分属基础设施与领域,应拆。


# 4.开闭原则 OCP

# 7.1 一句话理解

对扩展开放,对修改关闭。

新增功能 → 加新代码,不改旧码。

# 3.2 案例对照

回到 04 篇的支付例子:

// ❌ 违反 OCP
void pay(String channel) {
    if ("alipay".equals(channel)) /* … */
    else if ("wechat".equals(channel)) /* … */
}

// ✓ 满足 OCP
void pay(PaymentGateway g, Order o) { g.pay(o); }
1
2
3
4
5
6
7
8

加 PayPal → 加一个 PaypalGateway,pay 函数零改动。

# 3.3 实现路径

OCP 不是凭空实现的,需要前面所有招式协力:

flowchart LR
    SRP --> 拆出独立扩展点
    LSP --> 子类安全替换
    DIP --> 依赖抽象不依赖具体
    抽象 + 多态 --> OCP[OCP<br/>扩展开放<br/>修改关闭]
1
2
3
4
5

# 5.里氏替换 LSP

# 7.1 一句话理解

凡是基类能用的地方,子类必须能用,且行为不会令使用方惊讶。

# 5.2 经典反例

// 鸵鸟继承 Bird,但 fly() 抛异常
class Ostrich extends Bird {
    @Override public void fly() { throw new UnsupportedOperationException(); }
}

void migrate(Bird bird) { bird.fly(); }   // 拿到 Ostrich → 崩溃
1
2
3
4
5
6

子类违背了父类的契约。修复方式:让 fly() 不在父类(参考 05 篇用接口拆能力)。

# 5.3 契约不变

LSP 的核心是契约不变性:

契约 子类约束
前置条件 不能更严
后置条件 不能更松
不变量 必须保持
异常 不能抛父类未声明的

违反任意一条,子类就不再是"合法替代品",OCP 也就守不住——所以 LSP 是 OCP 的安全前提。


# 6.接口隔离 ISP

# 7.1 一句话理解

客户端不应该被迫依赖它不使用的方法。

# 6.2 胖接口拆解

// ❌ 一个胖接口什么都管
interface UserService {
    void register(User u);
    void login(String u, String p);
    void exportToExcel();
    void sendMarketingEmail();
}
// 客户端只想登录,却被迫看到导出/营销
1
2
3
4
5
6
7
8
// ✓ 拆成多个小接口
interface UserAuth      { void register(User u); void login(String u, String p); }
interface UserExporter  { void exportToExcel(); }
interface MarketingMail { void sendMarketingEmail(); }
1
2
3
4

# 6.3 与 SRP 的差异

原则 着眼点
SRP 类只承担一种变化原因
ISP 接口只服务于一类客户

二者经常被混淆——SRP 看实现,ISP 看契约,是同一思想在不同侧的体现。


# 7.依赖倒置 DIP

# 7.1 一句话理解

高层模块不应依赖低层模块;二者都应依赖抽象。 抽象不应依赖细节;细节应依赖抽象。

# 6.2 倒置的方向

flowchart LR
    subgraph 传统-自上而下依赖
        H1[业务层] --> L1[支付宝SDK]
    end
    subgraph 倒置后
        H2[业务层] --> Abs[支付抽象]
        L2[支付宝SDK] --> Abs
    end
1
2
3
4
5
6
7
8

依赖箭头被"倒过来"——业务不再被 SDK 牵着走,反而是 SDK 来适配业务定义的抽象。

# 6.3 IoC 与 DI

DIP 在工程上的落地是 IoC(控制反转)+ DI(依赖注入):

// ❌ 类自己 new 依赖
class CheckoutService {
    private AlipayGateway gateway = new AlipayGateway();
}

// ✓ 依赖通过构造注入
class CheckoutService {
    private final PaymentGateway gateway;
    CheckoutService(PaymentGateway gateway) { this.gateway = gateway; }
}
1
2
3
4
5
6
7
8
9
10

Spring/Dagger/Guice 等容器把"装配"职责从业务类剥离,业务类只声明"我需要什么",谁给 由容器决定——这就是 IoC 的字面含义。


# 8.原则与模式

# 8.1 原则是骨

SOLID 给了你"判断方向"的尺子,但没告诉你具体怎么写。

# 8.2 模式是肉

23 种设计模式正是SOLID 的具体落地剧本:

模式 服务的原则
工厂方法 OCP、DIP
策略模式 OCP(替代 if-else)
装饰者模式 OCP、LSP
适配器 DIP(适配老接口)
模板方法 OCP(钩子)
观察者 OCP(事件解耦)

# 8.3 23 种模式分类

flowchart TB
    23模式 --> 创建型[创建型 5 种<br/>对象怎么造]
    23模式 --> 结构型[结构型 7 种<br/>对象怎么拼]
    23模式 --> 行为型[行为型 11 种<br/>对象怎么协作]

    创建型 --> 工厂/抽象工厂/单例/建造者/原型
    结构型 --> 适配器/桥接/装饰/外观/享元/代理/组合
    行为型 --> 策略/模板/观察者/责任链/命令/迭代器/中介者/状态/备忘录/解释器/访问者
1
2
3
4
5
6
7
8
类别 关注点 代表案例
创建型 解耦"创建"与"使用" 工厂、建造者
结构型 类与对象的组合关系 装饰、代理
行为型 对象间的协作模式 策略、观察者

# 9.总结与下一步

flowchart LR
    OOP特性 --> SOLID
    SOLID --> 设计模式
    设计模式 --> 架构风格
    架构风格 --> DDD/微服务
1
2
3
4
5

面向对象设计的全景:从语言特性 → 通用原则 → 落地模式 → 架构思想,每一层都解决前一层无法回答的问题。

阶段 解决
特性 怎么写对象
原则 怎么写得"好"
模式 已知场景的最佳实践
架构 大规模系统的整体协作

# 10.综合实战案例

主线收束接点——05 篇我们拼出了商品能力体系,本篇要为「订单总馆」加装 SOLID 五条马马。

# 10.1 订单总馆需求变迁

还记得 01 篇那个订单系统吗?三年后,需求变成了这样:

- 接入 5 种支付渠道(接入、动态插拔)
- 接入 4 种营销活动(可叠加)
- 下发 3 家快递(可拓展)
- 代码量增长为初版 30 倍
- 需领域事件推送、发 BI
- 跨境订单需报关、财务检查
- 遇黑五需抔升结货能力 100 倍
1
2
3
4
5
6
7

代码能不崩吗?要看是否遵守 SOLID。

# 10.2 不用原则的崩塌路径

「不遵守」版很快变成开篇的「8000 行上帝类」。详细路径:

进度点 现象 违反
Day 30 OrderService 1500 行,出现 17 个 if (channel == "alipay") OCP / SRP
Day 90 「虚拟订单」丢进同一个服务、他们不需要发货但也不得不实现 ship() 招表 LSP
Day 180 OrderService 同时抱 MybatisMapper HttpClient KafkaProducer DIP
Day 365 接口 IOrderManager 包括 47 个方法、Mock 要写 47 个 null ISP

# 10.3 五条原则逐个介入

第 1 步·SRP——拆子域服务:

class OrderQueryService { ... }    // 查询
class OrderPlaceService { ... }    // 下单
class OrderRefundService { ... }   // 退款
class OrderShipService { ... }     // 发货
class OrderEventPublisher { ... }  // 领域事件
class OrderBiSink { ... }          // BI 上报
1
2
3
4
5
6

第 2 步·OCP——拆出中间件插拔点:

public interface OrderInterceptor {
    void preHandle(Order order, Context ctx);
    void postHandle(Order order, Result r, Context ctx);
}
class FinComplianceInterceptor   implements OrderInterceptor { ... }
class MarketingInterceptor       implements OrderInterceptor { ... }
class CrossBorderTaxInterceptor  implements OrderInterceptor { ... }
class MetricsInterceptor         implements OrderInterceptor { ... }
1
2
3
4
5
6
7
8

第 3 步·LSP——为「虚拟订单」重设子类:

// 错误示范:VirtualOrder.ship() throws UnsupportedException ——违反 LSP
// 正确示范:
abstract class Order {
    abstract boolean needShipping();
    Optional<ShipResult> tryShip() {
        return needShipping() ? Optional.of(doShip()) : Optional.empty();
    }
}
1
2
3
4
5
6
7
8

第 4 步·ISP——拆肥接口为能力接口:

public interface OrderQuery   { ... }
public interface OrderPlace   { ... }
public interface OrderRefund  { ... }
public interface OrderShip    { ... }
// 调用方只依赖自己需要的
1
2
3
4
5

第 5 步·DIP——高层仅依赖抽象:

// domain 包
public interface OrderRepo  { Order load(OrderId id); void save(Order o); }
public interface PaymentGateway { ... }

// infra 包
class MybatisOrderRepo implements OrderRepo { ... }
class AlipayGateway    implements PaymentGateway { ... }
// 高层看到的依然是 domain 包里的接口
1
2
3
4
5
6
7
8

# 10.4 类图与原则映射

flowchart TB
    subgraph 领域层[领域层 only-interface]
        OS[OrderPlaceService]
        PG[<<interface>><br/>PaymentGateway]
        OR[<<interface>><br/>OrderRepo]
    end
    subgraph 基础设施[基础设施]
        AG[AlipayGateway]
        WG[WechatGateway]
        MR[MybatisOrderRepo]
    end
    OS --> PG
    OS --> OR
    PG <|.. AG
    PG <|.. WG
    OR <|.. MR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这张图上身肣 5 条原则同时生效:

  • SRP:每个服务只负责一个子域
  • OCP:加一家支付?只加一个实现类
  • LSP:所有 PaymentGateway 实现都能互换
  • ISP:领域层不包含以什么能力以外的方法
  • DIP:领域层不依赖以 ORM/HTTP/SDK 在内的底层

# 10.5 留下三道思考题

答案在第 07 篇开头揭晓。

  • 🟢 易:上面架构中,我把 OrderRepo 接口放在了 domain 包、MybatisOrderRepo 实现放在 infra 包。交换位置(接口放 infra、实现放 domain)这件事身是否依然“能跑”?会违反 SOLID 中的哪一条?
  • 🟡 中:「跨境订单」需要 extends ThirdPartySdkBase。这跳跟 04 篇现场一样 ——你会选择「领域层引入 SDK 概念」还是「领域层实体透过中转适配」?为什么?
  • 🔴 难:本项目中 SOLID 在 8000 行上帝类上同时出错。如果要制定一个 CR 检查清单让 5 条原则能被机械发现、你会怎么设计?提示:类型列、包依赖检查、代码位置。

# 11.认知跃迁总结

回到开篇的 8000 行 OrderManager。如果从项目第一天起就被 SOLID 五条马马同时拉住马是事、它不可能长到 8000 行。它只会逐渐分裂、仅仅生长为一棵体系、而不是一坛腐股。1句话:

SOLID 不是面试题,是你代码上限的堆高。五条同时拉住,系统才能随需求生长不崩。

但“五条原则”本身也会被滥用、误用、过度设计。这正是下一篇 07.SOLID原则案例汇 要带你走进的「现场现场」:看它们在真实项目里被怎么用股、怎么被误叫、何时该刻意不遵守。

上次更新: 2026/06/17, 11:43:57
多用组合和少继承
SOLID原则案例汇

← 多用组合和少继承 SOLID原则案例汇→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式