框架相关面经

AOP面向对象编程(OOP)解决问题的重点在于对具体领域模型的抽象,而面向切面编程(AOP)解决问题的关键则在于对关注点的抽象。也就是说,系统中对于一些需要分散在多个不相关的模块中解决的共同问题,则交由AOP来解决;AOP能够使用一种更好的方式来解决OOP不能很好解决的横切关注点问题以及相关的设计难题来实现松散耦合。

Spring事务

链接: link.
链接: link.
(1)spring声明式事务的实现就是通过环绕增强的方式,在目标方法执行之前开启事务,在目标方法执行之后提交或者回滚事务,事务拦截器的继承关系图可以体现这一点。
在这里插入图片描述
(2)Spring采用AOP来实现声明式事,那么事务的AOP切面是如何织入的呢?
生成代理对象的核心类是AbstractAutoProxyCreator,实现了BeanPostProcessor接口,会在Bean初始化完成之后,通过调用postProcessAfterInitialization(会在初始化之后调用该方法)方法生成代理对象。

Spring事务传播行为

链接: link.
一般都会将service层的方法申明为事务,因为他直接会调用数据库的操作,即注解@Transactional(propagation=Propagation.REQUIRED)申明一个事务,也可以在配置文件中申明。
Spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit 或者rollback,取决于是否抛出runtime异常或error异常。
什么是事务传播行为: spring支持7种事务传播行为,确定客户端和被调用端(这里是调用与被调用的关系,并不是真正客户端)的事务边界
7种事务传播行为
在这里插入图片描述
配置声明式事务
(1)通知声明式事务管理
step1:配置dataSource
step2::配置事务管理器(TransactionManager)
step3:配置事务属性(各个事务的隔离级别,传播行为)
step4:配置事务切点,以及将事务切点和事务属性关联起来
在这里插入图片描述
(2)用@Transactional注解申明式的管理事务
Spring中注解的方式@Transactional标注事务方法。为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解。根据Spring AOP基于代理机制,只能标注公有方法。如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务。
在这里插入图片描述

在这里插入图片描述

一些常用的Spring Bean扩展接口

链接: link.

如何优化数据库查询能力

(1)硬件方面:频繁CRUD会吃磁盘IO,可以使用磁盘阵列。
(2)数据库配置方面:skip-name-resolve:禁止对外部连接进行DNS解析,可以消除mysql进行DNS解析的时间。key_buffer_size = 256M,指定索引的缓冲区大小,4G左右服务器可以设置成256或384.
(3)表结构设计方面
**char:**定长格式,每条记录占用等长的空间,不足的自动填充空格,在读取时,自动将空格去掉。范围0-255字符。适用于手机号码、身份证号码这种定长的字段。char(10),即该字段占10字符,不足10字符的用空格填充。
**varchar:**不定长的,可以指定最大存储长度,适合用于长度可变的属性。最长65535字节,但是前两个字节用来存储字节长度,所以最多存储65533字节(text同理)。varchar(50)表示最大存储50个字符,实际占用内存大小依赖于实际存储的字符串大小。varchar适用于数据值长度不太短,且长度变化较大的字段,反例varchar(3),尽管此字段为空也要比char(3)多浪费2字节,所以字符串长度太短的话,应该用char
**text:**不定长,当最大存储长度不知时,用text。最长长度65535字节。通varchar一样,实际内存占用以来与实际的字符串大小。
char的查询效率要比varchar和text快,因为在查询时varchar需要动态计算字符的长度,而char是定长的
关于定长和不定长:
定长(实际占用):即指定多少就占用多少,尽管你不占这么多,如char(15),指定了15个字符,但是只用了12字符,其实也占了15.
不定长:varchar(100),指定100,其实占用了50,那么存储空间也就占了50。
(4)加索引
(1)对经常要进行查询的字段加索引。
(2)用于表连接的字段(一般为外键)加索引。
(3)对需要进行范围查询、排序的字段加索引。
(4)应尽量避免在 where 子句中使用!=或>操作符,否则将引擎放弃使用索引而进行全表扫描
(5)应尽量避免在 where 子句中对字段进行 null 值判断(可以把null值用默认值如0,-1等表示),否则将导致引擎放弃使用索引而进行全表扫描。
(6)尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描。
不适合加索引的场景
(1)表中记录太少,宁愿进行全表扫描,因为如果是辅助索引的话,要还回表查询(即通过辅助索引获得主键,然后根据主键在主键索引中查找记录)
(2)字段中存在大量的重复的字段。

