设计模式之我的归纳总结

48 篇文章 0 订阅
算法和设计模式在大厂面试比较重要 所以归纳一下 只讲述一些比较有意义的点 其他的很多基础性知识网络上都有可维护性:修改添加代码不引入新BUG 不必花很长的时间 维护代码的工作一般会占很多 所以很重要可读性:代码可读性 评价代码质量最重要的指标之一 也会影响代码可维护性 编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等可扩展...
摘要由CSDN通过智能技术生成

算法和设计模式在大厂面试比较重要 所以归纳一下 只讲述一些比较有意义的点 其他的很多基础性知识网络上都有

首先要弄清楚,很多设计模式解决方案或者代码实现是很相似的 主要需要知道设计意图 为了解决什么场景,不同设计模式的主要区别就在于这里

可维护性:
修改添加代码不引入新BUG 不必花很长的时间     维护代码的工作一般会占很多 所以很重要
可读性:
代码可读性 评价代码质量最重要的指标之一  也会影响代码可维护性  
	编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等
可扩展性:
代码应对未来需求变化的能力 新功能直接插入而不需要改动大量原始代码
灵活性:
可扩展 易复用(底层已经写了很多可复用的模块) 接口可以复用很多应用场景
可复用性
可测试性

面向对象

面向对象编程:类 对象 封装 抽象 继承 多肽
面向对象编程语言是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。

多肽不只是java(静态语言)中的继承,像python(动态语言)的duck typing也是多胎:只要类有相同的方法即可 并不需要这两个类有具体的关系

1)注意不要滥用get set方法:没必要全部属性都加上get set 按需  否则就退化成面向过程的方法了 破坏了封装性
			注意尤其是数组 不要随意的暴露对象出去  外界可以拿到这个数据直接进行清空或者更改数据的操作 ----------要清空修改数据就要改造成在类里面加一个方法 透明地提供给外界使用(封装性)
	
	如果一定需要get获取到数据 可以调用Collections.unmodifiableList() 来返回一个不可被修改的list 这个是Collections集合的内部类		----这种方法有问题 就是获取到的数组没法改 但是get到的数组内部的数据,单个的对象数据可以被改
	还有种方法就是利用内部封装的方法直接返回结果 

在这里插入图片描述

2)不要滥用全局变量和方法
	例如不要把全部的静态变量放在一个配置类中 
			很容易导致更改这个类后 所有引用这个配置类的类重编译
	可以把配置类分门别类  也可以把需要用到的静态属性直接写在类里面--这样子这个类的复用性和内聚性大大提升

	不要滥用Utils类 如果可以写在其他类里面内聚是比较好的   没有属性只有方法的Utils类其实是面向过程的,虽然这没有多大的影响 
	
	很多时候在我们实际开发中并没有用好面对对象的开发 而是直接写成了面向过程的开发:pojo BO VO ENTITY都只保存着数据 业务逻辑都在service里  

接口和抽象类

实际开发中: 如果为了表示is a关系 并且实现代码复用 就用抽象类(自下而上 类有复用的代码了 就想办法抽象成抽象类 复用代码 设计模版 复用变量)
					  如果为了表示has a关系 是提供功能接口的 为了解决抽象而不是复用的 就用接口(自上而下的 想提供一个功能的接口了 就设计一个接口 再设计具体的接口实现)

 越抽象 越顶层 越脱离具体实现 越能提高灵活性 
 注意抽象意识   接口意识 (只定义上层真正有益抽象出来的方法 具体其他实现方法交给具体类 而且最好这些方法在具体类中保持private       接口只表明做什么 而不是怎么做     接口尽量保证替换接口具体实现类的时候不用改变接口)  封装意识(没必要对外暴露的不暴露)

如果某个功能只有一种实现方式 也不可能未来被替代了 就可以不用接口了 ,对于不稳定的系统 将来需要更改维护的尤其重要保证其扩展性

该用组合还是继承???

继承的问题:
继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性 


完全可以利用 组合+接口 来完全替代掉继承 :
例如写不同的功能接口 每个接口用实现类实现这个接口 完成方法书写    在需要使用的类中实现这个接口(多肽)  在类中利用组合加入实现了具体功能的类(复用),在这个类的方法中利用成员变量调用相应的方法即可 而无需自己再实现一遍

可知组合的问题:
需要定义更多的接口和类 类的划分更加细粒度(代码的复杂程度和维护成本)

