当前位置: 首页 > article >正文

25年上半年五月之软考之设计模式

目录

一、单例模式

二、工厂模式

三、 抽象工厂模式

四、适配器模式

五、策略模式

六、装饰器模式

​编辑

考点:会挖空super(coffeOpertion);

七、代理模式

为什么必须要使用代理对象?

和装饰器模式的区别

八、备忘录模式


一、单例模式

这个模式能确保一个类只有一个实例,并提供全局访问点,非常适合配置管理、日志对象等场景。

我们不使用单例模式的时候会导致

  1. 内存浪费(重复加载配置)

  2. 配置不一致(不同实例可能读取不同状态)

以下是不使用单例模式的例子

public class Printer {// 构造函数,用于初始化打印机对象时打印提示信息public Printer() {System.out.println("这是一台普通的打印机");}public static void main(String[] args) {// 创建第一个打印机对象Printer printer1 = new Printer();// 创建第二个打印机对象Printer printer2 = new Printer();// 判断两个打印机对象是否为同一实例,并输出结果System.out.println("printer1和printer2是同一台吗?" + (printer1 == printer2));}
}

运行结果

这是使用单例模式的结果:

知识点扫盲:
//    互斥访问 使用关键字synchronized 意思是打印完第一个,才能打印第二个,保证有序

public class Printer1 {private static Printer1 instance;
private Printer1(){System.out.println("全公司唯一一台打印机已经启动");
}
private static synchronized Printer1 getInstance(){if (instance==null){instance = new Printer1();}return instance;
}public void PrintDocument(String employee,String document){System.out.println(employee+"正在打印"+document);
}
public static void main(String[] args) {
//    员工A申请使用打印机Printer1 printerA = Printer1.getInstance();
//    员工B申请使用打印机Printer1 printerB= Printer1.getInstance();
//    检查是否是同一台打印机System.out.println("printerA和printerB是同一台吗?"+(printerA==printerB));}
}

true

二、工厂模式

假设你开了一家奶茶店,顾客可以点不同口味的奶茶(原味、芒果、草莓)。
问题:如果直接在代码里用new创建每种奶茶,会导致:

  1. 代码臃肿(每次新增口味都要改多处逻辑)