在高并发下如何提高数据库查询性能–用redis缓存

使用redis缓存,将从数据库中查询出来的数据放入缓存。每次进行查询时,先看key是否存在于redis中,如果存在就返回,如果不存在则在数据库中查询,将结果放入缓存中。当对数据库中的数据进行更新后,直接使缓存无效。

使用缓存会带来哪些问题

链接: link.

缓存穿透

(1)出现原因
查询一个根本不存在的对象,在缓存层不命中,然后存储层也不命中,一般不将空结果放入缓存。
(2)造成后果
每次查询不存在的对象,都要访问两层(缓存层、存储层),失去了缓存保护的意义。
(3)措施
a、缓存空对象。当存储层不命中时,将空对象(null),保存到缓存中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
带来的问题:空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法是针对这类数据设置一个较短的过期时间(如1min),让其自动剔除。
b、布隆过滤器拦截。在访问缓存层和存储层之前,将存储层存在的key用布隆过滤器提前保存起来,做第一层拦截。当访问时,首先判断该key是否在布隆过滤器中,如果不在说明存储层肯定没有数据,就直接返回客户端空数据。

缓存击穿

(1)出现原因
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多(并发用户查同一条数据),同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
(2)解决方案
a、设置热点数据永不失效
b、使用互斥锁即让一部分请求等待。对存储层的访问加锁。当缓存中没有目标key时,访问存储层,要tryLock去获得锁,如果获取锁失败,那么sleep(100),醒来后取缓存获取key。如果获得了锁那么访问存储层,并将结果set到缓存中,然后释放锁。

缓存雪崩

(1)出现原因
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
(2)造成后果
引起数据库压力过大甚至down机
(3)措施
a、设计成高可用的,如redis sentinel, redis cluster。这样一台机子宕机之后,进行故障转移,另外一台主机继续服务。
b、限流。可以通过限流组件设置让多少请求进入,比如来了5000请求,让2000通过,进入数据库,剩余请求走降级,限流组件会调用你自己开发好的一个降级组件,返回一些默认值,友情提示等。

java的不可变对象

定义:对象创建后,其内部的状态不会发生变化。状态即属性即成员变量
对象拥有哪些属性才会是不可变对象:
(1)String属性,因为String本身就是一个不可变对象,因为类用final修饰,这样不能被继承,同时成员变量为final Object[]即用final修饰,即为一个常量,同时String中的成员方法都是只读方法不会修改String的属性。
(2)final修饰的属性。
(3)同时,要求对象的方法都是只读方法,不会改变对象的属性。
优势
由于状态都是不可变的,那么在并发访问环境下,不用考虑线程安全的问题。

同步IO和异步IO

分布式

java注解

优先队列的实现

(1)优先队列可以用大顶堆实现,队列中的元素放到大顶堆中,堆的根节点就是优先级最大的元素。
(2)大顶堆的insert 和 删除。
堆的特征
堆是一个完全二叉树,并且父节点的值大于孩子节点,根节点的值最大(往往我们只关注根节点)。小顶堆的话是父节点的值小于孩子节点,即根节点的值最小。
向堆中插入元素
插入的元素,将该元素构建一个节点,放入堆的末尾。然后与父节点的值进行比较,如果大于父节点的值,那么交换父子节点的位置,不断重复这个过程,直到小于父节点的值或者到达根节点。
向堆中删除元素
删除都是将堆顶元素删除,用堆的末尾元素代替堆顶元素,然后执行下沉过程。即根元素与孩子元素比较,如果小于孩子元素,那么就和孩子元素中最大的那个元素交换位置。不断执行这个过程,知道父节点大于孩子节点。

线程之间会共享哪些数据