如果系统 继承结构不稳定经常变动 层次很深 继承关系复杂 =====尽量用组合代替继承
有的情况又只能用继承:我们继承外部给的类(没权限修改) 重写里面所需的方法
有的情况只能用组合: 类和类用需要公用的方法 但是两者并没有父子关系   所以利用组合 例如util工具类 

基于充血模型的DDD开发模式

轻 service   重domain    在pojo对象中写业务逻辑,从一开始就要设计好针对数据要暴露哪些操作
需要我们前期做大量的业务调研、领域模型设计 前期设计上    对复杂的系统很好 
充血模型的DDD开发更复用 ,所有扩展都是基于之前定义好的领域模型(可复用的业务中间层),而不是像传统贫血模式开发 充斥着大量的只有小改变 大部分重复的sql(很多功能都充斥在sql中)

充血模型只是为了扩大复杂业务系统的可复用性  所以没必要在controller或者repository上也换成充血模型

充血模型中 service负责一些不适合放在 Domain 类中的功能。比如,负责与 Repository 层打交道、跨领域模型的业务聚合功能、幂等事务等非功能性的工作

类和类之间的关系

在设计领域模型的时候 拿到需要我们分析类的关系的时候   所以要知道这些设计模式
 (1)泛化:就是继承
 (2)实现
 (3)聚合:类似组合 但是对象一般是从外部传递进来的 所以生命周期不受本对象的控制
 (4)组合:被组合的对象生命周期依赖组合的对象  因为是在组合的对象内部创建的
 (5)关联:聚合 组合
 (6)依赖:只要两个对象有任何的关系 就交依赖 即使是从方法参数

单一职责原则(SRP)

一个类只负责完成一个职责或功能 不要设计大而全的类,要设计粒度小、功能单一的类 
如果调用者只使用部分接口或接口的部分功能 那接口的设计就不够职责单一

高内聚 低耦合

判断不符合单一职责的
类中的代码行数、函数或者属性过多---这是一条抽象的规则 也是为了复用 好管理 扩展
类依赖的其他类过多,或者依赖类的其他类过多  ----
私有方法过多 ----拆成适当的其他类 共有方法  提高复用率    可以把原本public的方法抽象到顶层service 利用底层多个带有public方法的可复用类来完成业务 (充血型ooD)
比较难给类起一个合适的名字;----因为你难以知道这个类的功能 所以才没办法起名字
类中大量的方法都是集中操作类中的某几个属性。

开闭原则

并不是说不可以修改原有的类  只要它没有破坏原有的代码的正常运行,没有破坏原有的单元测试

大部分的设计模式都是为了解决系统扩展性 即开闭原则想要达到的目的

里氏替换原则

很像多肽  可以替换父类出现的任何位置,并且原来代码的逻辑行为不变且正确性也没有被破坏 不能改变结果!例如抛出个异常  
			函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明

接口隔离

如果部分接口只被部分调用者使用,那我们就需要将这部分接口隔离出来,单独给对应的调用者使用,而不是强迫其他调用者也依赖这部分不会被用到的接口

部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数

接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数

控制反转 依赖注入 依赖反转

控制反转
程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行 程序执行流程的控制从程序元解放到了框架

依赖注入
不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。

我们只需要通过依赖注入框架提供的扩展点,简单配置一下所有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情

依赖反转
高层模块不要依赖底层模块 而是共同依赖一个抽象(规则),例如tomcat和应用程序 都依赖servlet规则

kiss YAGNI原则

kiss原则:
要保持代码simple and stupid  -------  方便检查 易懂 少bug

并不是说代码少就是简单的  还要考虑逻辑复杂度 性能 实现难度 代码可读性  ,如果优化这段代码需要很复杂 但是对性能贫瘠又很重要 那就适合kiss原则
设计模式 开发中一个通用的概念:越是能用简单的方法处理复杂的问题 越能体现你的能力!

yagni:
不要设计过度 不要设计当前用不到的功能和代码  但是还是要保存好代码的扩展节点
没有必要提前引入所有开发库

DRY:
不要写功能重复的代码  功能不重复但是语意重复的代码 可以抽象出公用的方法来,也保证未来需要重构某个方法的时候更方便 复用 可扩展(entity vo bo的设计是相似的)
	也不要执行流程上代码重复  例如多次查询数据库 多次校验

