前言
官方的话叫书,我自己的理解则是说书,所以没必要这么死板
不然和复制粘的yxh有什么区别
23个设计模式按照六个创建型模式、七个结构型模式、十一个行为型模式来介绍
6+7+11=23,没毛病
六五个创建型模式
简单方法模式
?
工厂方法模式
告诉我你要什么,给你什么
相较简单工厂模式可以提供定制化服务
大部分的库都是采用此模式
抽象工厂模式
更强的定制化,你是要哪种颜色的帽子?
需要定义一个父级抽象类,然后通过子类继承该抽象类实现自定义修改
缺点:如果改变了需求则需要重写整个代码
awt采用此模式
建造者模式
和工厂方法类似,在创建过程中可以对对象进行细微调整
缺点:如果目标对象差异化很大则不适用
mybatis的大部分是基于建造者模式创建的
okhttp应该也是,正巧最近用到了
//创建一个多header的post,body类型是payload
public static Response postPayLoad(String url, String json,Map<String,String> stringHashMap) throws IOException {
RequestBody requestBody = RequestBody.create(JSON, json);
//创建创建者,并自定义各种需要设置的属性(比如url,请求体,和多个header)
Request.Builder builder = new Request.Builder().url(url).post(requestBody);
for ( Map.Entry<String, String> entry: stringHashMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
builder.addHeader(key,value);
}
//让创建者创建请求
Request request = builder.build();
//客户端执行请求,并得到响应
Response response = client.newCall(request).execute();
return response;
}
单例模式
保证对象只有一个,可控(多线程的时候)
缺点:3点。职责不单一,甚至繁重;扩展困难;鸡蛋都在一个篮子
原型模式(多例)
复印机
值得关注的是,大多数语言都有浅拷贝和深拷贝
对于值类型无区别,但是引用类型会重新创建一份再复制,使用时需要注意
(会去了解设计模式的人我觉得也不会关心这个...)
七个结构型模式
外观模式
为子系统中的一组接口提供一个统一的入口
举例:你问门卫哪里怎么走
大多数程序都是按照此模式来设计的
代理模式
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问
举例:不要以为是spring,他并没有代理功能,是cglib来做的
组合模式
创建一个包含自己对象组的类,同时该类也提供修改相同对象组的方式。
说人话:1+3=4
------ 1+(1+(1+1))=4
每个计算数都是组件,但组件还可以继续拆分为更细的计算数
比如,awt和swing中的btn和checkbox都是具体的叶对象,而container则是枝节点
也就是说一个页面所有的对象都可以看成一个组件,他包含组件的名称等属性,以及他是否包含子组件(个人的理解)
另外经常遇到的就是多层结构的js对象了
{
code: 200,
data: {
name: '一级目录',
child: {
name: '二级目录',
child: {
name: '三级目录',
child: null
}
}
}
}
适配器模式
客户端通过适配器来让适配者匹配为目标接口
举例:不要举插座例子了,咱换一个
资本家想压迫员工,但是直接压迫会有失体面,所以
当面对业务无法轻易更改而且种类繁多的时候可以考虑使用适配器模式
一般适配器模式有两个主要模式,对象适配器和类适配器
模式 | 通用优点 | 各自优点 | 各自缺点 |
---|---|---|---|
类适配器 | 不管啥都能处理,灵活 | 被适配的是子类,听话,可修改 | 多为Java语言特性造成,适配对象不可为final,不可同时适配多个类,目标类只能是接口 |
对象适配器 | 旧有的方法类一个不用改,可复用性高 | 可以适配不同的对象,或者他们的子类 | 2个字,麻烦 |
这里值得一提的是,因为java是单继承,所以java的类适配器会比较扭曲
最优(无脑)的情况是,适配器既可以是适配者的子类,同样也是多个目标类的子类形成多继承
这样适配器的逻辑工作都交给编译器就行了,让他们俩自己个儿去配对
springMVC的controller就是适配器模式,因为要适配的种类多(匹配地址映射,是否异常,是否过filter,过什么视图等等)
桥接模式
首先它是一个对象模型结构,用来分离对象的抽象部分和实现部分,通过在抽象部分实现实现部分的接口来达到桥接的目的
例子:
角色 | 释放 | 技能 |
---|---|---|
抽象部分 | 实现 | 实现部分 |
因此角色就是抽象部分,而技能则是实现部分,让他们俩能连起来就是桥接模式的作用
和以往抽象的概念不同的是,此处的抽象部分是一般是一个统称(共有模型)
一般该抽象部分会有实现类,继续拿角色释放技能来举例
角色 | 释放 | 技能 |
---|---|---|
张三 | 犯 | 法 |
张三就是角色的实现类
该模式主要避免继承关系的出现
比如,人和鱼都可以呼吸,但2个对象的种类差的非常多,但呼吸又是通用的,完全可以使用桥接模式来复用呼吸这个实现部分
不过你有本事做个父类叫动物然后一路继承做出了人和鱼,那我也无话可说,希望你产品能在我领养老金之前上线
装饰器模式
不改变对象结构的前提下,为对象动态的添加功能
举例:
抽象组件 | (继承于←)抽象组件的实现类 | 抽象装饰类 | (继承于←) 抽象装饰类的实现类 |
---|---|---|---|
练习生 | 某蔡姓练习生 | 进行练习 | 唱、跳、rap、打篮球 |
(抽象组件和抽象装饰类都继承/实现于共有父类)
对于客户端(使用时)而已,用代码表示大概是这样子的
练习生 练习生 = new 练习生();
练习生 蔡某 = new 进行练习(练习生);
//练习生经过练习学会了如下技能
蔡某.唱();
蔡某.跳();
蔡某.rap();
蔡某.打篮球();
注意,此种属于透明装饰模式
另外还有一种半透明装饰模式
(在不改变对象结构的基础上)希望省略多余的装饰过程,则可以考虑此种模式
再次举例:
比如成为偶像需要经过刻苦练习
练习生 练习生 = new 练习生();
练习生 努利练习的练习生 = new 进行练习(练习生);
偶像 偶像 = (努利练习的练习生)-> {
while(true) {
努利练习的练习生.努利练习();
if(真的很努力了) return 成为偶像(努利练习的练习生);
}
}
偶像.获奖();
但某练习生的资方背景雄厚,根本没必要去练习,那么他想直接上台领奖怎么办?
练习生 练习生 = new 练习生();
偶像练习生 蔡某 = new 进行包装(练习生); //偶像练习生继承于练习生
蔡某.靠着一首英文歌荣获2019华语金曲奖();
相较于透明装饰,半透明代码简洁,但是他改变了对象的类型导致较难再进行装饰
关于为什么叫透明和半透明:
对于程序来说,第一种他是知道对象的类型是什么(练习生)
因为new时和进行装饰后的对象类型并没有发生改变
但第二种则在装饰后对象类型发生了改变
因此其特点就是子类(偶像练习生)可以实现自己方法(氪金拿奖),但是父类(普通的练习生)并不能使用子类的方法(很难再进行装饰)
此外就是翻译的问题了
transparent
adj.透明的;清澈的;易识破的;易看穿的
我觉得应该叫可识装饰模式、半可识装饰模式
据说awt的某些组件也是用此功能,没怎么接触过ui组件不评论
享元模式
运用共享,有效地支持大量细粒度对象的复用。
举例:string常量池
一般享元对象需要划分内部状态和外部状态
内:a,b,c...他们状态是共享的,且不可变
外:a和c相组合,b和i l 相组合,他们的状态是不共享的,但可变(a可以和c组合,也可以和v组合)
缺点:设计起来非常麻烦,因为要划分内外状态
一般享元都是和别的模式组队出现,比如最常见的+工厂,+组合产生复合享元(批量修改属性)
一般不太会用到此类设计模式
十一个行为型模式
职责链模式
当多个对象需要处理请求,则将这些对象连接成一条链,并不断传递请求直到有对象处理为止
使用场景:当存在大量if else的时候可以使用,让每个处理职责单一化
注意,一般由客户端来创建职责链
比如:
//客户端
美食博主 老八 = new 美食博主();
String[] 食材 = {"臭豆腐","俘虏","柠檬","奥里给"};
结果 结果 = 老八.你看这汉堡它做的行不行(食材); //创建职责链
/////////////////////////////具体业务一瞥///////////////////////////////
public 结果 你看这汉堡它做的行不行(String[] 食材) {
if (不是奥里给(食材[0])) return 第二个食材是不是奥里给(食材);
else return "呕";
})
public 结果 第二个材料是不是奥里给(String[] 食材) {
if (不是奥里给(食材[1])) return 第三个食材是不是奥里给(食材);
else return "呕";
})
//后续省略...
此外职责链还可细分为纯职责链(如上)和不纯职责链
纯:处理后不再传递
不纯:处理后继续传递。有点像流式编程,比如js的阻止冒泡行为就是为了阻止不纯职责链的继续
观察者模式
使得每当一个对象状态发生改变时,其相关依赖对象都会收到通知,并被自动更新
举例:太多了...比如vue
需要一个观察者抽象类,被观察者抽象类,并有各自的实现类
public interface 观察者的接口 {
public void 作出反应();
}
public class 观察者 implements 观察者的接口{
...
//实现接口
//重写做出反应方法
}
////////////////////////////////////////////////////
public interface 被观察者的接口 {
public void 添加观察者(); //也可以添加多个观察者
public void 删除观察者();
public void 通知观察者();
}
public class 被观察者 implements 被观察者的接口{
...
//实现接口
//重写通知观察者()
}
public static void main(Strings[] args){
被观察者 茄子 = new 被观察者();
观察者 秋梨膏 = new 观察者();
茄子.添加观察者(秋梨膏);
茄子.通知观察者("A1高闪来一个"); //秋梨膏.作出反应();
}
虽然是观察者,但我觉得更像是被观察者通知,观察者听命的关系
缺点:添加多个观察者会占用资源;容易产生观察者与被观察者之间的循环依赖
中介者模式
用一个中介对象来封装的各个对象的交互过程,是最少知识原则的体现
举例:
道具 金克拉 = new 道具();
人物 非非 = new 人物(); //人物类实现了中介者接口
人物 日日 = new 人物();
非非.抢夺(金克拉,"金克拉是我们的");
日日.抢夺(金克拉,"金克拉是我们的");
//调停者同样实现了中介者接口
调停者 美美 = new 调停者();
美美.调停("不要打架,金克拉好处都有啥,谁说对了就给他");
//此处人物实现中介者的接口,因此才有了method辩论()
原因 非非的原因 = 非非.辩论("非洲农业不发达,必须要有金克拉");
原因 日日的原因 = 日日.辩论("日本资源太缺乏,必须要用金坷垃");
结论 美美的结论 = 美美.思考(非非的原因,日日的原因);
美美.执行结果(美美的结论); //非洲农业不发达 ,我们都要支援他
对于以下场景适用
- 对象之间关系复杂
- 希望通过公共类来抽取多个重复方法的时候
但也有两点需要注意
- 所有对象都要继承/实现中介者的接口
- 需要在中介者的实现类中完成所有对象之间的通讯
从上述小剧场就可以看出,调停者的工作非常多,因此很容易就造成后期调停者业务逻辑没法维护,这也是该模式的弊端
策略模式
将策略(算法)与环境分离,并封装所有策略类,使他们在环境中被使用时,可以互相替换
举例:
//客户端的具体调用
...
王境泽的当前环境 环境 = new 王境泽的当前环境(); //肚子很饿
环境.设置王境泽的策略(new 真香());
环境.执行();
...
//环境部分
class 王境泽的当前环境{
private 王境泽的抽象策略 策略;
public void 设置王境泽的策略(王境泽的抽象策略 策略) {
this.策略 = 策略;
}
//具体调用
public void 王境泽执行了策略() {策略.王境泽的算法();}
}
//策略部分
//所有策略都必须继承于抽象策略
public class 我就是死外边 extends 王境泽的抽象策略 {
@Override
public void 王境泽采取的策略(){...} //省略了具体算法内容
}
public class 从这里跳下去 extends 王境泽的抽象策略 {
@Override
public void 王境泽采取的策略(){...}
}
public class 也不吃你们一口东西 extends 王境泽的抽象策略 {
@Override
public void 王境泽采取的策略(){...}
}
public class 真香 extends 王境泽的抽象策略 {
@Override
public void 王境泽采取的策略(){...}
}
优点:使用起来非常灵活
缺点:客户端必须了解有哪些可选择的类,在设计时会创造出太多小类,同一时间无法使用多个策略
状态模式
允许当对象的状态发生改变时,改变他的行为
说人话:这只竹鼠中暑了(状态发生了改变),带他到河边洗澡(改变他的行为)
public static void main(Strings[] args){
ArrayList<竹鼠> 竹鼠们 = new 竹鼠们(); //假设在此处已经随机初始化所有的竹鼠了
竹鼠们.foreach((竹鼠)-> {
结果 结果 = 竹鼠.检查竹鼠();
结果.显示(); //中暑的竹鼠需要带到河边去洗澡
})
}
//对象
public class 竹鼠 {
private 抽象状态类 状态;
private boolean 这只竹鼠动吗;
public 结果 检查竹鼠() {
return 状态.检查状态();
}
//省略get set
}
//抽象状态类
abstract class 状态抽象类 {
public 竹鼠 竹鼠;
abstract void 检查状态();
}
//抽象状态的实现类
public class 健康的竹鼠 extends 状态抽象类 {
//省略通过构造器获取抽象父类的竹鼠对象
public 结果 检查状态() {
if (!竹鼠.这只竹鼠动吗) return new 中暑的竹鼠(竹鼠); //将状态切换的代码放到具体的状态中
return new 结果("健康的竹鼠");
}
}
public class 中暑的竹鼠 extends 状态抽象类 {
//省略通过构造器获取抽象父类的竹鼠对象
public 结果 检查状态() {
if (竹鼠.这只竹鼠动吗) return new 健康的竹鼠(竹鼠);
return new 结果("中暑的竹鼠");
}
}
感觉结构上类似于策略模式,但代码的实现远比策略模式复杂且混乱
(因为需要判断状态而不是指定策略)
当遇到对象状态会发生改变,或需要替换if/else的时候可以使用
(实际开发时状态尽量少于5个)
备忘录模式
在不破坏封装的前提下,捕获并保存一个对象的内部状态,需要时再恢复。
举例:事务管理,ctrl+z等
该模式下有3个角色,用一个拍照例子来描述这三者的关系
- 负责人:我,只负责请求储存(按快门)/读取内容(看照片),无法修改内容(照片)
- 原发器:照相机,负责创建内容(拍摄照片)和恢复内容(展示照片)
- 备忘录:照片,被拍下的瞬间状态的属性
使用备忘录会额外占用系统资源
这个模式不熟,不过多评论
命令模式
将一个请求封装为一个对象,使发送者和接收者完全解耦
大部分可视化的操作(复制粘贴)都是此模式
优点:降低耦合,可以处理批量请求或进行组合(因为每一个请求都是独立的对象)
缺点:需要为每一个请求都制作一个类,会产生过多的命令类
解释器模式
正常情况不会去设计这个模式的,正则表达式是他的应用
hibernate的hql应该也属于
迭代器模式
提供一种访问聚合对象的方法,但又不暴露细节。
(名词解释,聚合对象:List<Shabi>,聚合类:Shabi,聚合对象拥有聚合类)
主要为了分离储存和遍历两种行为,用的多,设计的少。
模板方法模式
定义一个骨架,将一些步骤延迟到子类中实现(重写)
模板方法使得子类可以不改变主程序结构即可重定义其中某些特定步骤
我觉得类似于框架师写好接口让底下板砖的去实现
比较常见
访问者模式
将数据结构与数据操作分离
举个例子:
普通员工有考评时会加入具体工作量的参数
但狗HR可能只有吹牛逼的能力
这样在统计年度工作能力的时候会出现要分开统计的情况
因此可以做一个接口
public interface 考核接口{
void 考核(普通员工 普通员工);
void 考核(狗HR 狗HR);
}
通过传入不同的对象来实现重载,得以区分2者的考评指标
对于扩展考核内容很容易,但新增一个种类就很麻烦,有点类似于抽象工厂模式
因为很少出现,不提了
总结
还是和六大设计原则一样,设计模式是前人总结的经验
在有开发经验后去看会发现,啊~原来是这样
屁都不懂的问这个根本是看不懂没体会的
就和你妈告诉你从小要好好读书,直到被老板压榨的喘不过气的时候才明白为啥
可惜人类从失败中吸取的教训就是永远不会从失败中吸取教训
网友评论