这个从jvm的内存布局来答。
jvm中将运行时数据区域划分为:堆区(最大的区域)、方法区、虚拟机栈、本地方法栈、程序计数器。虚拟机栈和本地方法栈,程序计数器都是线程私有的。而堆区、方法区都所有线程共享的数据,因此堆区、方法区中的数据都是共享的数据。对中存储的是对象、方法区存储的是静态变量、常量、类型信息。

死锁

什么是死锁
多个进程在竞争共享资源时,产生的一种僵持的状态,在这种状态下,如果没有外力作用,进程无法向前推进。
产生死锁的必要条件
(1)不可剥夺条件。进程在占用资源过程中,如果不是主动释放,该资源会一直被占用。
(2)互斥条件。一次只能有一个进程访问该共享资源。
(3)请求与保持条件。进程因请求资源而阻塞时,占用的资源不会被释放。
(3)循环等待条件。进程间形成自己本来就占用了资源但是又在请求其他被占用的资源这样的 环。
解决死锁的方法
(1)死锁预防----即破坏死锁形成的必要提交
a、破坏不可剥夺条件。得不到资源时,立即释放已占用资源。当进程占用了一部分资源,但是暂时得不到其他资源时,主动释放已占有的资源。
c、破坏请求与保持条件。资源一次性的分配给进程。
d、破坏循环等待条件。资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反
在java中的做法:
a、当线程要同时获得多个锁时,每个线程都要以相同的顺序去获得锁。根据锁对象的hashCode大小去获得锁。
b、超时放弃,当在指定的时间内没有获得到锁,那么就释放之前已经获得的所有锁。如Lock中的boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法。
(2)解除死锁
当出现死锁后,应该立即将进程从死锁状态下解除,常用的方法:
a、根据进程的价值大小,直接撤消进行。
b、从其他进程中剥夺足够的资源分配给死锁状态下的进程。
java中检查死锁
jstack -l pid,如果存在死锁,可以看出来

什么时候用接口什么时候用抽象类

链接: link.
(1)两者的区别
在这里插入图片描述
(2)使用时机
a、抽象类适合描述某个领域的固有属性,即本质特征;而接口用来描述某个领域的公共行为,适合定义某个领域的扩展功能。
c、当需要为一些类提供公共的实现代码时,应优先考虑抽象类。因为抽象类中的非抽象方法可以被子类继承下来,使实现功能的代码更简单。
d、当注重代码的扩展性跟可维护性时,应当优先采用接口。
①接口与实现它的类之间可以不存在任何层次关系,接口可以实现毫不相关类的相同行为,比抽象类的使用更加方便灵活;
②接口只关心对象之间的交互的方法,而不关心对象所对应的具体类。接口是程序之间的一个协议,比抽象类的使用更安全、清晰。一般使用接口的情况更多。

守护线程(Daemon)

定义与功能
守护线程是一种支持型线程,因为它主要用作程序中后台调度以及支持型工作(如垃圾收集线程)。这意味着,当jvm中只有守护线程时,jvm将退出。
将普通线程设置为守护线程方式
调用线程的Thread.setDaemon(true)方法

设计模式的六大原则

单一职责原则
(1)内容
一个类,或者一个接口,最好只做一件事情,当发生变化时,他只能受到单一的影响;因为职责过多,可能引起变化的原因将会很多,这样导致职责和功能上的依赖,将严重影响其内聚性和耦合度,混乱由此而生。
(2)优势
类的复杂性将降低,简单明细的代码将使可读性将大大提高,自然而然可维护性亦将同步提高,可维护性提高将预示着因变更引起的风险会大大降低。
如在开发时Service由多个service类组成,如UserService, 这个类只处理用户相关的业务。
里氏替换原则
(1)内容
子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法或者实现抽象方法外完成新增功能外,尽量不要重写父类的方法。
(2)优势
增强程序的健壮性。实际项目中,每个子类对应不同的业务含义,使父类作为参数,传递不同的子类完成不同的业务逻辑。
依赖倒置原则
(1)内容
依赖于抽象(抽象类或者接口),即面向接口编程。即在定义类中的属性或者方法中的形参时尽量用抽象类或则接口。
(2)优势
降低耦合,提高稳定性。
接口隔离
(1)内容
尽量将臃肿庞大的接口拆分成更小的和更具体的接口,避免庞大的总接口。类将的依赖应该建立在最小的接口上。
(2)优势
a、接口隔离提高了系统的内聚性,减少了对外交互,降低了系统的耦合性
b、能减少项目工程中的代码冗余。过大的大接口里面通常放置许多不用的方法,当实现这个接口的时候,被迫设计冗余的代码。
开闭原则
(1)内容
对扩展开放,对修改关闭。可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
(2)优势
开闭原则可以提高代码的复用性。
迪米特原则
(1)内容
又叫做最少知道原则,就是说一个对象应当对其它对象有尽可能少的了解。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
(2)优势
迪米特法则初衷在于降低类之间的耦合。由于每个类尽量减少对其它类的依赖,因此。很容易使得系统的功能模块独立,相互之间不存在(或很少有)依赖关系。
在这里插入图片描述