重构项目代码

保持功能不变的前提下,利用设计思想、原则、模式、编程规范等理论来优化代码,修改设计上的不足,提高代码质量,需要养成重构的思维      初步设计+不断重构才是一个项目的正确发展路线

代码质量评判标准来评判代码的整体质量  -》 对照设计原则来发现代码存在的具体问题  -》 用设计模式或者编码规范对存在的问题进行改善

函数的返回值

异常
		--下面这两个一般在不是异常但是找不到的情况返回
null			找不到对象
数字				找不到数据
		---下面这两个好处就是不用判断null  可以直接调用length或者for循环 
空字符串
空集合

设计模式:
设计模式—创建型

1)单例模式
		可以处理资源访问的冲突
		保证全局唯一
----饿汉 懒汉 双重检查 静态内部类 最好的:枚举(重置序列化 反射调用构造器无效)2)工厂模式  :简单工厂、工厂方法和抽象工厂    ---单独的工厂类来创建对象
简单工厂模式类:DateFormat Calender就是简单工厂模式  --- 直接new对象 而不是通过实现类创建对象
		1 可以每次方法传参生成一个新的对象
		2 也可以结合单例模式 用hashmap存储string - 对象    每次调用去拿到对应的已经生成在内存中的类即可
		spring 对应的就是 scope=prototype 表示返回新创建的对象,scope=singleton 表示返回单例对象

工厂方法:更符合开笔原则 , 
	使用接口实现类模式 没需要一种新的对象时候就实现一个新的实现类
	可以用工厂对象的工厂类来去除掉硬编码的if else 逻辑

抽象工厂:用组合方式包容多个工厂类对象  可以用这个工厂类创建多个不同的类对象 而不是一个工厂类创建一种类对象

依赖注入容器底层就是用的工厂模式 负责整个应用中所有类对象的创建
DI容器:
DI容器的功能:配置解析 对象创建 对象生命周期的管理
(1)通过配置文件创建需要的对象和对象中的参数 
(2)所有对象通过BeansFactory来创建即可 -- 抽象工厂模式 ,不需要每个工厂类创建具体对象这样项目类太庞大,
	是通过反射来动态创建对象的 所以不需要担心BeansFactory代码跟着类的增加膨胀
(3)对象生命周期的管理
怎么实现一个自己的简单的DI容器呢:
	1配置文件解析
	2根据配置文件通过“反射”语法来创建对象。
入口类包括 一个factory 一个parse ,加载类路径下的配置文件 然后通过parse进行解析成对象的类 通过factory来动态创建类对象,  入口类提供getBean方法来从factory中获取你要的类文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

BeansFactory通过解析到的BeanDefinition 利用反射 来创建对象
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();  
private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();  
利用singletonObjects来存储单例对象 在创建容器的时候就直接创建,利用beanDefinitions来存储id 和具体的BeanDefinition 后面动态创建对象的时候来使用

(3)建造者模式
 可以把建造者builder放在类的内容,功能是校验逻辑,创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法进行集中的校验,然后真正创建对象  , 也可以使得对象不存在无效的状态 构造者创建出来的对象一定是有效的

虽然也可以在构造函数中一次性把对象创建好 但是参数过多的时候就很没有灵活性了,代码可读性也差

(4)原型模式
利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象 (对象创建时间成本大 例如排序  RPC、网络、数据库、文件系统等)

在更新对象的时候 可以利用拷贝对象进行更新 更新完毕后一次性替换对象 

浅拷贝得到的属性中的对象跟原始对象共享数据,是指向同一个对象
深拷贝得到的是一份完完全全独立的对象

设计模式—结构型

主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题

(1)代理模式:
		方式1实现同一个接口  jdk动态代理
		方式2继承被代理类  cglib
		经常用来开发非功能性需求:监控、统计、鉴权、限流、事务、幂等、日志
(2)桥接模式:
	JDBC就是侨接模式 
	详细的分析系统功能,将各个独立的纬度都抽象出来,使用时按需组合
	将抽象和实现解耦,让它们可以独立变化
(3)装饰器模式:
1装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类
2装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点
代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。

举个例子: bufferdinpustream和 FilterInputStream 和 inputsteam的设计就很有意思:
		FilterInputStream充当真正装饰器的类 独自继承inputsteam ,而其他的bufferdinpustream继承FilterInputStream,利用FilterInputStream里装配好的INputsteam   而不是把每次装备inputsteram的任务交给每一个有增强功能的类 例如bufferdinpustream,datainputstream等  这样就避免了重复实现,无论是重复实现装饰器模式 还是重复实现一些没有增强功能的代码(否则外界无法使用)
