《设计模式》创建型模式总结
目录
创建型模式概述
Factory Method: 唯一的类创建型模式
Abstract Factory
Builder模式
Prototype模式
Singleton模式
最近在参与一个量化交易系统的项目,里面涉及到用java来重构部分vnpy的开源框架,因为是框架的搭建,所以会涉及到像事件驱动等设计模式的应用,因此不了解基础的设计模式就无法理解框架设计者对于各个模块以及类的设计,而这也正是我目前所欠缺的能力,之前看到大多数是偏编码规则。这也是我选取《设计模式-可复用面向对对象软件的基础》这本书进行学习的原因。
这本书分为三个部分,第一部分是导论,虽然说是导论但我觉得更多的是跳出设计模式的细节来看它具体在实际项目中的工作流以及一些设计模式的抽象概念,这部分我自认为对于一个对于各个设计模式没有实际实现经验的初学者来说过于抽象,所以我准备放到最后来看,可能会有更深的体会。第二部分是一个案例学习,带领你从零开始构建一个文本编辑器的设计模式并实现它,这部分我准备放在第二部分来看。第三部分就是介绍了三类23个典型的设计模式,这部分我准备首先了解,以期对于设计模式先有一个更加深刻的认识。
这篇文章先总结一下三类设计模式中的第一类-创建型模式。
创建型模式概述
首先,要明确这里所说的设计模式重点聚焦系统级的设计,系统本质上是一组类实例的组合,它区别于单一的类或者接口的设计,需要一定的抽象才能够使得后续的编码工作更有效率。创建型模式的最主要作用就是抽象了系统的实例化过程。在没有这种抽象之前,系统的实例化只是单纯的被看作是N个类的实例化,不利于后续的编码执行。
这种抽象在大体上有两个实现的原则。其一是将系统所需要用到的各种类信息给封装起来;其二是把这些类的创建和组合方式也给隐藏起来。总之,整个系统有一套抽象的接口负责统一和外部对接。至于这个系统内各个类的配置可以是静态的(在编译时固定,也就是类创建型模式),或者是动态的(在运行时指定,也就是对象创建型模式)。换句话说,类创建模式就是为每一个系统预先定义好类的框架,每一个框架通过特定的方法完成创建;而对象创建型模式就是没有预先定义系统类框架,系统是在程序执行中动态完成创建的。
Factory Method: 唯一的类创建型模式
Factory Method(工厂方法)首先针对目标系统定义一个抽象接口,然后让具体的系统实现类来决定如何实例化,它要求使用者必须先定义系统类的框架,而把类的实例化延迟到了子类。
文中作者举了一个案例,对于要设计一套面向不同文件类型的应用处理系统,比如对于图像文件要有图像文件处理系统,对于文本文件要有文本文件处理系统等等。
首先我预先定义好文件类型的接口以及具体的实现类:
//抽象接口
public interface Document {void open();void close();void save();
}//实现类FigureDocument
public class FigureDocument implements Document {@Overridepublic void open() {System.out.println("Opening Figure Document");}@Overridepublic void close() {System.out.println("Closing Figure Document");}@Overridepublic void save() {System.out.println("Saving Figure Document");}}//实现类TextDocument
public class TextDocument implements Document {@Overridepublic void open(){System.out.println("Text Document Opened");}@Overridepublic void close() {System.out.println("Text Document Closed");}@Overridepublic void save() {System.out.println("Text Document Saved");}}
很显然在Application抽象实例中,它是不知道要在什么情况下创建什么文件的,因此我们针对每一个文档都设计对应的application实现类,并且重构对应的document工厂方法:
//抽象接口
public interface Application {Document createDocument();
}//实现类FigureApplication
public class FigureApplication implements Application {@Overridepublic Document createDocument() {return new FigureDocument();}}//实现类TextApplication
public class TextApplication implements Application {public Document createDocument(){return new TextDocument();}
}
然后客户端就可以轻松针对不同的application实现来针对性的创建对应的文件:
public class Client {public static void main(String[] args) {Application figureApplication = new FigureApplication();Document figuredoc = figureApplication.createDocument();figuredoc.open();}
}
优点
首先我认为设计模式的优点最主要还是要面向系统框架的应用开发人员,他们是不是能在系统稳定的前提下简化并且高效率的使用这个框架是核心。从这个维度上说工厂模式有以下的优点:它将具体的系统类构建和它的抽象框架分离。使得应用开发人员除了在实例化的时候要关注创建的系统实例类型,其余时候都只需要对着抽象框架的接口来编程。
除此以外,工厂方法有两个注意事项:
工厂方法可以为子类提供一个扩展功能的钩子:通过在AbstractCreator的抽象工厂方法内提供一个缺省的实现,可以扩展工厂方法的功能,并且这个扩展的功能是可以根据不同的子类进行变化的(当然这个缺省的实现要通过super继承到子类的工厂方法中)。
连接系统外的类:只要是引入了对应工厂方法的类,相当于都整体纳入了你所设计系统的抽象体系当中,要注意在设计层面抽象的颗粒度。
Abstract Factory
Abstract Factory(抽象工厂设计模式)是对象创建型的,所以它并不是预先定义好系统的类框架,而是通过设计一个工厂体系来承担起系统的创建,每一个工厂里都包含了组成系统所必需组件的不同实现方式:
相比于工厂方法设计模式,抽象工厂设计模式面向需要更加灵活的系统设计,同时系统的各个组件未来预计会有大量优化迭代需求。比如文中举例的多视感用户界面应用,每一个应用实例内的组件都会有个性化的视觉需求,而且这种视觉需求需要不停的进行优化迭代,以期保持产品的竞争力。 这种场景里,再使用工厂方法在每一个子类中对每一个组件进行硬编码就显得过于耦合,也不利于后续各组件的优化迭代。
在具体实现时,首先对于系统的各个核心组件定义抽象接口(而不再是系统的抽象接口):
//核心组件Button的抽象接口
public interface Button {void paintButton();void clickButton();...
}//核心组件TextBox的抽象接口
public interface TextBox {void paintTextBox();...}
随后分别去实现不同风格的组件实例:
public class MacOSButton implements Button {private String ButtonName;public MacOSButton(String ButtonName){this.ButtonName = ButtonName;}public void paintButton(){System.out.println("MacOS Button " + ButtonName + " painted");}public void clickButton(){System.out.println("MacOS Button " + ButtonName + " clicked");}
}public class WindowsButton implements Button{private String ButtonName;public WindowsButton(String ButtonName){this.ButtonName = ButtonName;}public void paintButton(){System.out.println("Windows Button: " + ButtonName);}public void clickButton(){System.out.println("Windows Button: " + ButtonName + " is clicked");}
}public class MacOSTextBox implements TextBox{private String TextBoxName;public MacOSTextBox(String TextBoxName){this.TextBoxName = TextBoxName;}public void paintTextBox(){System.out.println("Paint MacOS TextBox: " + TextBoxName);}}public class WindowsTextBox implements TextBox {private String textBoxName;public WindowsTextBox(String textBoxName){this.textBoxName = textBoxName;}public void paintTextBox(){System.out.println("Paint Windows TextBox: " + textBoxName);}
}
随后定义相应的工厂体系,明确对于每一种风格的系统组件实现的组合:
//抽象工厂接口
public interface GUIFactory {Button createButton(String ButtonName);TextBox createTextBox(String TextBoxName);}//工厂实例1
public class MacOSFactory implements GUIFactory{public Button createButton(String ButtonName){return new MacOSButton(ButtonName);}public TextBox createTextBox(String TextBoxName){return new MacOSTextBox(TextBoxName);}}//工厂实例2
public class WindoxsFactory implements GUIFactory{public Button createButton(String ButtonName){return new WindowsButton(ButtonName);}public TextBox createTextBox(String TextBoxName){return new WindowsTextBox(TextBoxName);}
}
在客户端具体实现时可以直接通过统一的抽象方法来创建相应的组件,实现应用开发者接口调用的无感化:
public class Application {public static void main(String[] args) {GUIFactory macOSFactory = new MacOSFactory();Button buttonA = macOSFactory.createButton("ButtonA");//工厂方法无需具像化到特定组件类型TextBox textBoxA = macOSFactory.createTextBox("TextBoxA");//工厂方法无需具像化到特定组件类型buttonA.paintButton();textBoxA.paintTextBox();}}
优点
其实前文也说明了,抽象工厂设计模式抛弃了系统类的设计,将系统打散成了各个核心的组件来单独设计,最后通过工厂方法将对应的组件实例串起来。可以看出,这个设计模式更加的灵活,体现在一下几个方面:
- 使得产品系列的切换变得非常容易:基于上面的案例,可以更进一步在应用类中设置一个应用的创建方法,将工厂类作为输入,就可以实现只要一个工厂方法就可以完成一套系统所有组件的部署,只要改变一个工厂方法,整个应用的所有组件就会立刻完成变化。
- 有利于产品的一致性:同一个工厂方法定义了所有组件的一套版本,不会出现不同版本冲突的问题。
缺点:
- 难以支持新的组件:新的组件首先要在抽象工厂方法中定义创建接口,然后在每一个具体的工厂方法中分别实现,所需要的工作量比较大。
Builder模式
Builder模式侧重于实现系统的创建流程和具体的系统实例分离,也就是可以实现用一套创建流程来创建多种系统实例。细心的朋友们可以发现,这个功能Abstract Factory也可以实现,其实整体来看这两种方法非常的相似,都是尝试用一层抽象来统一不同系统实例的创建,但是两者存在一些差异,这个放到最后再说。
从这张架构图就可以看出来builder模式和Abstract Factory的相似性之高(Director这个角色在Abstract Factory中也完全可以设置,就是其优点第一点中所提的应用类中一个应用的创建方法)。而且Builder的设计与Factory也非常的类似,我把文中两个关于Maze的C++抽象类贴出来,大家可以对比一下:
首先对于各个组件的构建没有太大的区别,只不过Factory提供了缺省的实现,并且提供了公有的构造器(其实我觉得用protected未尝不可,这是一个抽象类,也不应该被子类之外来调用,大家批评指正)。
唯一MazeBuilder多的是GetMaze()这个方法,他会返回一个完整的Maze对象,这是Factory所没有的,这就引出了这两者最主要的区别,Builder实例中是带着系统实例的,但是Factory实例中没有,它只是单纯的工厂方法集合。 这就决定了他们创建系统实例的逻辑是不一样的,如下对比所示
factory方法是先创建maze实例,然后通过其他的工厂方法去为这个系统实例添加各个组件,而builder模式是一步一步的构建各个组件,最终通过get方法获取系统实例(其实再往下看一层,builder也是先创建系统实例,但是在面向应用开发接口的具体实现这一层(CreateMaze的方法实现),他是最后才获得系统实例,所以如果要说本质的区别,那就是builder相对于abstract factory再抽象了一层吧,至于这一层的抽象有没有意义,我觉得有吧,至少看着更简洁了一点,就像老马的猛禽1和猛禽3)。
知道了这个区别以后就不难理解builder模式的几个优势:
- 面向应用开发人员将系统的创建流程和具体的系统实例分离(这一点Abstract Factory也可以做到)
- 可以使得应用开发人员对于系统实例的创建过程进行更加精确的控制,并且这个控制相对于其他的创建模式更加简洁
Prototype模式
Prototype(原型)模式的核心就是用原型实例指定创建对象的种类,并且通过拷贝的方式创建这些原型新的对象。
原型模式主要应用于系统的各组件需要保持一致的场景,通过克隆的方式高效的保证所有组件都是一致的。
书中举了一个乐谱编辑器的例子,这个编辑器的主要处理对象当然是乐谱,以及在乐谱上的各种音符(以全音符和二分音符为例),这三个东西都是有标准的不会因为不同的乐谱对象而改变。所以适合使用原型模式。
首先定义这三个实现类及其接口,可以看到他们都实现了clone方法(建议还是要自定义一个接口,而不要直接使用Clonable接口,应用研发人员不易看懂):
//组件接口
public interface Graphic {void draw(Position position);Graphic clone();
}//乐谱实现类
public class Staff implements Graphic {public void draw(Position position){System.out.println("Drawing staff at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}
}//半分音符实现类
public class HalfNote implements Graphic{public void draw(Position position){System.out.println("Drawing HalfNote at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}
}//全音符实现类
public class WholeNote implements Graphic {public void draw(Position position){System.out.println("Drawing WholeNote at line "+position.getLineCount()+", column "+position.getColumnCount());}public Graphic clone(){try {return (Graphic) super.clone();} catch (Exception e) {throw new AssertionError("Clone not supported");}}}
然后可以直接定义一个工厂方法,通过已经实现的组件实例作为输入来调用他们的clone方法,达到创建的目的:
public class GraphicCreateFactory{private Staff staff;private WholeNote wholeNote;private HalfNote halfNote;public GraphicCreateFactory(Staff staff, WholeNote wholeNote, HalfNote halfNote){this.staff = staff;this.wholeNote = wholeNote;this.halfNote = halfNote;};public Staff createStaff(){return staff.clone();}public WholeNote createWholeNote(){return wholeNote.clone();}public HalfNote createHalfNote(){return halfNote.clone();}}
所以客户端可以预先创建原型后,作为参数调用相应的创建方法。
Singleton模式
Singleton(单例)模式在之前Effective Java中也介绍过,就是保证一个类仅有一个实例,并且提供一个访问它的全局访问点。它要实现的目的其实和Prototype有些类似,就是要保证组件的唯一性。
下面的单例模式设计可以根据环境变量type的值来定向实例化适当的MazeFactory子类。
public class MazeFactory {private static MazeFactory instance;protected MazeFactory(){};public static MazeFactory getInstance(String type){if(instance == null){synchronized(MazeFactory.class){if (instance==null) {if (type.equals("Standard")) {instance = new MazeFactory();}if (type.equals("Bombed")) {instance = new BombMazeFactory();}}}}return instance;}public Maze createMaze(){return new Maze();}public Room createRoom(int roomNo){return new Room(roomNo);}public Wall createWall(){return new Wall();}public Door createDoor(Room room1, Room room2){return new Door(room1, room2);}
}
还有就是单例的实现方式要注意并发的访问设计,这部分在Effective Java学习笔记--单例(Singleton)及其属性的增强有涉及,这里就不展开了。
相关文章:

《设计模式》创建型模式总结
目录 创建型模式概述 Factory Method: 唯一的类创建型模式 Abstract Factory Builder模式 Prototype模式 Singleton模式 最近在参与一个量化交易系统的项目,里面涉及到用java来重构部分vnpy的开源框架,因为是框架的搭建,所以会涉及到像…...

Conda安装与使用中的若干问题记录
Conda安装与使用中的若干问题记录 1.Anaconda 安装失败1.1.问题复述1.2.问题解决(安装建议) 2.虚拟环境pip install未安装至本虚拟环境2.1.问题复述2.2.问题解决 3.待补充 最近由于工作上的原因,要使用到Conda进行虚拟环境的管理,…...

人力资源招聘系统的革新之路:从传统到智能的转变
在全球化与数字化交织的今天,企业间的竞争日益激烈,而人才作为企业发展的核心驱动力,其重要性不言而喻。传统的人力资源招聘方式,如依赖纸质简历、人工筛选、面对面面试等,不仅效率低下,且难以精准匹配企业…...

Python网络爬虫与数据采集实战——网络协议与HTTP
目录 1. HTTP协议简介 2. 常见的请求方法 3. 状态码含义 实际应用中的HTTP协议 1. 如何在爬虫中使用HTTP协议 2. 模拟浏览器请求与爬虫反爬虫技术 3. 高级HTTP请求 实现爬虫时HTTP协议的优化与常见问题 总结 1. HTTP协议简介 HTTP的定义与作用 HTTP(超文本…...

从零开始的c++之旅——二叉搜索树
1、二叉搜索树概念 1. ⼆叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树: • 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值 • 若它的右⼦树不为空,则右⼦树上所有结…...

CSS回顾-基础知识详解
一、引言 在前端开发领域,CSS 曾是构建网页视觉效果的关键,与 HTML、JavaScript 一起打造精彩的网络世界。但随着组件库的大量涌现,我们亲手书写 CSS 样式的情况越来越少,CSS 基础知识也逐渐被我们遗忘。 现在,这种遗…...

Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别
Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别 keyword 与 text 区别term 查询match 查询match_phrase 查询match_phrase_prefix 查询写在最后 在讲述 es 查询时 term、match、match_phrase、match_phrase_prefix 的区别之前,先来了…...
低代码平台:跨数据库处理的重要性与实现方式
一、低代码平台概述 低代码平台作为一种创新的软件开发工具,为开发者带来了极大的便利。它具备可视化编程工具和大量预构建组件,这使得开发者无需编写大量代码就能创建应用程序,显著降低了软件开发的技术门槛。无论是专业开发人员还是业务人员…...
【jvm】如何破坏双亲委派机制
目录 1.说明2.重写ClassLoader的loadClass方法2.1 原理2.2 实现步骤2.3 注意事项 3.使用线程上下文类加载器3.1 原理3.2 实现步骤3.3 应用场景 4.利用SPI机制4.1 原理4.2 实现步骤4.3 应用场景 5.Tomcat等容器的自定义类加载器5.1 原理5.2 实现方式5.3 应用场景 1.说明 1.双亲委…...

ReactPress与WordPress:一场内容管理系统的较量
ReactPress Github项目地址:https://github.com/fecommunity/reactpress WordPress官网:https://wordpress.org/ ReactPress与WordPress:一场内容管理系统的较量 在当今数字化时代,内容管理系统(CMS)已成为…...

网络安全练习之 ctfshow_web
文章目录 VIP题目限免(即:信息泄露题)源码泄露前台JS绕过协议头信息泄露robots后台泄露phps源码泄露源码压缩包泄露版本控制泄露源码(git)版本控制泄露源码2(svn)vim临时文件泄露cookie泄露域名txt记录泄露敏感信息公布内部技术文档泄露编辑器…...
在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别
在 Service Worker 中,caches.put(request, response) 和 caches.add(request)/caches.addAll(requests) 方法都用于将资源添加到缓存中,但它们的使用场景和目的略有不同。 caches.put(request, response),一用在fetch事件当中,由…...

UNIAPP发布小程序调用讯飞在线语音合成+实时播报
语音合成能够将文字转化为自然流畅的人声,提供100发音人供您选择,支持多语种、多方言和中英混合,可灵活配置音频参数。广泛应用于新闻阅读、出行导航、智能硬件和通知播报等场景。 在当下大模型火爆的今日,语音交互页离不开语音合…...

跳房子(弱化版)
题目描述 跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下: 在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内…...
ubuntu22 安装 minikube
在Ubuntu 22上安装Minikube,你可以按照以下步骤进行: 安装依赖: 更新系统并安装必要的依赖项: sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl安装Docker: Minikube可以使用D…...
STM32 | 超声波避障小车
超声波避障小车 一、项目背题 由于超声波测距是一种非接触检测技术,不受光线、被测对象颜色等的影响,较其它仪器更卫生,更耐潮湿、粉尘、高温、腐蚀气体等恶劣环境,具有少维护、不污染、高可靠、长寿命等特点。因此可广泛应用于…...

打造旅游卡服务新标杆:构建SOP框架与智能知识库应用
随着旅游业的蓬勃兴起,旅游卡产品正逐渐成为市场的焦点。为了进一步提升服务质量和客户体验,构建一套高效且标准化的操作流程(SOP)变得尤为重要。本文将深入探讨如何构建旅游卡的SOP框架,并介绍如何利用智能知识库技术…...
通过脚本,发起分支合并请求和打tag
#!/bin/bash # Set GitLab API URL and access token GITLAB_API_URL"http://IP/api/v4" ACCESS_TOKEN"Token秘钥" # Define repository IDs declare -A repo_ids( ["gitIP:kingmq/client.git"]"123" ["gitIP:kingmq/s…...

【视频讲解】Python深度神经网络DNNs-K-Means(K-均值)聚类方法在MNIST等数据可视化对比分析...
全文链接:https://tecdat.cn/?p38289 分析师:Cucu Sun 近年来,由于诸如自动编码器等深度神经网络(DNN)的高表示能力,深度聚类方法发展迅速。其核心思想是表示学习和聚类可以相互促进:好的表示会…...
网络安全在线网站/靶场:全面探索与实践
目录 1. CyberPatriot 简介 功能与特点 适用人群 2. Hack The Box 简介 功能与特点 适用人群 3. OverTheWire 简介 功能与特点 适用人群 4. VulnHub 简介 功能与特点 适用人群 5. PortSwigger Web Security Academy 简介 功能与特点 适用人群 6. TryHackM…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...

MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...