  2. 难以维护(制作流程分散在各处)

解决方案:用工厂模式统一管理奶茶的创建过程!

首先我们先扫盲一下接下来代码的知识点

package org.factory;
public class Case {public static void main(String[] args) {String str1="Hello";String str2="hello";String str3="World";boolean result1 =str1.equals(str2);boolean result2 =str1.equalsIgnoreCase(str2);System.out.println(result1);  // 输出falseSystem.out.println(result2);  // 输出true}}

equalsIgnoreCase() 是 Java 中 String 类的一个方法,用于比较两个字符串的内容是否相同,忽略大小写差异

首先,先来梳理一下代码思路。首先定义一个接口,将制作奶茶的步骤在这个接口里。

之后创建不同的类实现先创建的接口,表示某种奶茶的具体制作过程。

之后创建工厂类,在工厂类里,客人要点什么奶茶就做什么奶茶。

最后,客人点单,具体喝什么奶茶就使用什么方法的调用。也算是做到了一个客户与程序之间的交互。

可能有人会疑问,为什么要写接口,为什么要写不同的奶茶类,试想,如果我们把所有实现奶茶的方法放在一个类里,让客户去从一个类里调用,判断条件会变多,代码也会变得很长,我们在寻找这些条件的时候,运行速度可能会变慢。所以当我们想要添加新口味时,前者需要修改同一个类,而后者只需扩展,不会影响已有代码。

这里也运用了多态的思想,客户点奶茶时,无论奶茶是原味、芒果还是其他类型,客户端都通过同一个接口方法(如prepare())操作。本质就是,不同奶茶类(Original、Mango)对这些方法有不同的实现。

经常面试题里会出现多态这样的提问,我以前会说,就像水,可以是冰,可以是气。现在我明白了,就像奶茶有不同的口味,就像王者荣耀的英雄同样是攻击却有不同的伤害技能,可以是物理攻击可以是法术攻击。

接下来我们来具体实现,观察工厂模式的作用。

至少需要5个文件来实现。

1.定义同一种类的行为模式

package org.factory;public interface MilkTea {void prepare();//准备材料void make();//制作奶茶void pack();//打包
//这里介绍了奶茶的制作过程
}

2.具体类里是做什么

我们实现接口,注意:接口是实现,类是继承

这是经典珍珠奶茶

package org.factory;public class OriginalMilkTea implements MilkTea{
//    代码实现流程:/*** 1.创建接口* 2.实现接口* 3.创建工厂类,在工厂中实现制造奶茶* 4.卖给客户不同的奶茶* 我们可以创建很多类似的类去表示不同奶茶的制作方式*/@Overridepublic void prepare() {
//    如果我要做珍珠奶茶 珍珠、红茶、牛奶System.out.println("正在准备材料");System.out.println("准备好材料:珍珠、红茶、牛奶");}@Overridepublic void make() {System.out.println("正在制作中");}@Overridepublic void pack() {System.out.println("用经典杯打包原味奶茶!");}
}

这是红糖姜茶

package org.factory;public class RedSugarTea implements MilkTea{/*此类我表示的是红糖姜茶*/@Overridepublic void prepare() {System.out.println("准备材料!");System.out.println("红糖、生姜、红茶");}@Overridepublic void make() {System.out.println("混合熬煮中!");}@Overridepublic void pack() {System.out.println("使用夏季限定包装杯包装!");}/**/
}

3.工厂模式来进行判断生产

package org.factory;public class MilkFactory {
//    工厂里面制作奶茶
//    客人需要什么奶茶我就制作什么奶茶,所以需要我们定义一个函数public static MilkTea createTea (String type){if ("Orginal".equalsIgnoreCase(type)){return new OriginalMilkTea();}else if ("RedSugar".equalsIgnoreCase(type)){return new RedSugarTea();}throw new IllegalArgumentException("没有这种口味的奶茶");}
}

4.客户需要去定义 这里也相当于一个测试类  假如需要喝红糖姜茶

package org.factory;public class Customer {public static void main(String[] args) {MilkTea RedSugar =MilkFactory.createTea("RedSugar");RedSugar.prepare();RedSugar.make();RedSugar.pack();}
}

接口就是一个数据类型,后面是一个对象,用对象去调用接口里面的方法,再识别出需要去调用哪个方法。

三、 抽象工厂模式

生活实例:苹果 vs 小米全家桶

假设你要购买一套电子产品,包含 手机 和 耳机。不同品牌(如苹果、小米)的产品风格不同,且手机和耳机需要配套使用(比如苹果手机和AirPods配对更佳)。
抽象工厂模式 就是用来生产 同一品牌系列产品 的解决方案,确保你获得的手机和耳机是同一品牌风格,避免混搭不兼容。

上一个我们讲的是制作奶茶的过程,但是只是同一款产品,那就是奶茶。

但是在此设计模式中,可以同时制造耳机和手机,只需要多设计一些接口和实现类。生产 多个相关联的产品(比如同时生产手机+耳机+手表,且保证同一品牌)。

四、适配器模式

顾名思义,适配器模式主要用于解决接口不兼容的问题,让原本无法一起工作的类可以协同工作。生活中常见的例子是电源适配器,比如不同国家的插头标准不同,适配器可以让不同插头在同一个插座上使用。

其实我觉得这个虽然好理解,但是我感觉代码是不好理解的。

本质是功能不同的两个类,转为功能相同的一个类。

实际例子:现在有英语导游给中国人导游 为了让中国人听得懂外语 ,   发明了耳机适配器.

代码实现:先明确输出的是中文,英文是英语导游说的,适配器是将英语导游的话转换为中文。

1.定义一个接口

package org.adapter;public interface ChineseSpeaker {
//    我们是将英文翻译成中文 这里定义的是说中文的能力String ChineseAbility();
}

2.英语导游说英语

package org.adapter;public class EnglishGuide {public String SpeakEnglish(){return "Hello !This is a GreatWall";}
}

3.,首先让值传入翻译器中,翻译之后,传出来

package org.adapter;public class Translator implements ChineseSpeaker{private EnglishGuide guide;public Translator(EnglishGuide guide){this.guide=guide;}
//   翻译功能,要先传入英文,再让其转换为中文@Overridepublic String ChineseAbility() {String message = guide.SpeakEnglish();
// replace原来的值    想要替换的值return message.replace("Hello !This is a","你好!这里是");
//        System.out.println(replace(message,"Hi,这里是"));}
}

4.给翻译器传入值,调用接口实现的函数

package org.adapter;public class Test {public static void main(String[] args) {EnglishGuide englishGuide = new EnglishGuide();Translator translator = new Translator(englishGuide);System.out.println(translator.ChineseAbility());}
}

五、策略模式

这个模式很好理解,面对同一个类有不同的策略,假如你想买车,不考虑预算,你想买什么车就买什么车,可以买白色宝马,灰色宝马,黑色宝马,黑色奔驰,灰色奔驰,白色小米,黄色小米,等等。任意搭配。我们把所有能够组成的策略的类都生成出来。代码文件很多。

需要定义的东西很多,我们需要把不同属性作为接口抽出来,比如颜色,品牌,油型。

假如我们想要买白色特斯拉用98的油型。就是这样的代码。

package org.strategy;public abstract class Car {
//    Car  颜色、油、品牌protected Color color;protected Soil soil;protected Brand brand;protected void info() {String info = color.color() + brand.Brand() + ": " + soil.soil();System.out.println(info);}}
package org.strategy;public class tebaiba extends Car{public tebaiba() {color = new WhiteImp();brand = new Tesila();soil = new Ninety_eight();}
}
package org.strategy;public class Test {public static void main(String[] args) {Car tebaiba = new tebaiba();tebaiba.info();}
}


White特斯拉: 98

在考试的时候最有可能挖空就是挖 

第一个空一般都是接口或者抽象类。

六、装饰器模式

我使用了DeepSeek预测,今年最可能考的就是装饰器模式和代理模式。

不修改原有对象,只是将原有对象进行加工。

这里我们使用(咖啡加料系统)来进行学习。

假如普通咖啡10元,不加糖。假如我们这里有两种操作。

A类咖啡:我们需要加椰果来增加口感,价格就变成了12元。

B类咖啡:我们需要加牛奶来增加醇香,价格就变成了15元。

1.       接口类

package org.Decorator;public interface CoffeOpertion {
//    1.对咖啡进行加料String addSugar();
//    2.对咖啡进行加价double price(double money);
}

2.       实现普通咖啡类

package org.Decorator;public class SimpleCoffe implements CoffeOpertion{
//    我们首先要知道普通咖啡是什么规格,再进行装饰@Overridepublic String addSugar() {return "无糖";}@Overridepublic double price(double money) {return 10.0;}
}

3.       抽象装饰类

package org.Decorator;
//抽象装饰类
public abstract class CoffeeDecorator implements CoffeOpertion {protected CoffeOpertion coffeOpertion;
//    这个是装饰类,你要把装饰的对象传进来public CoffeeDecorator(CoffeOpertion coffeOpertion){this.coffeOpertion=coffeOpertion;}}

4.       A操作

package org.Decorator;public class CoffeDecoratorA extends CoffeeDecorator{public CoffeDecoratorA(CoffeOpertion coffeOpertion) {super(coffeOpertion);}@Overridepublic String addSugar() {return "A类咖啡饮品,额外加椰果";}@Overridepublic double price(double money) {money = money+2;return money;}
}

5.B操作

package org.Decorator;public class CoffeDecoratorB extends CoffeeDecorator{public CoffeDecoratorB(CoffeOpertion coffeOpertion) {super(coffeOpertion);}@Overridepublic String addSugar() {return "拿铁咖啡,额外加牛奶";}@Overridepublic double price(double money) {money =money+5;return money;}
}

6.       实现类

package org.Decorator;public class Coffeshop extends SimpleCoffe{public static void main(String[] args) {SimpleCoffe simple = new SimpleCoffe();String sugar=simple.addSugar();double money = simple.price(10);System.out.println("这是普通咖啡的规格:"+sugar + ",价格" + money);CoffeOpertion coffeOpertionA= new CoffeDecoratorA(new SimpleCoffe());CoffeDecoratorB coffeDecoratorB = new CoffeDecoratorB(new SimpleCoffe());System.out.println(coffeOpertionA.addSugar()+"价格为"+coffeOpertionA.price(10));System.out.println(coffeDecoratorB.addSugar()+"价格为"+coffeDecoratorB.price(10));}
}

这是运行结果

考点:会挖空super(coffeOpertion);

在子类的构造函数里一定要强调父类。

    public CoffeDecoratorA(CoffeOpertion coffeOpertion) {super(coffeOpertion);
//        this.coffeOpertion=coffeOpertion;}

七、代理模式

为什么必须要使用代理对象?

1.隐藏复杂性 :代理对象可以控制何时、如何创建真实对象(例如延迟加载)

2.功能扩展:代理可以在不修改真实对象的前提下,添加额外逻辑(缓存、权限验证)

3.接口透明:客户端无需知道背后是代理还是真实对象,只需调用统一接口

和装饰器模式的区别

  • 代理:控制访问,侧重隐藏真实对象(如权限控制)。

  • 装饰器:动态扩展功能,侧重增强(如咖啡加糖)。

代码实现:一个真实的类,一个代理真实的类,一个抽象的接口,代理与真实的类拥有相同的功能。在特定条件下被实现,比如,老师今天感冒不能上课就由课代表代表上课。

2

1.

package org.Proxy;public interface Lesson {String CanLesson();
}

2.

package org.Proxy;public class RealTeacher implements Lesson{@Overridepublic String CanLesson() {return "老师正在朗读荷塘月色";}
}

3.

package org.Proxy;public class StudentProxy implements Lesson{
//    关键:持有真实对象的引用protected RealTeacher teacher;
//    构造函数public StudentProxy(){this.teacher = new RealTeacher();}
//    添加检查权限,没有老师的时候学生上课public boolean checkPermission(){return false;}@Overridepublic String CanLesson() {// 添加代理逻辑(示例:权限校验)if (checkPermission()) {return "老师在,老师上课:\n" +teacher.CanLesson();} else {return "老师不在学生代替老师上课,语文课代表正在朗读荷塘月色\n";}}
}

4.

package org.Proxy;public class Test {public static void main(String[] args) {StudentProxy proxy = new StudentProxy();String message= proxy.CanLesson();System.out.println(message);}
}

对我来说写多了,好像调用来调用去没什么区别哈哈。。。

相关文章:

25年上半年五月之软考之设计模式

目录 一、单例模式 二、工厂模式 三、 抽象工厂模式 四、适配器模式 五、策略模式 六、装饰器模式 ​编辑 考点:会挖空super(coffeOpertion); 七、代理模式 为什么必须要使用代理对象? 和装饰器模式的区别 八、备忘录模式 一、单例模式 这个…...

Mongo DB | 多种修改数据库名称的方式

目录 方法一:使用 mongodump 和 mongorestore 命令 方法二:使用 db.copyDatabase() 方法 方法三:使用 MongoDB Compass 在 MongoDB 中,更改数据库名称并不是一个直接的操作,因为 MongoDB 不提供直接重命名数据库的命…...

QListWidget的函数,信号介绍

前言 Qt版本:6.8.0 该类用于列表模型/视图 QListWidgetItem函数介绍 作用 QListWidget是Qt框架中用于管理可交互列表项的核心组件,主要作用包括: 列表项管理 支持动态添加/删除项:addItem(), takeItem()批量操作:addItems()…...

Python类属性与实例属性的覆盖机制:从Vector2d案例看灵活设计

类属性与实例属性的交互机制 Python中类属性与实例属性的关系体现了语言的动态特性。当访问一个实例属性时,Python会首先查找实例自身的__dict__,如果找不到,才会去查找类的__dict__。这种机制使得类属性可以优雅地作为实例属性的默认值。 …...

QML与C++交互2

在QML与C的交互中,主要有两种方式:在C中调用QML的方法和在QML中调用C的方法。以下是具体的实现方法。 在C中调用QML的方法 首先,我们需要在QML文件中定义一个函数,然后在C代码中调用它。 示例 //QML main.qml文件 import QtQu…...

EtherNet/IP机柜内解决方案在医疗控制中心智能化的应用潜能和方向分析

引言 在数智化转型浪潮席卷各行各业的今天,医疗领域同样面临着提升运营效率、改善患者体验和加强系统可靠性的多重挑战。Rockwell Automation于2025年5月20日推出的EtherNet/IP机柜内解决方案,为医疗中心的自动化升级提供了一种创新路径。本报告将深入分析这一解决方案的核心…...

springboot中各模块间实现bean之间互相调用(service以及自定义的bean)

springboot中各模块间实现bean之间互相调用(service以及自定义的bean) https://blog.csdn.net/qq_29477175/article/details/122827446?ops_request_misc&request_id&biz_id102&utm_termspringboot%E5%A4%9A%E6%A8%A1%E5%9D%97%E4%B9%8B%E…...

RabbitMQ 可靠性保障:消息确认与持久化机制(二)

四、持久化机制:数据安全的护盾 (一)交换机持久化 交换机持久化是确保消息路由稳定的重要保障 。在 RabbitMQ 中,交换机负责接收生产者发送的消息,并根据路由规则将消息路由到相应的队列 。如果交换机在 RabbitMQ 重…...

QML学习07Property

Property 1、Property1.1 定义控件1.2 给控件取别名,不向外暴露控件名字 2、总结 1、Property property int myTopMargin: 0 property int myBottomMargin: 0 property real myReal: 0.0 //双精度浮点数 property string myString: "test" property…...

Skywalking安装部署使用教程

目录 核心功能 架构设计 安装与配置 使用场景 社区与支持 总结 官网 https:///apache/skywalking 部署Skywalking 添加报警配置 自定义告警规则如果您需要自定义告警规则,则需要编辑 alarm-settings.yml 文件并添加自定义的规则。具体来说,您需要按照 YAML 格式定义…...

网络编程与axios技术

网络编程技术是指通过计算机网络实现不同设备之间数据交互的技术。它基于网络通信协议(如TCP/IP、HTTP)和编程语言的支持,结合库和API实现高效的数据传输与通信。以下是几种主流技术栈(JavaScript、TypeScript、React、Next.js、P…...

【结构设计】以3D打印举例——持续更新

【结构设计】以立创EDA举例——持续更新 文章目录 [TOC](文章目录) 前言立创EDA官网教程一、3D外壳绘制二、3D外壳渲染三、3D外壳打印1.3D打印机——FDM2.3D打印机——光固化 四、3D外壳LOG设计1.激光雕刻机 总结 前言 提示:以下是本篇文章正文内容,下面…...

MySQL中的重要常见知识点(入门到入土!)

基础篇 基础语法 添加数据 -- 完整语法 INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);-- 示例 insert into employee(id,workno,name,gender,age,idcard,entrydate) values(1,1,Itcast,男,10,123456789012345678,2000-01-01) 修改数据 -- 完整语法 UPDA…...

理解全景图像拼接

1 3D到2D透视投影 三维空间上点 p 投影到二维空间 q 有两种方式:1)正交投影,2)透视投影。 正交投影直接舍去 z 轴信息,该模型仅在远心镜头上是合理的,或者对于物体深度远小于其到摄像机距离时的近似模型。…...

云原生安全基石:Linux进程隔离技术详解

🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 进程隔离是操作系统通过内核机制将不同进程的运行环境和资源访问范围隔离开的技术。其核心目标在于: 资源独占:确保…...

基于PySide6与pycatia的CATIA几何阵列生成器开发实践

引言:参数化设计的工业价值 在航空航天、汽车制造等领域,复杂几何图案的批量生成是模具设计与机械加工的核心需求。传统手动建模方式存在效率低下、参数调整困难等问题。本文基于PySide6+pycatia技术栈,实现了一套支持​​动态参数配置​​、​​智能几何阵列生成​​的自动…...

Linux学习心得问题总结(三)

day09 文件权限篇 文件权限的属性有哪些?我们应如何理解这些属性? 文件权限的属性包括可读(r)、可写(w)、可执行(x)三种权限,根据文件类型可分为普通文件(.…...

蓝桥杯国14 不完整的算式

!!!!!!!!!!!!!!!理清思路 然后一步步写 问题描述 小蓝在黑板上写了一个形如 AopBC 的算式&#x…...

Anthropic推出Claude Code SDK,强化AI助理与自动化开发整合

Anthropic发布Claude Code SDK,协助开发团队将人工智慧助理整合进自动化开发流程,支援多轮对话、MCP协定及多元格式。 Anthropic推出Claude Code SDK,提供开发者与企业一套可程序化整合Claude AI助理至开发流程的工具。此SDK以命令列介面为基…...

6.4.1最小生成树

知识总览 生成树(一定是连通的): 是连通的无向图的一个子图,子图包含这个无向图的所有顶点有n-1条边(少一条边,生成树就不连通了)即为生成树,一个连通图可能有多个生成树 最小生成树(最小代价树): 只有连通的无向图才…...

DAY 33

知识点回顾: 1. PyTorch和cuda的安装 2. 查看显卡信息的命令行命令(cmd中使用) 3. cuda的检查 4. 简单神经网络的流程 a. 数据预处理(归一化、转换成张量) b. 模型的定义 i. 继承nn.Module类 ii. 定义…...

基于ICEEMDAN-SSA-BP的混合预测模型的完整实现过程

以下将为您详细阐述基于ICEEMDAN-SSA-BP的混合预测模型的完整实现过程,包含原理说明、算法实现、代码解析及优化策略。本教程分为六个核心部分,采用Python 3.9环境开发。 基于ICEEMDAN-SSA-BP的混合时间序列预测模型 一、模型架构设计 1.1 整体流程 #mermaid-svg-o4UD3HaTm…...

常见排序算法详解及其复杂度分析

常见排序算法详解及其复杂度分析 排序算法是数据结构与算法学习中的基础内容,也是面试高频考点。本文将系统介绍几种常见的排序算法,包括它们的原理、时间复杂度、空间复杂度以及 Python 实现方法。 一、冒泡排序(Bubble Sort) …...

DARLR用于具有动态奖励的推荐系统的双智能体离线强化学习(论文大白话)

1. 概述 离线强化学习是现在强化学习研究的一个重点。相比与传统的强化学习它不需要大量的实时交互数据,仅仅依赖历史交互日志就可以进行学习。本文就是将离线强化学习用于推荐系统的一篇文章。 这篇文章主要解决的核心问题有以下几个: 1)…...

第35节:PyTorch与TensorFlow框架对比分析

引言 在深度学习领域,PyTorch和TensorFlow无疑是当前最受欢迎的两大开源框架。 自2015年TensorFlow由Google Brain团队发布,以及2016年Facebook的AI研究团队推出PyTorch以来,这两个框架一直在推动着深度学习研究和工业应用的发展。 本文将从多个维度对这两个框架进行详细对…...

企业级智能体 —— 企业 AI 发展的下一个风口?

在AI技术迅猛发展的当下,企业对AI的应用不断深入。企业级智能体逐渐受到关注,它会是企业AI发展的下一个风口吗?先来看企业典型的AI应用场景,再深入了解企业级智能体。 企业典型AI应用场景 1. 内容生成:2025年&#xf…...

【软考向】Chapter 2 程序设计语言基础知识

程序设计语言概述低级语言 —— 机器指令、汇编语言高级语言 ——翻译:汇编、解释和编译语言处理程序基础 —— 翻译给计算机,汇编、编译、解释三类编译程序基本原理 —— 词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成文法和语言的形式描述确定的有限…...

JavaWeb:SpringBootAOP切面实现统计方法耗时和源码解析

介绍 快速入门 1.导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>2.切面类java Slf4j Aspect Component public class RecordTimeApsect {/*** 统计耗…...

RabbitMQ的其中工作模式介绍以及Java的实现

文章目录 前文一、模式介绍1. 简单模式2. 工作队列模式3. 广播模式4. 路由模式5. 通配符模式6. RPC模式7. 发布确认模式 二、代码实现1、简单模式2、工作队列模式生产者消费者消费者 1消费者 2 3、广播模式 (Fanout Mode)生产者消费者 4、路由模式 (Direct Mode)生产者消费者 5…...

vue2项目搭建

作者碎碎念&#xff1a;开历史倒车了&#xff0c;没想到不兼容&#xff0c;只能从vue3->vue2了。 1 vue3和vue2 这部分参考了官网的《vue3迁移指南》&#xff1a;Vue 3 的支持库进行了重大更新。以下是新的默认建议的摘要: 新版本的 Router, Devtools & test utils 来…...