泛型与泛型擦除

(1)什么是泛型
把类型当作是参数一样传递,把类型明确的工作推迟到创建对象(泛型类)或调用方法(泛型方法)的时候才去明确的特殊的类型。
(2)为什么需要泛型
如果没有泛型,那么把对象扔进集合中(Collection、Map),集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换。
即有了泛型之后就不需要强转了,而且集合中都是放的同一类数据。
(3)泛型的使用
a、泛型类:泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来
在这里插入图片描述
b、泛型接口—定义与泛型类相同
c、泛型方法
在这里插入图片描述
在这里插入图片描述

c、泛型类的子类
子类明确了泛型类型
在这里插入图片描述
子类未明确泛型类型
在这里插入图片描述
(5)类型通配符----常用的 T(type,具体的java类型),E(element),K(key),V(value),?(任意类型都可以匹配)
a、? 无界通配符
b、上界通配符–< ? extends E>
用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类.
在这里插入图片描述
c、下界通配符— < ? super E>
用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object
(6)泛型擦除
a、Java的泛型是伪泛型,这是因为Java在编译期间(即生成字节码),所有的泛型信息都会被擦掉,转为原始类型。
b、原始类型有两种情况(1)如果限定了类型(上界extends、下界super),则使用限定类型。(2)若没有限定,则使用Object代替。
在这里插入图片描述
因为在Pair中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair或Pair,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。
在这里插入图片描述
(7)泛型擦除带来的问题
a、Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
在这里插入图片描述
在这里插入图片描述
类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
b、自动类型转换
在这里插入图片描述
c、类型擦除与多态的冲突和解决方法------jvm会自动生成桥方法,在桥方法中调用重写的方法。
在这里插入图片描述
于是JVM采用了一个特殊的方法,来完成这项功能,那就是桥方法。编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
在这里插入图片描述

页面置换算法

地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。
最佳置换算法(OPT)
淘汰掉主存中,未来不再使用或者未来最长时间不再使用的页。这样可以保证最低的缺页率。
先进先出置换术算法(FIFO)
淘汰掉最先进入内存(即驻留时间最长的页)的页。FIFO会出现Belady 现象,即当所分配的物理块数增大而页故障数不减反增的异常现象。
最近最久未使用算法(LRU)
淘汰掉最久没有被使用的页。根据的是局部性原理,认为最近被访问过的页,再次被访问的几率很大。
clock算法—最近未用算法
.简单的CLOCK算法是通过给帧关联一个使用位(use bit)。初始时使用位都是0。当帧中载入页面时或者使用了每个页面时,其使用位置为1(即表示已被使用了)。当发生缺页时,从磁盘中加载页,寻找一个使用位为0的帧(0所对应的页面就是最近没有使用的页面),并将页面加载到使用位为0的帧中。在寻找过程中如果使用位是1,则将其置为0。

各种排序算法

在这里插入图片描述

jar包冲突&项目在不同服务器上产生不同状况的原因(如:同样的项目放到不同的服务器上,有的服务器上可以跑起来)

链接: link.
(1)同样的项目放到不同的服务器上,有的服务器上可以跑起来,可以从如下找原因:
在这里插入图片描述

