资料学习整理自:B站尚硅谷
我自己写了套代码理解,已上传 码云
由于设计模式是个大题,内容繁多,我的计划是先学部分常用的,再逐步全面化。
设计模式
Design Pattern
概念介绍
1)设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,是某类问题的通用解决方案,设计模式代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
2)设计模式的本质提高软件的维护性、通用性和扩展性,并降低软件的复杂度。
类型
23个设计模式分为三种类型:
1)创建型模式:单例模式、抽象工厂模式、工厂模式、原型模式、建造者模式;
2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;
3)行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter)、状态模式、策略模式、职责链模式(责任链)。
1、单例模式(创建型)
定义:在整个软件系统中,确保某个类只有一个实例,并且只提供一个获取实例的入口。
场景
重量级对象,不需要多个实例。如:线程池、数据库连接池等
实现方法:
1、饿汉式(静态常量)
private Singleton(){}
private static Singleton singleton = new Singleton();
public Singleton getInstance(){
return singleton;
}
结论:可能会造成内存浪费。
2、饿汉式(静态代码块)
private Singleton(){}
private static Singleton singleton;
static{
if(singleton==null){
singleton = new Singleton();
}
}
public Singleton getInstance(){
return singleton;
}
结论:可能会造成内存浪费。
3、懒汉式(单线程)
private Singleton(){}
private static Singleton singleton;
public Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
结论:多线程不安全。不使用。
4、懒汉式(同步方法)
private Singleton(){}
private static Singleton singleton;
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
结论:效率太低。不推荐使用。
5、懒汉式(同步代码块 )
private Singleton(){}
private static Singleton singleton;
public static Singleton getInstance(){
if(singleton==null){
synchronized(Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
结论:多线程不安全。不使用。
6、懒汉式(DCL双重检查)
private Singleton(){}
private static volatile Singleton singleton;
public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
return singleton;
}
}
结论:懒加载、线程安全、保证了效率。推荐使用。
7、静态内部类
private Singleton(){}
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
1)静态内部类在外部类装载的时候并不会执行,当调用getInstance时,静态内部类才会被装载:懒加载;
2)利用jvm类装载的机制来保证初始化实例的时候只有一个线程执行,静态属性只在第一次加载类时初始化 :线程安全;
结论:懒加载,线程安全,效率高。推荐使用。
8、枚举
enum Singleton(){
INSTANCE;
}
JDK1.5添加的枚举,天生单例,并且在反射源码中被直接禁止重新创建实例。是Effetive Java作者Josh Bloch提倡的方式。
反编译枚举类得知:枚举属于饿汉式(静态代码块)
应用场景:
-
需要频繁的进行创建和销毁的对象;
-
创建对象时 耗时较长或耗费资源过多的对象,但又经常用到的对象、工具类
-
频繁访问是数据库或文件的对象(数据源、session工厂等)
2、工厂方法模式(创建型)
# 工厂模式:以写歌为案例
写歌的操作一样:
- 作曲 compose()
- 作词 lyrics()
- 编曲 arrangement()
- 录音 recording()
歌曲有很多种类型的曲风:
- 民谣 ballad
- R&B RB
- 摇滚 rock
- 说唱 rap
简单工厂模式
当代码完成后,需要新增加一种歌曲类型:jazz
只需要两步,并且不影响其他原有的歌曲类型
1、创建 JazzSong 类,实现内部业务方法
2、在工厂类增加一个类型判断
/**
* 简单工厂模式
* @author: stone
* @create: 2020-09-16 00:08
*/
public class SimpleFactory {
ISong song;
public ISong getSong(String songType){
if("ballad".equalsIgnoreCase(songType)){
song = new BalladSong();
}else
if("rb".equalsIgnoreCase(songType)){
song = new RBSong();
}else
if("rock".equalsIgnoreCase(songType)){
song = new RBSong();
}else
if("rap".equalsIgnoreCase(songType)){
song = new RBSong();
}else
if("jazz".equalsIgnoreCase(songType)){
song = new JazzSong();
}else{
song = null;
}
return song;
}
}
工厂方法模式
定义一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
个人理解:创建一个子工厂类去继承原来的简单工厂类,原简单工厂类中的生产方法由子工厂类去继承实现,原工厂类升级为抽象类。子工厂类代表一种维度上的扩展,产品类又代表另一种维度上的扩展。而原简单工厂类中生产的方法下沉到具体子工厂类中,称之为方法下沉/推迟,因此得名工厂方法模型!
ISong song;
String countryName;
//创建具体歌曲的方法下沉到具体子工厂类中
public abstract ISong getSong(String songType);
public void makeSong(){
do{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入想要写歌的歌曲类型:");
try {
ISong song = getSong(bufferedReader.readLine());
if(song==null){
System.out.println("say good bye ~~");
break;
}
song.compose();
song.lyrics();
song.arrangement();
song.recording();
} catch (IOException e) {
e.printStackTrace();
}
}while (true);
}
public class ChinaFactory extends BaseFactory {
@Override
public ISong getSong(String songType) {
if("ballad".equalsIgnoreCase(songType)){
song = new BalladSongChina();
}else
if("rap".equalsIgnoreCase(songType)){
song = new RapSongChina();
}else{
song = null;
}
return song;
}
}
3、抽象工厂模式(创建型)
定义了一个interface用于创建相关或有 依赖关系的对象簇,而无需指明具体的类。
抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。工程师可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
/**
* 抽象工厂类
* @author: stone
* @create: 2020-09-17 22:52
*/
public interface AbsFactory {
/**
* 具体写歌类的业务由子类实现
* @param type
* @return
*/
ISong makeSong(String songType);
}
/**
* 具体工厂类
* @author: stone
* @create: 2020-09-16 01:50
*/
public class ChinaFactory implements AbsFactory {
@Override
public ISong makeSong(String songType) {
ISong song;
if("ballad".equalsIgnoreCase(songType)){
song = new BalladSongChina();
}else
if("rap".equalsIgnoreCase(songType)){
song = new RapSongChina();
}else{
song = null;
}
return song;
}
}
一个关键点,建立工厂接口类,子类继承实现具体内容。
使用时用接口工厂接收调用。
工厂模式小结
将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目依赖关系的解耦,从而提高项目的扩展和维护性。(依赖抽象原则)
- 创建对象实例时,不要直接new类,而是把这个new的动作放在一个工行的方法中,并返回。(有的书上说:变量不要直接持有具体的类引用);
- 不要让类继承具体类,而是继承抽象类或实现接口;
- 不要覆盖基类中已经实现的方法。
4、原型模式(创建型)
5、建造者模式(创建型)
6、适配器模式(结构型)
7、桥接模式(结构型)
8、装饰者模式(结构型)
解决类爆炸
定义:
装饰着模式,动态地将新功能附加到对象上。
在对象功能扩展方面,它比继承更有弹性,装饰着模式也体现了开闭原则(ocp);
有点构造器递归的意思
需求:
星巴克咖啡订单项目
1)咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
2)调料:Milk、Soy(豆浆)、Chocolate
3)要求在扩展**新的咖啡种类**时,具有良好的扩展性、改动方便、维护方便
4)使用面向对象的方式来计算不同种类咖啡的费用:客户可以单点咖啡,也可以单点咖啡+调料
使用装饰者模式解决:
/**
* 基类 饮料
* @author: stone
* @create: 2020-09-18 00:03
*/
@Data
public abstract class Drink {
public String name;
public double price;
/**
* 计算价格
* @return
*/
public abstract double cost();
}
/**
* 装饰者类的基类
* @author: stone
* @create: 2020-09-18 00:19
*/
@Data
public abstract class Decorator extends Drink {
//添加组合关系
private Drink drinkObj;
/**
* 传入需要装饰的主体
* @param drinkObj
*/
public Decorator(Drink drinkObj){
this.drinkObj = drinkObj;
}
/**
* 实现具体业务
* 计算通式:调料的价格 + 单品咖啡的价格
*/
@Override
public double cost() {
return super.getPrice() + drinkObj.cost();
}
}
/**
* @author: stone
* @create: 2020-09-18 00:36
*/
public class TestMain {
public static void main(String args[]){
//1、来一个意大利咖啡
Drink drink = new Espresso();
System.out.println("1、来一个意大利咖啡:");
System.out.println(drink.name + " 价格为: " + drink.cost());
System.out.println("=====================================");
//2、加一份牛奶
drink = new PureMilk(drink);
System.out.println("2、加一份纯牛奶(牛奶类价格双倍~):");
System.out.println(drink.name + " 价格为: " + drink.cost());
System.out.println("=====================================");
//3、加一份巧克力
drink = new Chocolate(drink);
System.out.println("3、加一份巧克力:");
System.out.println(drink.name + " 价格为: " + drink.cost());
System.out.println("=====================================");
System.out.println("=== 业务需要。扩展一个无因咖啡,只需要加一个主体的类,既可以跟全部配料任意搭配 ===");
System.out.println("=====================================");
//业务需要。扩展一个无因咖啡
//1、来一个无因咖啡
Drink drink2 = new Decaf();
System.out.println("1、来一个无因咖啡:");
System.out.println(drink2.name + " 价格为: " + drink2.cost());
System.out.println("=====================================");
}
}
代码已上传码云,就不贴了
console
1、来一个意大利咖啡:
意大利咖啡 价格为: 15.5
=====================================
2、加一份纯牛奶(牛奶类价格双倍~):
纯牛奶 价格为: 25.5
=====================================
3、加一份巧克力:
巧克力 价格为: 28.5
=====================================
=== 业务需要。扩展一个无因咖啡,只需要加一个主体的类,既可以跟全部配料任意搭配 ===
=====================================
1、来一个无因咖啡:
无因咖啡 价格为: 10.88
=====================================
Process finished with exit code 0
小结
关键在于基类的抽象,然后装饰类与主题的“组合”:将被装饰者传入装饰类。
//装饰者
public abstract class Decorator extends Drink {
//添加组合关系
private Drink drinkObj;
}
解决调料和单品的全搭配导致类爆炸问题。
9、组合模式(结构型)
10、外观模式(结构型)
11、享元模式(结构型)
12、代理模式(结构型)
Proxy
基本介绍
- 代理模式:为一个对象提供一个替身,以控制对这儿对象的访问。即通过代理对象访问目标对象。这样可以在目标对象实现的基础上,增强额外的功能操作。(即扩展目标对象的功能)
- 被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
- 代理模式有三种形式:静态代理、动态代理、Cglib代理
- 静态代理
- 动态代理:也叫 JDK代理、接口代理
- Cglib代理:可以在内存动态的创建对象,而不需要实现接口,也属于动态代理。
静态代理
基本介绍:
静态代理在使用时,需要定义接口或父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同的父类。
/**
* 抽象类
* 声明需要执行的方法
* @author: stone
* @create: 2020-09-18 21:42
*/
public abstract class ITeacherDao {
public abstract void teach();
}
/**
* @author: stone
* @create: 2020-09-18 21:48
*/
public class TestMain {
public static void main(String args[]){
//目标对象
ITeacherDao teacherDao = new TeacherDao();
//代理类
ITeacherDao teacherProxy = new TeacherProxy(teacherDao);
//执行代理类的方法来间接调用目标类
teacherProxy.teach();
}
}
console
代理老师来代理了~~~
目标老师上课中~~
代理老师走了~~~
Process finished with exit code 0
静态代理的优缺点:
# 优点:
在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
# 缺点:
因为代理类需要与目标类实现或继承一样的接口或父类,所以会需要写很多个代理类,一旦接口或父类增加了方法,目标类和代理类都需要修改增加。
动态代理
JDK代理、接口代理
基本介绍:
- 代理类不需要实现接口或继承父类,但是目标类仍需要实现接口或继承父类
- 代理类的生成是利用JDK的API,动态地在内存中构建代理对象
JDK中生成代理对象的API:
- 所在包:java.lang.reflect.Proxy
- JDK实现代理只需要使用newProxyInstance方法:
static Object newProxyInstacne(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
代码实现:
/**
* 动态代理模式
* 目标类
* @author: stone
* @create: 2020-09-19 00:49
*/
public class TeacherDao implements ITeacherDao{
@Override
public void teach() {
System.out.println("dynamic proxy is teaching !!!");
}
@Override
public String sayHi(String word) {
System.out.println("hi "+word);
return "七里香";
}
}
/**
* 动态代理模式
* 代理工厂
* 不继承任何类,可以动态代理任何类
* @author: stone
* @create: 2020-09-19 00:51
*/
public class ProxyFactory {
//接收需要代理的目标对象
Object target;
public ProxyFactory(Object target){
this.target = target;
}
/**
* 创建目标类的代理实例
* @return
*/
public Object getProxyInstance(){
/**
* 使用放射API实现
*
* 源码: public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* 1、loader:目标类的类加载器
* 2、interfaces:目标类的
* 3、事件处理对象(也就是代理的具体方法/函数/事件)
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//此处是:当目标对象的方法被调用时,触发的事件
System.out.println("ProxyFactory invoke is action. args: "+args.toString());
Object retuenVal = method.invoke(target, args);
System.out.println("ProxyFactory invoke is done.");
return retuenVal;
}
});
}
}
/**
* 应用
* @author: stone
* @create: 2020-09-19 01:02
*/
public class Client {
public static void main(String args[]){
//目标对象
ITeacherDao teacherDao = new TeacherDao();
//代理类
ProxyFactory proxyFactory = new ProxyFactory(teacherDao);
//利用代理类创建代理对象
ITeacherDao proxyTeacherDao = (ITeacherDao) proxyFactory.getProxyInstance();
//使用代理对象执行方法
// proxyTeacherDao.teach();
System.out.println("hello " +proxyTeacherDao.sayHi("石似心"));
}
}
console:
ProxyFactory invoke is action. args: [Ljava.lang.Object;@61bbe9ba
hi 石似心
ProxyFactory invoke is done.
hello 七里香
Process finished with exit code 0
Cglib代理
子类代理
基本介绍:
- 静待代理和JDK代理都要求目标对象实现一个接口,但是有的时候只是一个单独的类,并没有实现任何的接口,这个时候可使用目标对象子类来实现;
- Cglib代理是在内存中构建一个子类对象从而实现对目标对象功能扩展,所以有时候也可以说Cglib代理属于动态代理;
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。Cglib被广泛应用在AOP框架中,如Spring AOP实现方法拦截;
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类的。
使用说明:
- 需要引入jar包:asm.jar、asm-commons.jar、asm-tree.jar、cglib-2.2.jar;
- 在内存中构建子类,如果目标对象有final修饰则会报错:
java.lang.IllegalArgumentException;
- 目标对象的方法如果是final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。
代码实战:
/**
* 使用cglib框架实现动态代理
*
* @author: stone
* @create: 2020-09-19 14:52
*/
public class ProxyFactory implements MethodInterceptor {
//接收代理目标对象
Object target;
//构造器
public ProxyFactory(Object target){
this.target = target;
}
/**
* 生成代理对象的方法
* 此处使用cglib的方式生成代理对象
* 简单理解:给目标类构造子类,再通过子类创建父类得到目标对象
* @return
*/
public Object getProxyInstance(){
//1、加载cglib工具类 Enhancer
Enhancer enhancer = new Enhancer();
//2、设置目标类为父类
enhancer.setSuperclass(target.getClass());
//3、设置回调方法
enhancer.setCallback(this);
//4、生成目标对象的代理对象
return enhancer.create();
}
/**
* 拦截方法
* @param o
* @param method 执行的具体类
* @param objects 参数列表
* @param methodProxy
* @return 目标方法的返回值 可能会null
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib代理模式拦截开始~ ~");
Object returnVal = method.invoke(target, objects);
System.out.println("cglib代理模式拦截结束~ ~");
return returnVal;
}
}
console:
cglib代理模式拦截开始~ ~
hi 石似心
cglib代理模式拦截结束~ ~
hello 七里香
小结:
简单理解:给目标类构造子类,再通过子类创建父类得到目标对象
应用场景例举
- 防火墙代理
内网通过代理穿透防火墙,实现对公网的访问。
- 缓存代理
当请求图片文件等资源时,先到缓存代理中取,如果取不到资源再到公网或者数据库中取同时存入缓存代理。
- 远程代理
远程对象的本地代表,通过它可以把远程对象当作本地对象来调用。而远程代理通过网络和真正的远程对象进行沟通。
- 同步代理
主要使用在多线程编程中,为资源对象封装同步处理(锁)。
13、模版方法模式(行为型)
14、命令模式(行为型)
15、访问者模式(行为型)
16、迭代器模式(行为型)
17、观察者模式(行为型)
业务场景:
天气预报项目:
- 气象站将每天测量到的温度、湿度、气压等信息以公告的形式发布出去;
- 需要设计开放型API,便于其他第三方获取测量的气象数据;
- 提供温度、湿度、气压的接口;
- 测量数据更新时,要能实时通知给第三方。
普通方案实现:
/**
* 天气信息类 气象站
* 将测量到的数据填入系统
* @author: stone
* @create: 2020-09-19 15:48
*/
@Data
public class WeatherData {
private float temperatrue;//温度
private float humidity;//湿度
private float pressure;//气压
//第三方
CCTV cctv = new CCTV();
/**
* 更新数据
*/
public void updateData(float temperatrue,float humidity,float pressure){
this.temperatrue = temperatrue;
this.humidity = humidity;
this.pressure = pressure;
//气象有更新,将数据推送出去
//CCTV
cctv.showWeather(temperatrue,humidity,pressure);
}
}
代码实测
观察者模式原理
气象站为Subject
第三方为Observer
第三方去观察气象站
/**
* 使用观察者模式
* 观察者
* @author: stone
* @create: 2020-09-19 17:14
*/
public interface Observer {
// 接受气象信息
public void showWeather(float temperatrue,float humidity,float pressure);
}
/**
* 使用观察者模式
* 数据提供者
* @author: stone
* @create: 2020-09-19 17:13
*/
public interface Subject {
/**
* 更新数据
*/
public void updateData(float temperatrue,float humidity,float pressure);
}
/**
* @author: stone
* @create: 2020-09-19 15:56
*/
public class ClientMain {
public static void main(String args[]){
//第三方
Observer cctv = new CCTV();
Observer baidu = new Baidu();
Observer tencent = new Tencent();
WeatherData weatherData = new WeatherData();
//注册第三方 非常便于扩展
weatherData.registerObserver(cctv);
weatherData.registerObserver(baidu);
weatherData.registerObserver(tencent);
//测量到了新的气象信息
weatherData.updateData(29,70,1006);
}
}
小结:
类似于消息队列的模式,观察者来发布中心注册,然后我有消息时我推送到发布中心,由发布中心发送给全部注册的观察者。
18、中介者模式(行为型)
19、备忘录模式(行为型)
20、解释器模式(行为型)
Interpreter 模式
21、状态模式(行为型)
22、策略模式(行为型)
23、职责链模式(行为型)
责任链模式