(4)适配器模式:
1类适配器   使用继承

在这里插入图片描述

2对象适配器

在这里插入图片描述

接口方法很多的话可以用类适配器(适配器类继承外部需要被接口的类 而不是组合)因为可以复用父类的代码,但是如果接口定义和外部类都大部分不同 那只能用组合了

适配器模式主要用来解决接口不兼容问题
1隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计
	或者说外部类来自外部sdk 我们无权修改代码 就只能利用自己的适配器类来进行封装接入
2某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑
3把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动,达到扩展的目的 而不是修改原代码框架
4解决版本升级 例如jdk的 Enumeration ,已经废弃 使用 iterator 但是为了兼容老版本 返回的仍然是Enumeration 只不过内部逻辑封装的是iterator
5适配不同格式的数据

小总结:
代理模式:主要目的是控制访问,而非加强功能
桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

(5)门面模式:解决接口粒度粗细问题导致的可复用性(通用性)和易用性之间的矛盾
例如后端提供一个调用了三个接口的总接口给前端使用 这样就解决了前端需要调用三个接口导致的性能问题
1封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口
2解决性能问题
3解决分布式事务问题:
  可以利用 分布式事务框架   或者 事后补偿机制
  最简单的是用:利用数据库事务或者 Spring 框架提供的事务 在一个事务中 利用门面模式 在一个接口中调用两个分布式的sql操作—— 利用spring事务的传播特性来处理!
(6)组合模式:
组合模式并不是组合关系,数据需要满足树型结构 一般用到递归(链表) 递归遍历(树)   是一种用java面向对象语言抽象出来的数据结构    所有的树型结构都可以当作file director模型进行构造-------目录下面包括file 和目录 都是extends一样的父类
(7)享元模式:复用对象,节省内存,前提是享元对象是不可变对象  (或者相似对象提取共同属性造成享元对象 大家都引用他  因为大家都引用他 避免属性修改所以弄成不可变对象 属性不可改变 对外不提供set方法) 也就是说享元对象只提供read方法
		大部分对象的某些属性用的是同一部份的对象 提升效率 节省内存
享元模式和单例模式代码结构不一样(跟多例像一点) 设计意图也不一样,跟缓存的目的也不同 主要是为了复用 
	和线程池等池化技术也不一样  池化为了节省创建时间 并发 每个对象是被一个使用的   而享元模式更多的是被多个大部分对象共享以节省空间

设计模式—行为型
解决 类或对象之间的交互 问题

(1)观察者模式:被观察者通知观察者 
同步阻塞 异步非阻塞 进程内 进程间(例如消息中间介)
(2)模版模式:  解决复用和扩展
定义一个算法骨架,并将某些步骤推迟到子类中实现,让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。 ----例如 各种生命周期流程中的钩子函数
  1 复用  : 父类定义好的流程模版可以被任何子类重复使用 只需要自己实现好自己的模版函数即可
  2扩展:指的是框架的扩展性 例如servlet框架 doget dopost请求都是在service的流程中调用的 ,这样用户不需要修改servlet框架或者知道具体细节

讲一下回掉函数 : 依然是给框架提供了扩展的功能 框架A的a方法中提供扩展点  B调用A的a方法 传入B的b方法 a方法流程中就会调用B的b方法  这就是回掉

一般来说回调 比 模板会好 --- 组合和继承的分别
(3)策略模式:
将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端
1省略 if else 语句  就像是工厂模式中用hashmap保存对象来获取一样的套路
2可以写在配置文件中 或者 注解标志  通过反射动态获取 再到1中去获取来减少代码的改动

(4)责任链模式:
将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止
A ——》 b ——》c    依次按照自己职责处理任务,可以设置一个chain类来管理链条 head tail属性即可 然后调用的时候就是加入handle类 调用handle方法 一个一个按照链上的逻辑进行递归调用查询是否可以处理(也可以用数组保存handler对象而不是一定要链表)
可以结合模版模式 定制抽象类的责任链模版 子类只需要重写流程中的定制方法