Spring如何解决循环依赖

答的时候,解释三级缓存作用,然后解释在实例化完成之后暴露singletonbean(即放入第三级缓存),然后解释getBean时调用getsingleton逻辑。
链接: link.
链接: link.
总体:利用三级缓存。即在实例化完成后,将singletonFactory(通过它可以得到该bean的半成品)暴露出去即放入第三级缓存中,其他对象在注入的时候,就可以拿到这个bean的半成品,继续接下来的属性填充、初始化等阶段。

/** Cache of singleton objects: bean name --> bean instance */
//一级缓存,用来存储,已经初始化完成的,即已经完成了实例化(即调用构造函数)、依赖注入、初始化这一整个周期的Bean。每次要用Bean,即getBean方法时,都是首先尝试从一级缓存中拿去Bean。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

//二级缓存,用来存放半成品的Bean,即只完成了实例化,但是正在注入依赖的Bean。是通过三级缓存中的SingletonBean.getObject方法得到半成品的Bean,并将其放入二级缓存中。
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

//三级缓存。用来存储SingletonFactory即BeanFactory的,通过其getObject方法,返回一个半成品的Bean。是在完成初始化之后,将singletonFactory放到三级缓存中的。
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

在这里插入图片描述
Bean的初始化主要分为以上三步。实例化就是通过反射得到构造方法,然后通过构造方法newInstance,。属性填充就是依赖注入。初始化话就是,如果Bean实现了initializationBean接口,重写了afterProportySets方法,那么就执行该方法,如果配置bean时标注了init-method那么再执行该方法。
由以上分析,那么循环依赖,只可能发生在实例化即执行构造函数阶段和依赖注入阶段。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

在getBean的时候,会首先会执行以上方法。即先看Bean是否已经存在了,或者是否已经正在创建。
(1)首先尝试在一级缓存中拿Bean,即如果该bean已经初始化完成,那么直接返回。
(2)如果一级缓存中没有Bean,那么判断该Bean是否正在创建,那么从二级缓存中拿,即拿一个半成品。如果二级缓存中存在则直接返回。
(3)如果二级缓存中没有,那么判断是否允许提前暴露Bean,如果允许的话,那么从三级缓存中拿出对应的SingletionFactory,然后执行getObject方法,返回一个半成品Bean,并放入二级缓存中。同时将三级缓存中的singletionFactory删除。

那么singletonFactory是什么时候放入三级缓存的呢,是在实例化完成后,依赖注入之前将这个工厂暴露出去的。这也表明了,构造函数中的循环依赖没办法解决,因为在实例化阶段,singletonFactory还没有将自己暴露出去。其他Bean没办法拿到半成品。
以下方法就是在实例化之后执行,即向singletonFactory暴露出去,

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

Spring中用到的设计模式

(1)动态代理:(JDK动态代理、CGLib动态代理)----->AOP
(2)工厂模式:FactoryBean,中的getObject()方法,按照自己来生成一个bean,然后交给Spring管理。
(3)单例模式:保证一个类仅有一个实例,即Bean的生命周期,并提供一个访问它的全局访问点(BeanFactory)
(4)适配器模式:Spring MVC中的HandlerAdapter,来统一调用handler。因为handler的实现方式有很多种,如@RequerstMappering。 实现Controller接口。以及实现HttpRequestHandler接口

策略模式

链接: link.
(1)理解:策略模式定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换

在这里插入图片描述
(1)Context(上下文):拥有Strategy变量,通过构造函数注入具体的策略。屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化
(2)Strategy(抽象策略】): 是对策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性
(3)ConcreteStratey(具体的策略):实现抽象策略中的操作,即具体的策略实现。

(1)何时使用:
一个系统有许多类,而区分它们的只是他们直接的行为时。
(2)有点:
a、算法可以自由切换
b、避免使用多重条件判断(如果不用策略模式我们可能会使用多重条件语句,不利于维护)
c、展性良好,增加一个策略只需实现接口即可
(3)缺点
a、策略类数量会增多,每个策略都是一个类,复用的可能性很小
b、所有的策略类都需要对外暴露

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值