23种设计模式-
设计模式
前言
【狂神说Java】通俗易懂的23种设计模式教学(停更)_哔哩哔哩_bilibili
什么是设计模式
-
设计模式(design pattern) 是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。他不是语法规定,而是一套用来提高代码复用性,可维护性,可读性,稳健性以及安全性的解决方案。
-
1995年,GoF(Gong of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了23种设计模式,从此树立了软件设计模式领域的里程碑,人称【GoF设计模式】
设计模式的意义
-
设计模式本质是面向对象设计原则的实际运用,是对类的封装性,继承性和多态以及类的关联关系和组合关系的充分理解。
-
正确使用设计模式有以下优点:
- 可以提高程序员的思维能力,编程能力和设计能力。
- 使程序设计更加标准化,代码编制更加工程化,软件开发效率大大提高,从而缩短软件开的的周期。
- 使设计的代码可重用性高,可读性强,灵活性好,可维护性强。
GOF23
- GoF23
- 一种思维,一种态度,一种进步
- 创建型模式:
- 单例模式,工厂模式,抽象工厂模式,建造者模式,原型模式。
- 结构型模式:
- 适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式。
- 行为型模式:
- 模板方法模式,命令模式,迭代器模式,观察者模式,中介者模式,备忘录模式,解释器模式,状态模式,策略模式,职责链模式,访问者模式。
OOP七大原则
- 开闭原则:对扩展开放,对修改关闭。
- 里氏替换原则:继承必须确保超类所拥有的性质再子类中任然成立
- 依赖倒置原则:面向接口编程,不要面向实现编程。
- 单一职责原则:控制类的粒度大小,将对象解耦,提高其内聚性。
- 接口隔离原则:要为各个类建立他们需要的专用接口
- 迪米特法则:只与你的直接朋友交谈,不和陌生人说话。
- 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
设计模式23
1.单例模式
-
核心作用
- 保证一个类只有一个实例,并且提供该实例的全局访问点
-
常见场景
- Windows的任务管理器
- Windows的回收站
- 项目中,读取配置文件的类,一般也只有一个对象,没必要每次都去New对象读取
- 网站的计数器一般也会采用单例模式,可以保证同步
- 数据库连接池的设计一般也是单例模式
- 再servlet模式中,每个servlet也是单例模的
- 再spring中,每个bean默认就是单例的
- 。。。。。。
1.饿汉式
一来就创建这个对象
缺点:会造成资源的浪费。
package design23.single;
/**
* TODO
*
* @author
* @date 2022/2/24
*/
//构造器私有
//饿汉式
public class Hungry {
//可能会浪费空间。
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGARY = new Hungry();
public static Hungry getInstance(){
return HUNGARY;
}
}
2.懒汉式
先判断有没有对象,没有就创建对象。
几个版本更迭
-
基础版本:只有if(lazyman == null)
-
双重检测锁模式:
-
双重检测:(lazyman == null)*2
-
锁:synchronized(LazyMan.class){}
-
漏洞:由于new 不是原子操作,分三步,会在多线程中表现出多个,破坏单例。应对->给对象设置 volatile
private volatile static LazyMan lazyman;
-
再漏洞,怕反射(一个正常+一个反射)
-
-
构造方法内部加锁加对象判断版本
- 构造方法加锁,加对象判断。
- 漏洞:反编译获取构造方法,用构造方法创建多个对象。
-
红绿灯模式
- 加一个标识符,构造方法用标识符代替对象判断。
- 漏洞:反编译获取你得红绿灯判断标识,用反射创建一个对象后,反射复位红绿灯再创建另一个。为了解决这个漏洞,介绍了枚举类型的反射。
package design23.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* TODO
*
* @author
* @date 2022/2/24
*/
//懒汉式单例 dcl
public class LazyMan {
//红绿灯
private static boolean whh = false;
private LazyMan(){
synchronized (LazyMan.class){
if (whh == false){
whh = true;
}else {
throw new RuntimeException("不要试图使用反射来破坏异常");
}
/*if(lazyman!=null){
throw new RuntimeException("不要试图使用反射来破坏异常");
}*/
}
// System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static LazyMan lazyman;
//双重检测锁模式 DCL
public static LazyMan getInstance(){
//加锁对类
if(lazyman==null){
synchronized (LazyMan.class){
if(lazyman==null){
lazyman = new LazyMan();
//不是原子性操作,可能会指令重排。
/*
* 1.分配内存空间。
* 2.执行构造方法。
* 3.把对象指向空间。(执行完这条就会!=null)
*
* 123
* 132A
* B
* */
}
}
}
return lazyman;
}
/* //多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}*/
//反射下破坏单例模式
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// LazyMan instance = LazyMan.getInstance();
Field whh = LazyMan.class.getDeclaredField("whh");
whh.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
whh.set(instance1,false);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
3.静态内部类
package design23.single;
/**
* TODO
*
* @author
* @date 2022/2/24
*/
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return Inner.HOLDER;
}
public static class Inner{
private static final Holder HOLDER = new Holder();
}
}
4.枚举防反射
枚举类型的反射获取构造方法是有参的(String,int)。
package design23.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* TODO
*
* @author
* @date 2022/2/24
*/
//enum 枚举本省就是一个类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
// EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
2.工厂模式
- 作用
- 实现了创建者和调用者的分离
- 详细分类
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- OOP七大原则
- 开闭原则:一个软件实体应当对扩展开放,对修改关闭。
- 依赖倒转原则:要针对接口编程,不要针对实现编程。
- 迪米特法则:只与你的直接朋友通信,避免和陌生人通信。
- 核心本质:
- 实例化对象不使用new,用工厂方法代替
- 将选择实现类,创建对象同意管理和控制。从而将调用者跟我们的实现类解耦。
- 三种模式:
-
简单工厂模式
-
用来生产同一等级结构中的任意产品(对于增加新的产品,需要覆盖原有代码)
-
消费者不用去自己造车(new)直接去工厂买车。
package design23.factory.simple; /** * TODO * * @author * @date 2022/2/25 */ public class Consumer { public static void main(String[] args) { // 1.知道接口和实现类,相当于自己造车。 /*Car car1 = new WuLing(); Car car2 = new Tesla();*/ // 2.简单工厂模式,向工厂要车 Car car1 = new CarFactory().getCar("五菱"); Car car2 = new CarFactory().getCar("特斯拉"); // 3.添加一种类型得,要改工厂方法。加个大众,破坏开闭原则。 car1.name(); car2.name(); } }
-
-
工厂方法模式
-
用来生产同一等级结构中的固定产品(支持增加任意产品)
-
结构图:
-
每种车有自己的工厂,消费者拿车直接去对应的工厂拿就好了。
package design23.factory.method; /** * TODO * * @author * @date 2022/2/25 */ public class Consumer { public static void main(String[] args) { Car car = new WuLingFactory().getCar(); Car car1 = new TeslaFactory().getCar(); Car car2 = new ParscheFactory().getCar(); car.name(); car1.name(); car2.name(); } // 结构复杂度:simple // 代码复杂度:simple // 编程复杂度:simple // 管理复杂度:simple // 根据设计原则:工厂方法模式! // 根据实际业务:简单工厂模式! }
-
3.抽象工厂模式
-
定义:抽象工厂模式提供一个创建一系列相关或者相互依赖对象的接口,无需指定他们具体的类。
-
适用场景:确定了有哪些产品(手机,路由。。。),方便扩展工厂。
- 客户端(应用层)不依赖于产品实例如何被创建,实现等细节(不用自己造车,造路由器,交给工厂做就好)
- 强调一系列相关的产品对象(属于同一产品族)一起使用创建对象需要大量的重复代码。
- 提供一个产品类的库,所有产品以同样的接口出现,从而使得客户端不依赖于具体的实现。
-
结构图uml
-
优点:
- 具体产品在应用层的代码隔离,无需关心创建的细节。
- 将一些列的产品统一创建。
-
缺点:
-
规定了所有可能创建的产品集合,产品族中扩展的产品困难;
-
增加了系统的抽象性和理解难度。
-
-
小结:
- 简单工厂模式(静态工厂模式)
- 实际使用最多,某种程度上不符合设计原则!
- 工厂方法模式
- 不修改已有类的前提下,通过增加新的工厂类实现扩展。
- 抽象工厂模式
- 不可以增加产品,可以增加产品族(加个厂)
-
应用场景:
- JDK中Calendar的getInstance方法
- JDBC中的Connection对象的读取
- Spring中IOC容器创建管理Bean对象。
- 反射中Class对象的newInstance方法。
4.建造者模式
-
建造者模式属于创建型模式,它提供了一种创建对象的最佳方式。
-
定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
-
只要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
-
用户只需要给出指定对象的类型和内容,建造者模式负责按照顺序创建复杂对象(把内部的建造过程和细节隐藏起来)
-
例子:
- 工厂(建造者模式):负责制造汽车(组装过程和细节在工厂内)
- 汽车购买者(用户):你只需要说出你需要的型号(对象的类型和内容),然后直接购买就可以使用了(不需要知道汽车是怎么组装的(车轮,车门,发动机,方向盘等等。))
-
示例:
-
上面示例是Builder模式得常规用法,导演类Director在Builder模式中具有很重要得作用,它用于指导具体构建者如何构建产品,控制调用先后顺序,并向调用者返回完整得产品类,但是这些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。
-
通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义,内部有复杂对象得默认实现,使用时可以根据用户需求自由定义更改内容,并且无无需改变具体得构造方式。就可以生产出不同复杂产品。
-
比如:麦当劳得套餐,服务员(具体建造者)可以随意搭配几件产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给了用户来操作,使得产品得更加简洁灵活。
-
结构uml
-
优点:
- 产品得建造和表现分离,实现了解耦。使用建造者模式可以使得客户端不必知道产品内部组成得细节。
- 将复杂产品得创建步骤分解在不同的方法中,使得创建过程更加清晰。
- 具体得建造者类之间是相互独立得,这样有利于系统得扩展。增加新的具体创建者无需修改原有得类库得代码,符和开闭原则。
-
缺点:
- 建造者模式所创建得产品一般具有较多得共同点,其组成部分相似;如果产品之间得差异性很大,则不适合使用建造者模式,因此其使用范围受到一定得限制。
- 如果产品得内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
-
应用场景:
- 需要生成得产品对象具有复杂得内部结构,这些产品对象具有共性;
- 隔离复杂对象得创建和使用,并使得相同得创建过程可以创建不同得产品。
- 适合于一个具有较多零件(属性)得产品(对象)得创建过程。
-
建造者与抽象工厂模式得比较:
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关品
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需的产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂的对象,返回一个完整的对象。
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?