应用场景:过滤敏感词,springmvc的拦截器 web容器的filter都是数组责任链模式
主要目的:解耦ifelse 连着判断的语句  为了扩展性 复杂系统 开闭原则 更灵活(可以选择你要做的方法)
(5)状态模式:
首先理解状态机:
1if else逻辑判断witch case   来完成状态转移  对于复杂的状态机肯定不好 
2查表法    通过保存二维数组来保存 状态和对应的动作  通过配置文件就可以控制 
3状态模式:
状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑
原理: 双向绑定  
状态机类 持有状态类---》持有状态 和 动作   
状态类 持有状态机类  调用动作函数  更改所指向的状态机类的 状态类 并且更改状态机类的值(例如如果有socre等需要增减的话)

状态特别多的情况用查表法会更好否则会有很多的状态类    而状态少动作业务逻辑复杂的用状态模式比较好

(6)
迭代器模式:    用来遍历集合对象,将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中
迭代器(基于接口编程) 通过组合方式 放在容器(基于接口编程)中
迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法 不一定要这样。待遍历的容器对象通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器
遍历方式总结:
1for循环遍历
2迭代器遍历(foreach是语法糖 底层也是迭代器)
		迭代器好处:(1)处理复杂遍历 例如图和树的遍历(将遍历操作拆分到迭代器类中 而不是通过程序员重复在业务上实现)
							  (2)可以创建多个不同的迭代器,同时对同一个容器进行遍历而互不影响(通过游标)							(3)迭代器基于接口编程 更改算法 添加迭代器都方便很多 扩展性好 符合开闭原则
注意: 在遍历的过程中删除集合元素,结果是不可预期的(不可预期的错误很有可能引来很多隐藏很深的bug) 有可能会导致此次遍历某些元素遍历不到(被删除了+游标位置导致)
		在遍历中增加元素 可能会导致重复遍历
所以java语言选择了在迭代中增删进行报错处理:设置一个变量 遍历时候检查是否相等(调用修改函数就增加变量) 若不等,选择 fail-fast 解决方式,抛出运行时异常,结束掉程序
java iterator的remove方法设置cursor变量来保证遍历中删除不会出错(只能删除遍历的前一个元素
 且不可以连续调用remove进行删除)

可以用快照来解决读遍历和写的问题(我发现java并没有实现):
实现方式(1):每次调用iterator方法的时候拷贝一份到iterator中   --- 占用内存   ,但是因为java核心类实现的拷贝的浅拷贝 不会有内存问题,被引用的对象也不会失效 因为被iterator引用了
			(2):两个数组(一个用来支持时间戳 一个用来支持数组随机查询)
						两个时间戳数组  记录数组中元素的加入时间和删除时间(一个数组不真正进行删除 记录时间戳 提供快照功能   一个数组进行真正的删除操作 用来客户端需要针对随机访问功能--因为记录快照的数组没有进行真正的删除无法提供随机访问功能--相对的   不一定要这样 有很多种方法可以实现)
(7)访问者模式:
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身

