单例模式
什么是单例?
- 保证一个类在任何情况下只有一个实例,该类构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀个静态公有⽅法获取实例。
那些地方用到了单例?
- 网站的计数器,一般是使用单例模式实现的,否则难以同步。
- 应用程序的日志应用,一般都是单例模式实现。
- 多线程的线程池设计一般也是采用单例模式,因为线程池要对池中的线程进行控制。
单例优缺点
优点:
- 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例
- 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。
- 避免对共享资源的多重占用。
缺点:
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
代码实现:
单例模式有好几种写法:
饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高,但是类加载时就回初始化,浪费内存空间。
懒汉式:类初始化时不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。
- 简单懒汉式(在方法声明时加锁)
- DCL双重检验加锁(进阶懒汉式)
- 静态内部类(优雅懒汉式)
饿汉式
1 | //饿汉式 |
- 简单懒汉式
1 | //简单懒汉式 |
- DCL懒汉式
1 | //DCL懒汉式 |
- 静态内部类懒汉式
1 | //静态内部类懒汉式 |
工厂模式
简单工厂模式
简单工厂模式是由一个工厂对象来创建实例,客户端不需要不关注创建逻辑,只需要提供传入工厂的参数
适⽤于⼯⼚类负责创建对象较少的情况,缺点是如果要增加新产品,就需
要修改⼯⼚类的判断逻辑,违背开闭原则,且产品多的话会使⼯⼚类⽐较
复杂。
工厂方法模式
和简单工厂模式中工厂负责生产所有的产品相比,工厂方法模式将生成具体产品的任务分发给具体的产品工厂。也就是定义⼀个抽象⼯⼚,其定义了产品的⽣产接⼝,但不负责具体的产品,将⽣产任务交给不同的派⽣类⼯⼚。这样不⽤通过指定类型来创建对象了。
抽象工厂模式
简单⼯⼚模式和⼯⼚⽅法模式不管⼯⼚怎么拆分抽象,都只是针对⼀类产
品,如果要⽣成另⼀种产品,就⽐较难办了!
抽象⼯⼚模式通过在 AbstarctFactory 中增加创建产品的接⼝,并在具体⼦
⼯⼚中实现新加产品的创建。
适配器模式
在我们的应⽤程序中我们可能需要将两个不同接⼝的类来进⾏通信,在不
修改这两个类的前提下我们可能会需要某个中间件来完成这个衔接的过程。
这个中间件就是适配器。
所谓适配器模式就是将⼀个类的接⼝,转换成客户期望的另⼀个接⼝。它可以让原本两个不兼容的接⼝能够⽆缝完成对接。
作为中间件的适配器将⽬标类和适配者解耦,增加了类的透明性和可复⽤
性
优点:
- 提⾼了类的复⽤;
- 组合若⼲关联对象形成对外提供统⼀服务的接⼝;
- 扩展性、灵活性好。
缺点:
- 过多使⽤适配模式容易造成代码功能和逻辑意义的混淆。
- 部分语⾔对继承的限制,可能⾄多只能适配⼀个适配者类,⽽且⽬标类
必须是抽象类。
代理模式
代理模式的本质是⼀个中间件,主要⽬的是解耦合服务提供者和使⽤者。
使⽤者通过代理间接的访问服务提供者,便于后者的封装和控制。
是⼀种结构性模式。
代理模型有静态代理和动态代理。
静态代理:由程序员手动创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 特点是需要⾃⼰写代理类,实现对应的接⼝,⽐较麻烦。
动态代理常⻅的⼜有两种实现⽅式:JDK动态代理和CGLIB代理
- JDK动态代理其实就是运⽤了反射的机制,⽽CGLIB代理则⽤的是利⽤ASM框架,通过修改其字节码⽣成⼦类来处理。3
- 要实现静态代理需要将代理类硬编码在程序中,有一些代理一个代理就要负责一个类,这样代理模式可能会非常多,这就造成了很多不必要的资源浪费并且增加了很多的代码量。
- 动态代理可以帮助我们仅仅在需要的时候再创建代理类,减少资源浪费,此外由于动态代理是一个模板的形式,也可以减少程序的代码量。
静态代理和动态代理的区别
- 灵活性 :动态代理更加灵活,不需要必须实现接⼝,可以直接代理实
现类,并且可以不需要针对每个⽬标类都创建⼀个代理类。 - 另外,静态代理中,接⼝⼀旦新增加⽅法,⽬标对象和代理对象都要进⾏修改,这是⾮常麻烦的!
- JVM 层⾯ :静态代理在编译时就将接⼝、实现类、代理类这些都变成
了⼀个个实际的 class ⽂件。⽽动态代理是在运⾏时动态⽣成类字节
码,并加载到 JVM 中的。
观察者模式
概念
观察者模式主要⽤于处理对象间的⼀对多的关系,是⼀种对象⾏为模式。
该模式的实际应⽤场景⽐较容易确认,当⼀个对象状态发⽣变化时,所有该对象的关注者均能收到状态变化通知,以进⾏相应的处理。(类似于广播)
观察者模式的优缺点
优点:
- 被观察者和观察者之间是抽象耦合的;
- 耦合度较低,两者之间的关联仅仅在于消息的通知;
- 被观察者⽆需关⼼他的观察者;
- ⽀持⼴播通信;
缺点:
- 观察者只知道被观察对象发⽣了变化,但不知变化的过程和缘由;
- 观察者同时也可能是被观察者,消息传递的链路可能会过⻓,完成所有通知花费时间较多;
- 如果观察者和被观察者之间产⽣循环依赖,或者消息传递链路形成闭环,会导致⽆限循环;
装饰器模式
概念
装饰器模式主要对现有的类对象进⾏包裹和封装,以期望在不改变类对象及其类定义的情况下,为对象添加额外功能。是⼀种对象结构型模式。
需要注意的是,该过程是通过调⽤被包裹之后的对象完成功能添加的,⽽不是直接修改现有对象的⾏为,相当于增加了中间层。
应⽤场景
- 如果你希望在⽆需修改代码的情况下即可使⽤对象, 且希望在运⾏时为对象新增额外的⾏为, 可以使⽤装饰模式。
- 装饰能将业务逻辑组织为层次结构, 你可为各层创建⼀个装饰, 在运⾏时
将各种不同逻辑组合成对象。 由于这些对象都遵循通⽤接⼝, 客户端代码
能以相同的⽅式使⽤这些对象。 - 如果⽤继承来扩展对象⾏为的⽅案难以实现或者根本不可⾏, 你可以使⽤
该模式。 - 许多编程语⾔使⽤ final 最终关键字来限制对某个类的进⼀步扩展。 复⽤最终类已有⾏为的唯⼀⽅法是使⽤装饰模式:⽤封装器对其进⾏封装。
和代理模式的区别:
可以看到,代理模式和装饰器博士都是给类或者对象进行功能的增强。
但是代理模式的访问控制主要在于对目标类的透明访问,也就是说我们不需要知道目标类的具体情况,只需要知道代理类可以帮助我们完成想要的功能就对了;
而对于装饰者模式,我们是有一个目标类的对象的,这个对象可以完成特定的功能,但是不能满足我们的要求,所以需要对其进行增强。
也就是说,在代理模式中,目标类对于客户端是透明的,由代理类隐藏其具体信息并响应客户端请求;而装饰者中客户端对特定的目标类对象进行增强。
所以,代理类与真实对象的关系常在编译时就确定,而装饰者在运行时递归构造。
责任链模式
概念:
- ⼀个请求沿着⼀条“链”传递,直到该“链”上的某个处理者处理它为⽌
- ⼀个请求可以被多个处理者处理或处理者未明确指定时。
- 它最原始的裸体结构: switch-case 语句。
应⽤场景
- 当程序需要使⽤不同⽅式处理不同种类请求, ⽽且请求类型和顺序预
先未知时, 可以使⽤责任链模式。该模式能将多个处理者连接成⼀条
链。接收到请求后, 它会 “询问” 每个处理者是否能够对其进⾏处理。
这样所有处理者都有机会来处理请求。 - 当必须按顺序执⾏多个处理者时, 可以使⽤该模式。⽆论你以何种顺序将处理者连接成⼀条链, 所有请求都会严格按照顺序通过链上的处理者。
策略模式
策略模式(Strategy Pattern)属于对象的⾏为模式。其⽤意是针对⼀组算法,将每⼀个算法封装到具有共同接⼝的独⽴的类中,从⽽使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发⽣变化。
其主要⽬的是通过定义相似的算法,替换 if else 语句写法,并且可以随时相互替换。
Spring 框架中⽤到了哪些设计模式?
- ⼯⼚设计模式 : Spring 使⽤⼯⼚模式通过
- BeanFactory 、 ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板⽅法模式 : Spring 中 jdbcTemplate 、 hibernateTemplaTemplate 结尾的对数据库操作的类,它们就使⽤到了模板包装器设计模式 : 我们的项⽬需要连接多个数据库,⽽且每次访问中根据需要会去访问不同的数据库。这种模式让客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使⽤到了spring MVC 中也是⽤到了适配器模式适配 Controller 。
参考资料: