设计模式和设计原则
六大设计原则:
单 一 职 责 原 则
SRP:Single Reposibility Principle
定义:一个类或者模块只负责完成一个职责。
里 氏 替 换 原 则
LSP:Liskov Substitution Principle
定义:所有使用父类的地方可以使用子类的对象,子类可以扩展父类的功能,但是不能
替换父类的的功能。如果需要替换父类功能,建议——多用组合,少用继承。
依 赖 倒 置 原 则
DIP:Dependence Inversion
Principle
定义:
下层模块引入上层模块
的依赖
,改变原有自上而下的
依赖方向。
小结:设计原则 设计模式针对场景,比如,将设计原则比作孝顺,那么设计模式对应了不同的孝顺方法,如果资金充足,对父母的孝顺可以是买好的东西,如果资金不足的情况下,帮父母做些力所能及的事情,也是一种孝顺的方式。
设计模式设计原则课程中提到的组合,组合的话 应该就是不继承 直接将你需要的类实例化引用进来
接 口 隔 离 原 则:
定义:建立单一接口,不要建立臃肿庞大的接口。
接口尽量细化
,同时接口中的方法尽
量少。
含义:
①
接口要尽量小
不要违反单一职责原则。
要适度的小,要适度。
②
接口要高内聚
提高接口、类、模块的处理能力,减少对外的交互。
③
定制服务
通过对高质量接口的组装,实现服务的定制化。
迪 米 特 法 则 / 最 少 知 识 原 则
LoD:Law of Demeter
定义:只和你的密友谈话。
具体解释:一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)
的内部是如何复杂,
那是你的事儿,和我没关系
,我就知道你提供的这么多public方法,
我就调用这么多,其他的我一概不关系。
拓展知识:DDD
开 闭 原 则
定义:类、方法、模块应该
对扩展开放
,
对修改关闭
。
含义:添加一个功能应该是
在已有的代码基础上进行扩展,而不是修改已有的代码
。
创建型设计模式
创 建 型 模 式 的 特 点 和 分 类
创建型模式的
主要关注点
是“
怎样创建对象
”,它的
主要特点
是“
将对象的创建与使用分离
”。
这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
创建型模式分为以下5种。
1>
单例(Singleton)
某个类只能生成一个实例
,该类提供了一个全局访问点供外部获取该实例,其拓展
是有限多例模式。
2>
原型(Prototype)
将一个对象作为原型,通过对其进行复制而
克隆
出多个和原型类似的新实例。
3>
工厂方法(FactoryMethod)
定义一个用于
创建产品
的接口,由子类决定生产什么产品。
4>
抽象工厂(AbstractFactory)
提供一个
创建产品族
的接口,其每个子类可以生产一系列相关的产品。
5>
建造者(Builder)
将一个复杂对象
分解
成多个相对简单的部分,然后根据不同需要分别创建它们,最
后
构建
成该复杂对象。
volatile在单例中表示的含义是:线程和线程之间是有缓存的,就是所谓的线程的可见性,在一个线程中,我对一个数据进行修改的时候,另一个线程看不到,那怎样在一个线程中修改的值可以被看到呢?增加一个volatile就可以了,让他们之间的所有的修改和可见性直接在内存中完成。但是volatile保证不了线程安全,因为它解决的是可视性,是读的方面。
单 例 模 式 ( S i n g l e t o n )
定义:确保
一个类只有一个实例
,并提供一个全局访问点。
五种实现方式:
①
饿汉式
②
懒汉式
③
双重校验
④
静态内部类
⑤
枚举类
问:为什么静态内部类的方式是线程安全的?
答:首先要了解类加载过程中的最后一个阶段:即类的初始化,类的初始化阶本质就是执行类构造器的<clinit>方法。那么什么是<clinit>方法? 这不是由程序员写的程序,而是根据代码由javac编译器生成的。它是由类里面所有的【静态成员的的赋值语句】和【静态代码块】组成的。JVM内部会保证一个类的<clinit>方法在多线程环境下被正确的加锁同步、也就是说如果多个线程同时去进行“类的初始化”,那么只有一个线程会去执行类的<clinit>方法,其他的线程都要阻塞等待,直到这个线程执行完<clinit>方法。然后执行完<clinit>方法后,其他线程唤醒,但是不会再进入<clinit>方法。也就是说同一个加载器下。一个类型只会初始化一次。
5.枚举类(了解即可,一般不会这样用),使用枚举类的时候一般会有一些内存类的字典。枚举元素本身就是单例
原 型 模 式 ( P r o t o t y p e )
定义:用原型实例指定创建对象的种类,并且
通过拷贝
这些原型创建新的对象。也就是
说,这种
不通过new关键字来产生一个对象
,而是通过对象复制(Java中的
clone
或
反序
列化
)来实现的模式,就叫做原型模式。
性能优良:原型模式是在内存
二进制流的拷贝
,要比直接new一个对象性能好很多,特别
是要在一个循环内产生大量的对象时,原型模式可能更好地体现其优点。
逃避构造函数的约束,直接在内存中拷贝,
构造函数是不会执行的
。
浅拷贝
与
深拷贝
使用场景:
① 类的初始化需要消耗非常多的资源。
② 通过new产生一个对象需要非常繁琐的数
据准备或访问。
③ 一个对象需要提供给其他对象访问,而且
每个调用者可能都需要修改其值时。
简 单 工 厂 ( S i m p l e F a c t o r y )
定义:简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。
定义了一个创建对象的接口(类或接口中的方法),但由子类决定要实例化的类
是哪一个。工厂方法把实例化推迟到子类。
理解:把变动的代码抽取到一个类里面,就是一个简单工厂
工 厂 方 法 模 式 ( F a c t o r y M e t h o d )
定义:定义了一个创建对象的接口(类或接口中的方法),但由子类决定要实例化的类
是哪一个。工厂方法把实例化推迟到子类。
抽 象 工 厂 模 式 ( A b s t r a c t F a c t o r y )
定义:
提供一个接口,用于创建相
关或依赖对象的
家族
,而不
需要明确指定具体类。
工厂方法:
通过
抽象方法
提供对象生成
入口。
抽象工厂:
通过
接口
,来创建一组产品。
建 造 者 / 生 成 器 模 式 ( B u i l d e r)
定义:使用生成器模式,可以
封装一个产品的构造过程
,并允许
按步骤
构造产品。
优点:
1.将一个复杂对象的创建过程封装起来。
2.
允许对象通过多个步骤来创建,并且可以改变过程。
3.向客户隐藏产品内部的表现。
4.
产品的实现可以被替换,因为客户只看到一个抽象接口。
用途和缺点
1.经常被用来创建组合结构。
2.与工厂模式相比,采用生成器模式创建对象的客户,需要具备更多的领域知识。
结构型设计模式
构 造 型 模 式 的 特 点 和 分 类
结构型模式描述如何将类或对象
按某种布局组成更大的结构
。分为
类结构型
模式(采用
继承机制来组织接口和类)和
对象结构型
模式(釆用组合或聚合来组合对象)。
结构型模式分为以下 7 种:
•
代理(Proxy)
:为某对象提供一种代理以控制对该对象的访问。即:客户端通过代理
间
接地访问
该对象,从而限制、增强或修改该对象的一些特性。
•
适配器(Adapter)
:将一个类的接口转换成客户希望的另外一个接口,使得原本由于
接
口不兼容
而不能一起工作的那些类能一起工作。
•
桥接(Bridge)
:
将抽象与实现分离
,使它们可以
独立变化
。它是
用组合关系代替继承关
系
来实现的,从而降低了抽象和实现这两个可变维度的耦合度。
•
装饰(Decorator)
:动态地给对象增加一些职责,即:
增加其额外的功能
。
•
门面(Facade)
:为多个复杂的子系统
提供一个一致的接口
,使这些子系统更加容易被
访问。
•
享元(Flyweight)
:运用
共享技术
来有效地支持大量细粒度对象的复用。
•
组合(Composite)
:将对象
组合成树状层次结构
,使用户对单个对象和组合对象具有一
致的访问性。
代 理 模 式 ( P r o x y )
解决问题:当我们想要对一个业务类进行某些
横切性
的增强时,例如:增加请求与响应
的日志、增加权限校验、增加远程请求对象封装等等。我们可以采用代理模式去实现,
而不需要修改原有的类。
定义:代理模式为另一个对象提供一个
替身
或
占位符
,以控制对这个对象的访问。使用
代理模式创建代理对象,让代理对象控制某对象的访问,被代理的对象可以是远程的对
象、创建开销大的对象或需要安全控制的对象。
SpringAOP中采用的
JDK动态代理
,就是最典型的例子。
适 配 器 模 式 ( A d a p t e r )
定义:将一个类的接口,转换成客户期望的另一
个接口。适配器让原本
接口不兼容
的类可以合作
无间。
桥 接 模 式 ( B ri d g e )
定义:将抽象部分和实现部分,
分离解耦
,使得两者可以
独立地变化
。桥接模式通过
将
实现和抽象放在两个不同的类层次中
而使它们可以独立变化。
优点:
1.将实现予以解耦,抽象和实现可以独立扩展,不会影响到对方。
2.对于“具体的抽象类”所做的改变,不会影响到客户。
用途和缺点:
1.适合使用在需要跨越多个平台的图形和窗口系统上。
2.当需要用不同的方式改变接口和实现时,你会发现桥接模式很好用。
3.桥接模式的缺点是增加了复杂度。
装 饰 者 模 式 ( D e c o r a t o r )
问题:咖啡店之前有四种咖啡,分别是:HouseBlend、DarkRoast、Espresso和Decaf,
每个咖啡都有自己的cost()售价方法。但是,购买咖啡时,很多客户要求往里加入各种调
料,例如:Milk(牛奶)、Mocha(也就是巧克力风味)、Soy(豆浆)等。那么总价就
会有很多种组合方式,如果针对每种组合都实现具体类来提供总价cost(),那么就
会造成
类爆炸
。
定义:动态地将责任附加到对象上。若要
扩展功能
,装饰者提供了比继承
更弹性
的替代
方案。
门 面 模 式 ( F a c a d e )
问题:比如你去一个公司办理入职,如果人事需要你去找人事,公积金去自己办公积金
转移,公司的电脑需要自己采购及安装公司要求的软件,工位需要自己去申请分配,工
资卡需要自己去维护进去……那估计你这一套没有办完你就走了。你肯定很气愤,我就
是办理入职,为什么什么都需要我自己去办? 这个公司也太不正规了吧。我之前办理入
职的时候,都是一群入职的同学到一个会议室,漂亮的HR小姐姐们,把各种资料信息电
脑都准备好了,签个劳动协议的字就可以了啊。
定义:提供了一个
统一的接口
,用来访问子系
统中的一群接口。外观定义了一个
高层接口
,
让子系统更容易使用。
享 元 / 蝇 量 模 式 ( F l y w e i g h t )
问题:比如有一个业务场景,就是有2000万人要填报自己的个人信息用于核算检测数据
采集。采集表里包含:姓名,居住地址,要去哪个核酸检测医院或网点,做的是什么品
牌的核算。那么针对于这些信息,我们如果每个人一份这样的信息,其实是对空间的浪
费。因为,我们可以看出来,姓名和居住地址是
个性化
的,但是核酸检测的医院或网点,
以及核酸的品牌这些都是
固定的
。那么我们就可以采取将这些通用实例池化,然后直接
去池里获取即可。
定义:享元模式是
池技术
的重要实现方式,其定
义如下:
使用共享对象可以有效地支持大量的细
粒度的对象
。
组 合 模 式 ( C o m p o s i t e )
定义:将对象组合成
树形结构
以表示“
部分-整体
”的层次结构。组合模式使得用户对单个
对象和组合对象的使用
具有一致性
。
行为型设计模式
行 为 型 模 式 的 特 点 和 分 类
定义:行为型模式用于描述程序在运行时
复杂的流程控制
,即:描述多个类或对象之间
怎样
相互协作
共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分
配。
行为型模式分为
类行为模式
(采用继承机制来在类间分派行为)和
对象行为模式
(采用
组合或聚合在对象间分配行为)。由于组合关系或聚合关系比继承关系耦合度低,满足
“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是GoF设计模式中最为庞大的一类,它包含以下 11 种模式。
•
模板方法(Template Method)
:定义一个操作中的算法骨架,将算法的一些步骤延迟
到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
•
策略(Strategy)
:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,
且算法的改变不会影响使用算法的客户。
•
命令(Command)
:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任
分割开。
•
职责链(Chain of Responsibility)
:把请求从链中的一个对象传到下一个对象,直到请
求被响应为止。通过这种方式去除对象之间的耦合。
•
状态(State)
:允许一个对象在其内部状态发生改变时改变其行为能力。
•
观察者(Observer)
:多个对象间存在一对多关系,当一个对象发生改变时,把这种改
变通知给其他多个对象,从而影响其他对象的行为。
•
中介者(Mediator)
:定义一个中介对象来简化原有对象之间的交互关系,降低系统中
对象间的耦合度,使原有对象之间不必相互了解。
•
迭代器(Iterator)
:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合
对象的内部表示。
•
访问者(Visitor)
:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访
问方式,即每个元素有多个访问者对象访问。
•
备忘录(Memento)
:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以
便以后恢复它。
•
解释器(Interpreter)
:提供如何定义语言的文法,以及对语言句子的解释方法,即解
释器。
模 板 方 法 模 式 ( T e m p l a t e M e t h o d )
定义:在一个方法中定义一个
算法骨架
,而将一些步骤延迟到子类中。模板方法使得子类
可以在不改变算法结构的情况下,
重新定义算法中的某些步骤
。
策 略 模 式 ( S t r a t e g y )
定义:定义了
算法族
,分别封装起来,让它们之间可以互相替换,此模式
让算法的变化
独立于使用算法的客户
。
命 令 模 式 ( C o m m a n d )
定义:将“请求”封装成
命令对象
,以便使用不同的请求、队列或日志来参数化其他对象。
责 任 链 模 式 ( C h a i n o f R e s p o n s i b i l i t y )
定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关
系。将这些对象
连成一条链
,并沿着这条链传递该请求,直到有对象处理它为止。
状 态 模 式 ( S t a t e )
定义:允许对象在
内部状态改变时改变它的行为
,对象看起来好像修改了它的类。
观 察 者 模 式 ( O b s e r v e r )
定义:定义了对象之间的
一对多依赖
,这样一来,当一个对象改变状态时,它的所有依
赖者都会
收到通知
并自动更新。
中 介 者 模 式 ( M e d i a t o r )
定义:使用中介者模式来
集中
相关对象之间复杂的
沟通和控制方式。
迭 代 器 模 式 ( I t e r a t o r )
定义: 提供一个方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
访 问 者 模 式 ( V i s i t o r )
定义:表示一个作用于某个对象结构中的各元素的操作。它使你可以在不改变各元素的
类的前提下定义作用于这些元素的新操作。
备 忘 录 模 式 ( M e m e n t o )
定义: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
对象之外保存这个
状态
,这样以后就可以将该对象恢复到原先保存的状态。当你需要让对象返回之前的状
态时(例如:你的用户请求“撤销”),就使用备忘录模式。
针对面试,设计模式会怎么问?
创建型模式中:
1.单例,有五种实现方式,前四种非常重要!饿汉式是什么?懒汉式是什么?什么是双重校验?什么是静态内部类?用什么优化可以使得饿汉式变成懒汉式?懒汉式又会出现哪些问题?懒汉式优化之后变成双重校验,在双重校验中又有哪些问题?双重校验优化后变成静态内部类,静态内部类又有哪些问题?
2.建造者模式
结构型模式中:
1.代理,会引出什么?CGlib,动态JDK,静态代理,动态代理,什么是代理?为什么有代理?代理能够做什么?这些是关键的。
适配器,也会涉及到。
装饰者,会问到
门面,可能会涉及到一点点。
行为模式中:
模板模式,
命令,
责任链(选择性掌握即可,可掌握可不掌握),
状态,
观察者,
中介者,
访问者(选择性掌握即可,可掌握可不掌握,如果面试官问到,那说明面试官挺刁钻的)
一般面试官会问你,工作中你用过什么设计模式,回答之后面试官可能会继续问,你的场景是怎样的?为什么要用这种设计模式?用别的模式行不行?
针对框架,比如Spring,MyBatis等等,如果看过底层源码,则会问,你看过源码之后,有没有看过源码中相对使用了什么设计模式?大概在什么地方?怎么用的?或者在源码上进行提问。一般都是基于你工作中怎么去用的来提问。应届生一般不会从工作上去问,因为没经验,可能直接问你,是否看过设计模式?如果你看过,或者简历中写了,那么会问你对哪个设计模式比较熟悉?还可能问到,设计模式分成几大块?3大块,创建型、结构型、行为型。建议准备:单例,代理,模板方法,中介者