精髓---- 如果某个类的方法有重载 但是都是子类  而传递的函数是用了多态的编译期父类 这种情况可以在子类内定义方法 传递进来重载的函数  调用this当作参数 这个this是可以通过编译的!

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Single Dispatch:执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的编译时类型来决定
Double Dispatch:是执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时类型来决定   ----   访问者模式思想的核心 用single dispatch的语言来实现double dispatch,如果语言支持了double dispatch就没必要用访问者模式了
	当前主流的面向对象编程语言(比如,Java、C++、C#)都只支持 Single Dispatch,不支持 Double Dispatch
如果工具的功能很多的话推荐使用访问者模式(类定义要相比工厂模式少很多),否则可以使用工厂模式 毕竟访问者模式不好理解
(8)备忘录模式:防止丢失、撤销、恢复   (快照模式)
在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态   像linux的快照一样
最简单的方法 维持一个stack 每次输入一个对象 在内存中保持这个对象最新的状态  把没被修改的状态push进去保存

为了应付内存 可以使用 低频率 全量备份 + 高频率增量备份   的方法
也可以不记录完整的内容而记录其他信息来进行恢复 例如一个大文件 可以记录字节数或者文字数 配合手头的对象进行恢复
(9)命令模式:
把函数封装成对象 因为对象可以存储 以此达到: 控制命令的执行 异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等
一般处理客户端服务器交互两种方式:(1)主线程接受 开启新的线程处理 (2)同一个线程接受处理 不断轮询   这个在手游开发中比较多使用  避免io密集型业务中线程不断切换带来的性能消耗
(10)解释器模式:
根据语法规则对“语言”进行解读
将语法规则拆分成一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析(例如加减乘除  用类表示数字 用类表示操作)
(11)	中介模式:
定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互
例如把分散在不同控件(或者类)中的业务逻辑集中到中介类,但是可能会使得中介类庞大,根据实际的情况,平衡对象之间交互的复杂度和中介类本身的复杂度

中介和观察者的差别:
(1)可以按自定义顺序调用参与者 而观察者是不可以的
(2)参与者交互关系错综复杂 维护成本很高的时候才使用中介模式   有可能产生大而复杂的上帝类

接下来结合实际应用实战演练介绍设计模式

 (1)canlendar类:getInstance方法是工厂方法 耦合在了canlendar类中与其他功能方法一起
 							内置了一个Builder构造者模式定制化创建canlendar子类对象
(2)Collection类的装饰器模式:内部类UnmodifiableCollection 的构造函数接收一个 Collection 类对象,然后对其所有的函数进行了包裹(Wrap),来增强Collecttion类的功能(变成无法修改)
(3)Collections中的适配器模式:
public static <T> Enumeration<T> enumeration(final Collection<T> c) {
        return new Enumeration<T>() {
            private final Iterator<T> i = c.iterator();

            public boolean hasMoreElements() {
                return i.hasNext();
            }

            public T nextElement() {
                return i.next();
            }
        };
       				 模板模式:sort函数
(4)jdk观察者模式 Observable  Observer:       ---------特殊说明 vector类不是线程安全的 只是每个方法是安全的 但是符合操作不一定安全    可以(1)把符合操作变成原子操作  (2)system。copy复制一个数组局部变量(快照 多线程编程中减小锁粒度、提高并发性能的常用方法  但是也会有其他问题 例如新增元素无法通知到 删除元素其他线程无法当作是删除的 具体好不好用看具体的业务场景)  对这个数组的删除不回影响到原数组该位置上的元素
(5)runtime类中的单例模式:  就是饿汉 							

怎么应对复杂的系统(业务代码量很大的 而不是技术难度很高的–人工智能等的项目)

(1)抽象封装 例如unix系统的一切皆文件
(2)模块化 ,每个小的团队聚焦于一个独立的高内聚模块来开发
(3)分层    计算机任何问题都可以用分层来解决 每一层都对上层封装实现细节,暴露抽象的接口来调用    任意一层都可以被重新实现,不会影响到其他层的代码,把容易复用、跟具体业务关系不大的代码,尽量下沉到下层,把容易变动、跟具体业务强相关的代码,尽量上移到上层
(4)不同层 模块之间通过接口进行通信
(5)高内聚 低耦合
(6)为扩展而设计
(7)可读性 很重要 比可扩展还重要 二者只选一个 那就是选可读性
(8)要遵守统一的开发规范,避免反直觉的设计

严格执行代码规范
编写高质量的单元测试 减少底层细粒度的bug
code review
没开发前一定要写开发文档
持续重构
拆分团队和代码(分层 模块化)

如何开发通用的模块

在业务代码开发中 善于发现非业务的、可复用的功能点 从业务逻辑中解耦出来 开发成独立的模块
	1类库
	2框架:让业务代码嵌套在里面开发 目的是为了更聚焦于业务
	3功能组件(重量级的类库 且更聚焦)
	共同特点: 复用 + 和业务无关  (复用且和业务有关的话 抽离出来  就是微服务了)

Immutable模式
即:多线程设计模式

一个对象的状态在对象创建之后就不再改变,这就是所谓的不变模式。其中涉及的类就是不变类,对象就是不变对象
1普通的不变模式:对象中包含的引用对象是可以改变的
2深度不变模式:对象包含的引用对象也不可变
所有的成员变量都通过构造函数一次性设置好,不暴露任何 set 等修改成员变量的方法。所以不存在并发读写问题,因此常用在多线程环境下,避免加锁。 

何为函数式编程?

把程序用一系列数学表达式或者数学的函数来表示  主要适用于科学计算 分析和统计 数据处理等
	无状态: 变量都是局部变量 函数执行结果只受入参影响  跟其他的任何外部变量都没有关系   --》 只要输入函数的参数是相同的 返回结果就是相同的  (有状态函数会使用外部的共享变量(甚至是全局变量)  就有可能每次执行外部变量不同而导致即使本次输入的参数是相同的 结果却不同)

java中的函数式编程:stream lambda 和 函数式接口

说几个在spring中存在的设计模式:

1约定好过配置思想
提供配置的默认值,优先使用默认值  因为开发中8成的配置都是可以依照规定来的

2低侵入 松耦合
要替换一个框架的时候 对原来业务代码改动很少 
		例如:ioc 不会对类强行切入什么代码  不用继承实现   通过配置来引入类   换一个ioc框架原本的bean都不需要更改 改改配置即可
			 AOP 将非业务代码集中放到切面中 
3模块化 轻量级:
上面的模块依赖下层模块 同层之间不依赖     且按需引入

4再封装、再抽象
对市面上主流的中间件、系统的访问类库,做了进一步的封装和抽象

具体的设计模式
5观察者模式:
event listener publish,  自己定义事件  监听器 往applicationcontext中发送事件即可 不需要修改任何代码的情况下 扩展新的事件和监听

6模板模式:
spring bean的创建过程:
	1对象的创建 :反射
	2对象的初始化:
			(1)自定义一个初始化函数 通过配置文件告诉spring在bean创建的时候调用哪个初始化函数  --》初始化函数并不固定 需要spring通过反射运行时动态调用这个函数 反射会影响执行性能
			(2)让类实现Initializingbean接口 ,spring在需要初始化的时候直接调用afterPropertiesSet方法 ---》耦合的业务代码和框架代码,因为实现了Initializingbean接口 替换框架的成本就高了
	
	再说一下类似的对象的销毁过程:
				(1)配置destroy-method
				(2)实现DisposableBean接口
	初始化前置 初始化后置:BeanPostProcessor   (在初始化前后进行的操作)
	具体的执行顺序: 创建对象 -》 BeanPostProcessor 前置操作 -> afterPropertiesSet ->init-method -> BeanPostProcessor 的后置操作 ->类的使用  -> DisposableBean  ->destroy-method
	
7适配器模式:
spring通过HandlerAdapter接口来管理不同的handler的实现类(handler是映射url+controller 而controller有三种方式可以实现:实现controller接口 继承servlet 注解方式  对应不同的方法需要调用不同的方法 注解则是通过反射调用),通过请求的url来从handlermapping中获取对应的HandlerAdapter然后直接调用handle方法即可
   handler(url+controller) 保存在 handlermapping中  在handlermapping中通过url来获取对应的handler 然后发送此handler给handlerapdapters数组 遍历此数组找到可以处理此handler的handleradapter然后调用handle 传入handler进行处理

8策略模式:根据状态值 环境变量  计算结果等参数来动态决定使用哪个策略
aop ,通过不同的策略选择jdk动态代理还是cglib   ,通过策略工厂来创建具体的策略接口的实现子类 通过该类的策略方法来创建具体的代理对象    ,这种思路可以推广到其他

9组合模式:
缓存管理功能CacheManager ,用组合模式(树形结构)来管理缓存结构

10装饰器模式:
TransactionAwareCacheDecorator 在事务提交和回滚后 把缓存也对应地处理了  ,实现了cache接口 包含成员变量cache 就是对spring cache的增强

11工厂模式
通过配置factory-method 来指定工厂类里创建对象的方法 传入参数来返回所创建的(或者缓存好的)对象

12解释器模式:
SpEL

对比一下操作数据库的类库或者框架

(1)JdbcTemplate:
优点  : 轻量级  性能好
缺点:   sql和代码耦合 ,不具备orm功能需要自己解析数据库数据和对象的映射

(2)Hibernate:
优点:可以根据业务需求自动生成sql 全自动orm    易用性高!
缺点: sql性能可能不行 也没有手写的针对

追求易用性,性能就差一些。追求性能,易用性就差一些。越简单方便,灵活性就越差
mybatis就基于性能和易用性在二者之间

mybatisplugin与职责链

统计sql操作耗时   分库分表 自动分页  数据脱敏 加密解密。。。

拦截myhbatis在执行sql过程中涉及的方法,例如统计sql操作的耗时 可以拦截executor parameterHandler(设置sql占位符参数) resultsethandler(封装执行结果) statementhandler(执行sql语句)

底层原理: 借助动态代理实现的职责连
	Interceptor + InterceptorChain  + Plugin(用来生成被拦截对象,即上面写的executor等 的动态代理)
动态代理给职责连添加代理后的对象 并且是经过了多层invocation的封装后的 一层一层执行intercept后才会执行executor parameterhandler等的方法

filter通过递归实现责任链
springmvc的interceptor通过在拦截方法前后加方法实现
mybatis plugin通过嵌套动态代理方式实现
	

mybatis所设计到的设计模式:

1利用建造者模式SqlSessionFactoryBuilder来创建 SqlSessionFactory,但是并不是为了设置参数 其实参数并不多  主要是因为在创建SqlSessionFactory需要创建Configuration,创建Configuration比较复杂 ,利用SqlSessionFactoryBuilder封装创建configuration的细节
2SqlSessionFactory工厂模式重载了很多同名方法创建sqlsession
	以上两个都不是传统的建造者 / 工厂模式
3BaseExecutor模版方法  例如 update 和 dpupdate(抽象的方法 在update中执行 交给子类来实现)
4sqlnode:解释器模式解释mybatis的动态sql
5利用threadlocal来实现线程唯一的单例
6PerpetualCache缓存类 + 其他9个包括增强的缓存类  ----   装饰器模式
7PropertyTokenizer迭代器模式
8Log 适配器模式

限流算法和限流模式

限流算法:
固定时间窗口限流算法:
当前时间窗口内,如每秒钟最大允许 100 次接口请求,累加访问次数超过限流值,就触发限流熔断,拒绝接口请求。当进入下一个时间窗口之后,计数器清零重新计数。
		缺点:在两个窗口临接的时间短期内可能会有最大的访问 导致限流框架检测不出来却让系统瘫痪

滑动时间窗口限流算法、令牌桶限流算法、漏桶限流算法


限流模式:
单机限流:针对某个服务的单个实例的访问频率进行限制

分布式限流:针对某个服务的多个实例的总的访问频率进行限制

接口幂等

在接口连接超时的时候可以直接重传 重传需要系统保证幂等姓:
可以在项目代码中或者 远程调用框架中加入超时重发的算法

幂等号:保证 针对同一个接口,多次发起同一个业务请求,必须保证业务只执行一次
			调用方生成并发送幂等号,接口实现方接受解析比较 存在直接返回 不存在记录继续执行业务
 			可以通过aop或者直接写入代码

一般请求三个流程:  如果发生异常
接受:
	异常  幂等还未记录 重试请求会执行   安全
业务:
	业务异常: 不删除幂等号 因为删除也是徒劳
	系统异常:删除幂等号 需要重新执行
	业务系统宕机:在应该删除幂等号前系统却宕机了 
					 (1)	利用分布式事务同时让幂等号的记录(redis)和业务系统的数据库记录 失败一起回滚			
					 (2)在业务数据库中建立表存储幂等号 先存储到mysql  后同步到幂等框架的redis,直接利用业务数据库本身的事务属性,保证业务数据和幂等号的写入操作,要么都成功,要么都失败,只要mysql没有写入 意味着任务没有执行成功 redis不会存储    这种情况下甚至不用自动同步mysql和redis 毕竟系统已经宕机   只需要人工比对即可
					 	
返回:
	幂等好已经记录 返回异常   重试也不会影响 安全

幂等框架一旦出问题 业务流程也得中止  这个跟限流框架不同  幂等框架出差错业务也需要中止否则容易出现例如多转账

怎么生成幂等号:
(1)集中生成并且分派给调用方 
		调用方通过调用远程接口来获取幂等号  算法也是幂等系统管理
(2)直接由调用方生成
	调用方按照跟接口实现方预先商量好的算法,自己来生成幂等号 ,但是不同接口需要有不同的幂等号生成 算法也需要自己调整

灰度发布

老接口继续承担大部分的调用 新接口承担一部分 再慢慢得变多 这就是灰度

用配置中心配置一个变量 if else来调控新旧代码的逻辑,根据灰度对象的值来判断走哪个接口

灰度框架需要提供:
1配置
需要设置一个 key 值,来唯一标识要灰度的功能
选择一个灰度对象(比如用户 ID)
配置这个 key 对应的灰度规则和功能开关
2提供接口判断是否灰度



灰度规则热更新:
定时器定时读取灰度规则配置信息,并且解析加载到内存中,替换掉老的灰度规则
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值