设计模式学习笔记 - 设计模式与范式 -总结:1.回顾23中设计模式的原理、背后的思想、应用场景等
1.创建型设计模式
创建型设计模式包括:单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。
1.单例模式
单例模式用来创建全局唯一的对象。一个类只允许创建一个对象(或者叫实例),那这个类就是单例类,这种设计模式叫做单例模式。单例模式有几种经典的实现方式,它们分别是饿汉式、懒汉式、双重校验、静态内部类、枚举。
尽管单例是一个很常用的设计模式,在实际的开发中,我们也确实经常用到它,但是,有些人认为单例是一种反模式(anti-pattern),并不推荐使用,主要的理由有以下几点:
- 单例对 OOP 特性的支持不友好
- 单例会隐藏类之间的依赖关系
- 单例对代码的扩展性不友好
- 单例对代码的可测试性不友好
- 单例不支持有参的构造函数
那么有什么替代单例的解决方案呢? 如果要完全解决这些问题,我们可能要从跟上寻找其他方式来实现全局唯一类。比如,通过工厂模式、IOC 容器来保证全局唯一性。
有人把单例当做反模式,主张杜绝在项目中使用,我个人觉得这个观点有点极端。模式本身没有对错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局类,我们在其他地方 new 的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。
此外,我们还讲了进程唯一单例、线程唯一单例、集群唯一单例、多例等扩展知识点,这一部分在实际的开发中并不会被用到,但是可以扩展你的思路、锻炼你的逻辑思维。
2.工厂模式
工厂模式包括简单工厂、工厂方法、抽象工厂这3种细分模式。其中,简单工厂和工厂方法比较常用,抽象工厂的应用场景比较特殊,所以很少用到。
工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的类型来决定创建哪种类型的对象。实际上,如果创建对象的逻辑并不复杂,那我们就直接通过 new 来创建对象就可以了,不需要使用工厂模式。当创建逻辑比较复杂,是一个 “大工程” 的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。
当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免一个过于庞大的类,我们推荐使用工厂方法模式,将创建逻辑拆分的更细,每个对象的创建逻辑独立到各个工厂类中。
详细点说,工厂模式的作用有下面 4 个,这也是判断要不要使用工厂模式最本质的参考标准。
- 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
- 代码复用:创建代码抽离到独立的工厂类之后可以复用。
- 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
- 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。
此外,我们还讲了工厂模式一个非常经典的应用场景:依赖注入框架,比如 Spring IOC、Google Guice,它用来集中创建、组装、管理对象,跟具体业务代码解耦,让程序员聚焦在业务代码的开发商。DI 框架已经成为了我们开发的必备框架。
3.建造者模式
建造者模式用来创建复杂对象,可以通过设置不同的可选参数, “定制化” 地创建不同的对象。建造者模式的原理和实现比较简单,重点是掌握应用场景,避免过度使用。
如果一个类有很多属性,为了避免构造函数参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任何一种,我们就考虑使用建造者模式了。
- 我们把类的必填属性放到构造函数中,强制创建对象时就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表过长的问题。我们把必填属性通过
set()方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放。 - 如果类的属性之间有依赖关系或者约束条件,我们继续使用构造函数配合
set()方法的设计思路,那这些依赖关系或者约束条件就无处安放。 - 我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能暴露
set()方法。构造函数配合set()方法来设置属性值的方式就不适用了。
4.原型模式
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段相同),在这种情况下,我们可以利用已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,已达到节省创建时间的目的。这种基于原型来创建对象的方式叫做原型模式。
原型模式有两种实现方式,深拷贝和浅拷贝。浅拷贝只会复制对象的基本数据类型和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象…而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比浅拷贝来说,更加耗时,更加耗内存空间。
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没有问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。操作非常耗时的情况下,比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝。
二、结构型设计模式
结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。
1.代理模式
代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰者模式最大的不同。一般情况下,我们让代理类和原始类实现相同的接口。但是,如果原始类没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方式来实现代理模式。
静态代理需要针对每个类多创建一个代理类,并且每个代理类的代码都有点像模板式的 “重复” 代码,增加了维护和开发成本。对于静态代理存在的问题,可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行时动态地创建原始类对应的代理类,然后再系统中用代理类替换掉原始类。
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、事务、幂等、日志。我们将这些功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。此外,代理模式还可以用在 RPC、缓存等应用场景中。
2.桥接模式
桥接模式的代码实现非常简单,但理解起来稍微有点难度,并且应用场景也比较局限,所以,相对来说,桥接模式在实际的项目中并没有那么常用,你只需要简单了解,见到能认识就可以了。
桥接模式有两种理解方式。
- 第一种理解方式是 “将抽象和实现解耦,让它们能独立开发”。这种理解方式比较特别,应用场景也不多。
- 另一种理解方式更加简单,等同于 “组合优于继承” 设计原则,这中理解方式更加通用,应用场景比较多。
不管哪种理解方式,它们的代码结构都是想同的,都是一种类之间的组合关系。
对于第一种理解方式,弄懂定义中 “抽象” 和 “实现” 两个概念,是理解它的关键。定义中的 “抽象”,指的并非 “抽象类”,而是被抽象出来的一套 “类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义种的 “实现” 来完成。而定义种的 “实现”,也并非接口实现类,而是一套独立的 “类库”。“抽象” 和 “实现” 独立开发,通过对象之间的组合关系组装在一起。
3.装饰器模式
装饰器模式主要用于解决继承过于复杂的问题,通过组合来替代继承,给原始类增强功能。这也是判断是否该用装饰器模式的一个重要的依据。
此外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这样的需求,在设计的时候,装饰器类需要跟原始类继承想同的抽象类或接口。
4.适配器模式
代理模式、装饰器模式提供的都是跟原始类想同的接口,而适配器提供跟原始类不同的接口。适配器模式是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式的实现方式有两种:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
适配器模式是一种事后补救策略,用来补救设计上的缺陷。应用这种设计模式是 “无奈之举”。如果在设计初期,我们就能规避接口不兼容的问题,那这种模式就无用武之地了。在实际的开发中,什么情况下才会出现接口不兼容呢?我总结了下面这 5 中场景:
- 封装有缺陷的接口设计
- 统一多个类的接口设计
- 替换依赖的外部系统
- 兼容老版本接口
- 适配不同格式的数据
5.门面模式
门面模式原理、实现都非常简单,应用场景比较明确。它通过封装细粒度的接口,提供组合各个细粒度接口的高层次接口,来提高接口的易用性,或者解决性能、分布式事务等问题。
6.组合模式
组合模式跟面向对象中的 “组合关系(通过组合来组装两个类)”,完全是两码事。这里的组合模式,主要是用来处理树形接口数据。正式因为其应用场景的特殊性,数据必须能表示成树形结构,这也导致了这种模式在实际的项目开发中并不那么常用。但是,一旦数据满足树形结构,应用这种设计模式就能发挥很大作用,能让代码变得非常简洁。
组合模式的设计思路,与其说是一种设计模式,倒不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示成数这种数据结构,业务需求可以通过在树上的递归遍历算法来实现。组合模式,将一组对象组织成树状结构,将单个对象和组合对象都看作树种的节点,以统一处理,并且它利用属性接口的特点,递归地处理每个子树,依此简化代码实现。
7.享元模式
所谓 “享元”,顾名思义就是被共享的单元。享元模式的意图是复用对象,节省内存,前提是享元对象是不可变对象。
具体来讲,当一个系统中存在大量重复对象时,可以利用享元模式,将对象设计成享元,在内存中只留存一份实例,供多出代码引用,这样就可以减少内存中对象的数量,以起到节省内存的目的。实际上,不仅仅相同的对象可以设计成享元,对于相似对象,也可以将这些对象中想同的部分(字段),提取出来设计成享元,让这些大量相似对象引用这些享元。
三、行为型设计模式
创建型设计模式主要解决 “对象的创建” 问题,结构型设计模式主要解决 “类或对象的组合” 问题,那行为型设计模式主要解决的是 “类或对象之间的交互” 问题。行为型模式比较多,有 11 种,它们分别是:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
1.观察者模式
观察者模式将观察者和被观察者代码解耦。观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的解耦,再或者一些产品的设计思路,都有这个模式的影子,比如邮件订阅、RSS feeds,本质上都是观察者模式。
不同的应用场景和需求下,这个模式也有截然不同的实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
- 同步阻塞是最经典的实现方式,主要是为了代码解耦;
- 异步非阻塞除了能实现代码解耦外,还能提高代码的执行效率。
- 进程间的观察者模式解耦更加彻底,一般都是基于消息队列实现,用来实现不同进程间的被观察者和观察者之间的交互。
框架的作用是隐藏实现细节,降低开发难度,实现代码复用,解耦业务与非业务代码,让程序员聚焦业务开发。针对异步非阻塞观察者模式,我们业可以将其抽象成 EventBus 框架来达到这样的效果。EventBus 翻译为 “事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。
2.模板模式
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的 “算法”,可以理解为 “业务逻辑”,并不是特指数据结构和算法中的 “算法”。这里的算法骨架是 “模板”,包含算法骨架的方法就是 “模板方法”,这也是模板方式模式名字的由来。
模板模式有两大作用:扩展和复用。
- 其中复用指的是可以复用父类中提供的模板方法的代码。
- 扩展指的是,框架通过模板模式提供的功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
此外,我们还提到了回调。它跟模板模式具有相同的作用:代码复用和扩展。在一些框架、类库、组件等的设计中经常会用到,比如 JdbcTemplate 就是用了回调。
相对于普通的函数调用,回调是一种双向调用关系。A 类实现注册某个函数 F 到 B 类,A 类在调用 B 类的 P 函数时,B 类反过来调用 A 类注册给它的 F 函数。这里的 F 函数就是 “回调函数”。A 调用 B,B 反过来调用 A,这种调用机制就叫做 “回调”。
回调可以细分为同步回调和异步回调。从应用场景上来看,同步回调看起来更新模板模式,异步回调看起来更像观察者模式。回调跟模板模式的区别,更多的是在代码实现上,而非应用场景上。回调基于组合关系来实现,模板模式基于继承关系来实现。回调比模板模式更加灵活。
3.策略模式
策略模式定义一组算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于它们的客户端(这里的客户端指的是使用算法的代码)。策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成。
策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。策略的创建由工长类来完成,封装测试创建的细节。策略模式的包含一组策略可选,客户端代码选择使用哪个策略,有两种确定方式:编译时静态确定和运行时动态确定。其中,“运行时动态确定” 才是策略模式最典型的应用场景。
在实际的项目开发中,策略模式也比较常用。最常见的应用场景时,利用它来避免冗长的 if-else 或 switch 分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。实际上,策略模式的主要作用还是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。此外,对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化、集中话代码改动,减少引入 bug 的风险。
4.职责链模式
在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完成后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫职责链模式。
在 GoF 的定义中,一旦某个处理器能处理这个请求,就不会再继续将请求传递给后续的处理器了。当然,在实际的开发中,也存在对这个模式的变体,那就是请求不会中途终止传递,而是会被所有的处理器都处理一遍。
职责链模式常用在框架开发中,用来实现过滤器、拦截器功能,让框架的使用者在不修要修改框架源码的情况下,添加新的过滤、拦截功能。这也体现了对扩展开放、对修改关闭的设计原则。
5.迭代器模式
迭代器模式也叫游标模式,它用来遍历集合对象。这里说的 “集合对象”,也可以叫 “容器” “聚合对象”,实际上就是一组包含对象的对象,比如数组、链表、树、图、跳表。迭代器模式主要作用是解耦容器代码和遍历代码。大部分编程语音都提供了现成的迭代器可以使用,我们不需要从零开发。
遍历结合的方式一种有三种:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看做迭代器遍历。相对于 for 循环遍历,利用迭代器来遍历有 3 个优势:
- 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可。
- 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
- 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。此外,因为迭代器都实现相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
通过迭代器来遍历集合元素的同时,新增或者删除集合中的元素,有可能会导致某个元素被重复遍历或遍历不到。针对这个问题,有两种比较干脆利索的解决方案,来避免出现不可预期的运行结果。一种是遍历的时候不允许增删元素,另一种是增删元素之后让遍历报错。
- 第一种解决方式比较难实现,因为很难确定迭代器使用结束的时间点。
- 第二种解决方式更加合理,Java 语言就是采用的这种解决方案。增删元素之后,选择 fail-fast 解决方式,让遍历操作直接抛出运行时异常。
6.状态模式
状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。状态机又叫有限状态机,它由三个部分组成:状态、事件、动作。其中,事件也叫转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须得,也可能只转移状态,不执行任何操作。
针对状态机由三种实现方式:
- 第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每个状态转移原模原样得直译成代码。对简单的状态机来说,这种实现方式最简单、最直接,是首选。
- 第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较适合。通过二维数组来表示状态转移图,能极大的提高代码的可读性和可维护性。
- 第三种实现方式是利用状态模式。对于状态不多、状态转移也简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种方法。
7.访问者模式
访问者模式允许一个或多个操作应用到一组对象上,设计意图是解耦操作和对象本身,保持类职责单一、满足开闭原则及应对代码的复杂性。
对于访问者模式,学习的难点在于代码实现。而代码实现比较复杂的主要原因是,函数重载在大部分面向对象编程语言中是静态绑定的。也就是说,调用类的哪个重载函数,是在编译期间,由参数的声明类型决定的,而非运行时,根据参数的实际类型决定的。此外,还讲到 Double Dispatch。如果某种语音支持 Double Dispatch,那就不需要访问者模式了。
正是因为代码实现难理解,所以,在项目中应用这种设计模式,会导致代码的可读性比较差。如果你的同事不了解这种设计模式,可能就会读不懂、维护不了你写的代码。所以,除非不得已,不要使用这种设计模式。
8.备忘录模式
备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象先前的状态。这个模式表达了两部分的内容:一部分是,存储副本以便后期恢复;另一部分是,要在不违背封装原则的前提下,进行对象的备份和恢复。
备忘录模式的应用场景也比较明确和有限,主要用来防丢失、撤销、恢复等。它跟 “备份” 很相似。两种的主要区别在于,备忘录模式更侧重代码的设计和实现。备份更侧重架构设计或产品设计。
对于大对象的备份来说,备份占用的存储空间会比较大,备份和恢复的耗时会比较长。针对这个问题,不同的业务场景有不同的处理方式。
- 比如,只备份必要的恢复信息,结合最新的数据来恢复;
- 再比如,全量备份和增量备份相结合,低频全量备份,高频增量备份,两种结合来做恢复。
9.命令模式
命令模式在平时工作中并不常用,你稍微了解下即可。
落实到编程,命令模式用到最核心的实现手段,就是将函数封装成对象。我们知道,在大部分编程语言中,函数是没法作为参数传递给其他函数的,也没法复制给变量。借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。
命令模式的主要作用和应用场景,是用来控制命令的执行、异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等,这才是命令模式能发挥独一无二作用的地方。
10.解释器模式
解释器模式为某个语言定义它的语法表示,并定义一个解释器用来处理这个语法。实际上,这里的 “语言” 不仅仅指我们平时说的中、英、法等各种语言。从广义上来讲,只要是能承载信息的载体,都可以称之为 “语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。
要想了解 “语言” 要表达的信息,就必须定义相应的语法规则。这样,书写者就可以根据语法规则来书写 “句子”,阅读者根据语法规则来阅读 “句子”,这样才能做到信息的正确传递。而我们要讲的解释器模式,其实就是用来根据语法规则解读 “句子” 的解释器。
解释器模式的代码实现比较灵活,也没有固定的模板。前面讲过,应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
11.中介模式
中介模式的设计思想跟中间层很像,通过引入一个中间层,将一组对象之间的交互关系从多对多转换为一对多。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。
观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同之处在于应用场景上。
- 在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。
- 而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者,也可以同时是消息的接收者。
相关文章:
设计模式学习笔记 - 设计模式与范式 -总结:1.回顾23中设计模式的原理、背后的思想、应用场景等
1.创建型设计模式 创建型设计模式包括:单例模式、工厂模式、建造者模式、原型模式。它主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。 1.单例模式 单例模式用来创建全局唯一的对象。一个类只允许创建一个对象…...
22 文件系统
了解了被打开的文件,肯定还有没被打开的文件,就是磁盘上的文件。先从磁盘开始认识 磁盘 概念 内存是掉电易失存储介质,磁盘是永久性存储介质 磁盘的种类有SSD,U盘,flash卡,光盘,磁带。磁盘是…...
OVITO-2.9版本
关注 M r . m a t e r i a l , \color{Violet} \rm Mr.material\ , Mr.material , 更 \color{red}{更} 更 多 \color{blue}{多} 多 精 \color{orange}{精} 精 彩 \color{green}{彩} 彩! 主要专栏内容包括: †《LAMMPS小技巧》: ‾ \textbf…...
【Java开发指南 | 第一篇】类、对象基础概念及Java特征
读者可订阅专栏:Java开发指南 |【CSDN秋说】 文章目录 类、对象基础概念Java特征 Java 是一种面向对象的编程语言,它主要通过类和对象来组织和管理代码。 类、对象基础概念 类:类是一个模板,它描述一类对象的行为和状态。例如水…...
Neo4j 图形数据库中有哪些构建块?
Neo4j 图形数据库具有以下构建块 - 节点属性关系标签数据浏览器 节点 节点是 Graph 的基本单位。 它包含具有键值对的属性,如下图所示。 NEmployee 节点 在这里,节点 Name "Employee" ,它包含一组属性作为键值对。 属性 属性是…...
002 springboot整合mybatis-plus
文章目录 TestMybatisGenerate.javapom.xmlapplication.yamlReceiveAddressMapper.xmlreceive_address.sqlReceiveAddress.javaReceiveAddressMapper.javaIReceiveAddressServiceReceiveAddressServiceImpl.javaReceiveAddressController.javaTestAddressService.javaSpringboo…...
代码随想录训练营第三十五期|第天16|二叉树part03|104.二叉树的最大深度 ● 111.二叉树的最小深度● 222.完全二叉树的节点个数
104. 二叉树的最大深度 - 力扣(LeetCode) 递归,可以前序遍历,也可以后序遍历 前序遍历是backtracking 下面是后序遍历的代码: /*** Definition for a binary tree node.* public class TreeNode {* int val;* …...
Mac版2024 CleanMyMac X 4.15.2 核心功能详解 cleanmymac这个软件怎么样?cleanmymac到底好不好用?
近些年伴随着苹果生态的蓬勃发展,越来越多的用户开始尝试接触Mac电脑。然而很多人上手Mac后会发现,它的使用逻辑与Windows存在很多不同,而且随着使用时间的增加,一些奇奇怪怪的文件也会占据有限的磁盘空间,进而影响使用…...
【华为OD机试】执行任务赚积分【C卷|100分】
题目描述 现有N个任务需要处理,同一时间只能处理一个任务,处理每个任务所需要的时间固定为1。 每个任务都有最晚处理时间限制和积分值,在最晚处理时间点之前处理完成任务才可获得对应的积分奖励。 可用于处理任务的时间有限,请问在…...
mybatis分页实现总结
1.mybatis拦截器相关知识 1.作用 mybatis的拦截器是mybatis提供的一个拓展机制,允许用户在使用时根据各自的需求对sql执行的各个阶段进行干预。比较常见的如对执行的sql进行监控,排查sql的执行时间,对sql进行拦截拼接需要的场景,…...
Vue3——html-doc-js(html导出为word的js库)
一、下载 官方地址 html-doc-js - npm npm install html-doc-js 二、使用方法 // 使用页面中引入 import exportWord from html-doc-js// 配置项以及实现下载方法 const wrap document.getElementById(test)const config {document:document, //默认当前文档的document…...
第19天:信息打点-小程序应用解包反编译动态调试抓包静态分析源码架构
第十九天 本课意义 1.如何获取到目标小程序信息 2.如何从小程序中提取资产信息 一、Web&备案信息&单位名称中发现小程序 1.国内主流小程序平台 微信 百度 支付宝 抖音头条 2.小程序结构 1.主体结构 小程序包含一个描述整体程序的app和多个描述各自页面的page …...
外观模式:简化复杂系统的统一接口
在面向对象的软件开发中,外观模式是一种常用的结构型设计模式,旨在为复杂的系统提供一个简化的接口。通过创建一个统一的高级接口,这个模式帮助客户端通过一个简单的方式与复杂的子系统交互。本文将详细介绍外观模式的定义、实现、应用场景以…...
PHP数组去重
public function array_unique_key($arr,$key) {$tmp_arrarray();foreach($arr as $k > $v){if(in_array($v[$key],$tmp_arr)){ //判断是否重复unset($arr[$k]); //重复则删除}else{$tmp_arr[]$v[$key]; //将值存储在临时数组中}}return $arr; } public function array…...
论软件系统的架构风格,使用三段论 写一篇系统架构师论文
软件系统的架构风格是指在软件系统设计与开发过程中,采用的一组相互协调的设计原则、模式和实践。这些风格不仅影响着系统的技术实现,还关乎到系统的可维护性、可扩展性和可靠性等关键质量属性。通过三段论的结构,本文旨在探讨软件系统架构风…...
深度挖掘响应式模式的潜力,从而精准优化AI与机器学习项目的运行效能,引领技术革新潮流
🌈 个人主页:danci_ 🔥 系列专栏:《设计模式》 💪🏻 制定明确可量化的目标,坚持默默的做事。 🔥 转载自热榜文章:探索设计模式的魅力:深度挖掘响应式模式的…...
企业级网络安全:入侵防御实时阻止,守护您的业务安全
随着互联网技术的快速发展,企业级网络安全问题日益凸显。在这个数字化时代,企业的业务安全不仅关系到企业的形象和声誉,还直接影响到企业的生存和发展。因此,加强企业级网络安全,预防和抵御各种网络攻击已成为企业的重…...
(一)Java八股——Redis
1 Redis缓存 1.1 什么是缓存穿透 ? 怎么解决 ?(穿透) 缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情…...
2024.4.15力扣每日一题——设计哈希映射
2024.4.15 题目来源我的题解方法一 链表自定义哈希函数 题目来源 力扣每日一题;题序:706 我的题解 方法一 链表自定义哈希函数 使用链表存储每个<key,value>。由于题目有限制put的次数不超过10000次,因此对于哈希函数的设计为&#…...
数据结构DAY4--哈希表
哈希表 概念:相当于字典,可以根据数据的关键字来寻找相关数据的查找表。 步骤:建立->插入->遍历->查找->销毁 建立 建立数据,形式随意,但一般为结构体(储存的数据量大)ÿ…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
华为OD机试-最短木板长度-二分法(A卷,100分)
此题是一个最大化最小值的典型例题, 因为搜索范围是有界的,上界最大木板长度补充的全部木料长度,下界最小木板长度; 即left0,right10^6; 我们可以设置一个候选值x(mid),将木板的长度全部都补充到x,如果成功…...
【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...
