设计模式
原则
什么是SOLID原则?
S单一职责SRP Single-Responsibility Principle
一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则的引申,将职责定义为引起变化的原因,以提高内聚性减少引起变化的原因。
比如: SpringMVC 中Entity,DAO,Service,Controller, Util等的分离。
- O开放封闭原则OCP Open - Closed Principle
对扩展开放,对修改关闭(设计模式的核心原则)
比如: 设计模式中模板方法模式和观察者模式都是开闭原则的极好体现
- L里氏替换原则LSP Liskov Substitution Principle
任何基类可以出现的地方,子类也可以出现;这一思想表现为对继承机制的约束规范,只有子类能够替换其基类时,才能够保证系统在运行期内识别子类,这是保证继承复用的基础。
比如:正方形是长方形是理解里氏代换原则的经典例子。(讲的是基类和子类的关系,只有这种关系存在时,里氏代换原则才存在)
- I接口隔离法则ISL Interface Segregation Principle
客户端不应该依赖那些它不需要的接口。(接口隔离原则是指使用多个专门的接口,而不使用单一的总接口; 这个法则与迪米特法则是相通的)
- D依赖倒置原则DIP Dependency-Inversion Principle
要依赖抽象,而不要依赖具体的实现, 具体而言就是高层模块不依赖于底层模块,二者共同依赖于抽象。抽象不依赖于具体, 具体依赖于抽象。
什么是合成/聚合复用原则?
Composite/Aggregate ReusePrinciple ,CARP: 要尽量使用对象组合,而不是继承关系达到软件复用的目的。
组合/聚合可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。
此原则和里氏代换原则氏相辅相成的,两者都是具体实现"开-闭"原则的规范。违反这一原则,就无法实现"开-闭"原则。
什么是迪米特法则?
Law of Demeter,LoD: 系统中的类,尽量不要与其他类互相作用,减少类之间的耦合度.
又叫最少知识原则(Least Knowledge Principle或简写为LKP).
- 不要和“陌生人”说话。英文定义为: Don’t talk to strangers.
- 只与你的直接朋友通信。英文定义为: Talk only to your immediate friends.
比如:外观模式Facade(结构型)
常用设计模式
针对三种设计模式类型,常见的设计模式是:
- 创建型:单例模式、工厂方法模式(及变式)、建造者模式;
- 结构型:适配器模式、代理模式、门面(外观)模式;
- 行为型:策略模式、观察者模式
各设计模式对比及编程思想总结
设计模式 | 一句话归纳 |
---|---|
工厂模式(Factory) | 只对结果负责,不要三无产品。 |
单例模式(Singleton) | 保证独一无二。 |
适配器模式(Adapter) | 需要一个转换头(兼容)。 |
装饰器模式(Decorator) | 需要包装,但不改变本质(同宗同源)。 |
代理模式(Proxy) | 办事要求人,所以找代理。 |
观察者模式(Observer) | 完成时通知我。 |
策略模式(Strategy) | 我行我素,达到目的就行。 |
模板模式(Template) | 流程标准化,原料自己加。 |
委派模式(Delegate) | 干活是你的(普通员工),功劳是我的(项目经理)。 |
原型模式(Prototype) | 拔一根猴毛,吹出千万个。 |
编程思想总结
Spring思想 | 应用场景(特点) | 一句话归纳 |
---|---|---|
AOP | AspectOrientedProgramming(面向切面编程)找出多个类中有一定规律的代码,开发时拆开,运行时再合并。面向切面编程,即面向规则编程。 | 解耦,专人做专事。 |
OOP | ObjectOrientedProgramming(面向对象编程)归纳总结生活中一切事物。 | 封装、继承、多态。 |
BOP | BeanOrientedProgramming(面向Bean编程)面向Bean(普通的java类)设计程序。 | 一切从Bean开始。 |
IOC | InversionofControl(控制反转)将new对象的动作交给Spring管理,并由Spring保存已创建的对象(IOC容器)。 | 转交控制权(即控制权反转)。 |
DI/DL | DependencyInjection(依赖注入)或者DependencyLookup(依赖查找)依赖注入、依赖查找,Spring不仅保存自己创建的对象,而且保存对象与对象之间的关系。注入即赋值,主要三种方式构造方法、set方法、直接赋值。 | 先理清关系再赋值。 |
单例模式 (Singleton)
讲讲单例模式
实现1个类只有1个实例化对象 & 提供一个全局访问点
作用 : 保证1个类只有1个对象,降低对象之间的耦合度
优点
- 提供了对唯一实例的受控访问;
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能;
- 可以根据实际情况需要,在单例模式的基础上扩展做出双例模式,多例模式;
缺点
- 单例类的职责过重,里面的代码可能会过于复杂,在一定程度上违背了“单一职责原则”。
- 如果实例化的对象长时间不被利用,会被系统认为是垃圾而被回收,这将导致对象状态的丢失。
单例模式的实现方式
单例模式的实现方式有多种,根据需求场景,可分为2大类、6种实现方式。具体如下:
饿汉式
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了。
依赖
JVM
类加载机制,保证单例只会被创建1次,即 线程安全
JVM
在类的初始化阶段(即 在Class
被加载后、被线程使用前),会执行类的初始化- 在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化
class Singleton {
// 1. 加载该类时,单例就会自动被创建
private static Singleton ourInstance = new Singleton();
// 2. 构造函数 设置为 私有权限
// 原因:禁止他人创建实例
private Singleton() {
}
// 3. 通过调用静态方法获得创建的单例
public static Singleton newInstance() {
return ourInstance;
}
}
懒汉式
只有当调用getInstance的时候,才回去初始化这个单例。
class Singleton {
// 1. 类加载时,先不自动创建单例
// 即,将单例的引用先赋值为 Null
private static Singleton ourInstance = null;
// 2. 构造函数 设置为 私有权限
// 原因:禁止他人创建实例
private Singleton() {
}
// 3. 需要时才手动调用 newInstance() 创建 单例
public static Singleton newInstance() {
// 先判断单例是否为空,以避免重复创建
if( ourInstance == null){
ourInstance = new Singleton(); // a 实例化时机
}
return ourInstance;
}
}
这种写法是线程不安全的
线程A 执行到 a 实例化时机时,因为对象初始化需要一定的时间
而此时 线程B 也执行到 a 实例化时机处,发现没有对象,也进行初始化对象
最终就会实例化了两个对象,不符合单例模式要求
懒汉式-同步锁
- 使用同步锁
synchronized
锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建
- 即,
getInstance()
方法块只能运行在1个线程中- 若该段代码已在1个线程中运行,另外1个线程试图运行该块代码,则 会被阻塞而一直等待
- 而在这个线程安全的方法里我们实现了单例的创建,保证了多线程模式下 单例对象的唯一性
class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
// 加入同步锁
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
缺点
每次访问都要进行线程同步(即 调用synchronized
锁),造成过多的同步开销(加锁 = 耗时、耗能)
实际上只需在第1次调用该方法时才需要同步,一旦单例创建成功后,就没必要进行同步
懒汉式-双重校验锁
懒加载 、线程安全、性能都兼顾到了
class Singleton {
private static Singleton ourInstance = null;
private Singleton() {
}
public static Singleton newInstance() {
// 加入双重校验锁
// 校验锁1:第1个if
if( ourInstance == null){ // ①
synchronized (Singleton.class){ // ②
// 校验锁2:第2个 if
if( ourInstance == null){
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
}
说明
校验锁1:第1个if
作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
即直接跳到执行 return ourInstance
校验锁2:第2个 if
作用:防止多次创建单例问题
原理:
- 线程A调用newInstance(),当运行到②位置时,此时线程B也调用了newInstance()
- 因线程A并没有执行instance = new Singleton();,此时instance仍为空,因此线程B能突破第1层 if 判断,运行到①位置等待synchronized中的A线程执行完毕
- 当线程A释放同步锁时,单例已创建,即instance已非空
- 此时线程B 从①开始执行到位置②。此时第2层 if 判断 = 为空(线程A释放了锁,单例已创建),因此也不会创建多余的实例
懒汉式-双检锁+volatile
上面的双检锁有可能出现指令重排引起的线程安全问题
在指令层面
new singleton()不是一个原子操作
分成了三步
1 给内存分配地址
2 初始化对象
3 对象指向内存地址
在真正执行时,虚拟机可能会为了效率对指令进行重排
123 变成了 132
当线程A 执行了第三步,此时instance 还未被初始化,如果线程B执行到了
if( ourInstance == null) 返回false, 执行 return
此时A线程内的instance还未完全初始化
B线程调用实例会出现线程不安全的情况
class Singleton {
private volatile static Singleton ourInstance;
private Singleton() {
}
public static Singleton newInstance() {
// 加入双重校验锁
// 校验锁1:第1个if
if( ourInstance == null){ // ①
synchronized (Singleton.class){ // ②
// 校验锁2:第2个 if
if( ourInstance == null){
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
}
静态内部类实现单例
- 根据 静态内部类 的特性,同时解决了按需加载、线程安全的问题,同时实现简洁
- 在静态内部类里创建单例,在装载该内部类时才会去创建单例
- 线程安全:类是由
JVM
加载,而JVM
只会加载1遍,保证只有1个单例
class Singleton {
// 1. 创建静态内部类
private static class Singleton2 {
// 在静态内部类里创建单例
private static Singleton ourInstance = new Singleton();
}
// 私有构造函数
private Singleton() {
}
// 延迟加载、按需创建
public static Singleton newInstance() {
return Singleton2.ourInstance;
}
}
调用过程说明:
- 外部调用类的newInstance()
- 自动调用Singleton2.ourInstance
2.1 此时单例类Singleton2得到初始化
2.2 而该类在装载 & 被初始化时,会初始化它的静态域,从而创建单例;
2.3 由于是静态域,因此只会JVM只会加载1遍,Java虚拟机保证了线程安全性 - 最终只创建1个单例
loadclass方法使用了synchronized方法来保证线程安全
但是上面的方法都会被反射破坏(获得了其私有构造器),对于枚举类型是不能被反射破坏的,因此反射就不能破坏枚举类型的单例
枚举实现的单例
public enum Singleton{
//定义1个枚举的元素,即为单例类的1个实例
INSTANCE;
// 隐藏了1个空的、私有的 构造方法
// private Singleton () {}
}
// 获取单例的方式:
Singleton singleton = Singleton.INSTANCE;
这是 最简洁、易用 的单例实现方式,借用《Effective Java》
的话:
单元素的枚举类型已经成为实现
Singleton
的最佳方法
但枚举会在程序启动时,就把这个实例构造好等待使用。
Spring 的bean 是单例的吗?why?
是单例的,为了提高性能!!!
从几个方面:
- 少创建实例
- 新生成实例消耗包括两方面,第一,Spring 会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
- 垃圾回收
- 由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
- 缓存快速获取
- 因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快。
单例有啥劣势?
如果是有状态的话在并发环境下线程不安全。
Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的。
关联问题:spring 作用域
singleton、prototype、request、session、global session。
关联问题:单例bean与原型bean的区别
如果一个bean被声明为单例的时候,在处理多次请求的时候在spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。
但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。
关联问题: @Controller @Service是不是线程安全的?
默认配置下不是的。为啥呢?因为默认情况下@Controller没有加上@Scope,没有加@Scope就是默认值singleton,单例的。意思就是系统只会初始化一次Controller容器,所以每次请求的都是同一个Controller容器,当然是非线程安全的。
加了@Scope注解多的实例prototype是不是一定就是线程安全的呢?
即便是加上@Scope
注解也不一定能保证Controller 100%的线程安全
- 在
@Controller/@Service
等容器中,默认情况下,scope值是单例-singleton的,也是线程不安全的。 - 尽量不要在
@Controller/@Service
等容器中定义静态变量,不论是单例(singleton)还是多实例(prototype)他都是线程不安全的。 - 默认注入的Bean对象,在不设置
scope
的时候他也是线程不安全的。 - 一定要定义变量的话,用
ThreadLocal
来封装,这个是线程安全的。
https://cloud.tencent.com/developer/article/1743283
简单工厂模式(SimpleFactoryPattern)
- 简单工厂模式又叫静态方法模式(因为工厂类定义了一个静态方法)
- 现实生活中,工厂是负责生产产品的;同样在设计模式中,简单工厂模式我们可以理解为负责生产对象的一个类,称为“工厂类”。
将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦。
即使用者可直接消费产品而不需要知道其生产的细节
优点
- 将创建实例的工作与使用实例的工作分开,使用者不必关心类对象如何创建,实现了解耦;
- 把初始化实例时的工作放到工厂里进行,使代码更容易维护。 更符合面向对象的原则 & 面向接口编程,而不是面向实现编程。
缺点
-
工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
-
违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
-
简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
工厂方法模式(Factory Method)
工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。
优点
- 更符合开-闭原则
新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可
简单工厂模式需要修改工厂类的判断逻辑
- 符合单一职责原则
每个具体工厂类只负责创建对应的产品
简单工厂中的工厂类存在复杂的switch逻辑判断
- 不使用静态工厂方法,可以形成基于继承的等级结构。
简单工厂模式的工厂类使用静态工厂方法
工厂模式可以说是简单工厂模式的进一步抽象和拓展,在保留了简单工厂的封装优点的同时,让扩展变得简单,让继承变得可行,增加了多态性的体现。
缺点
-
添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销;
-
由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
-
虽然保证了工厂方法内的对修改关闭,但对于使用工厂方法的类,如果要更换另外一种产品,仍然需要修改实例化的具体工厂类;
-
一个具体工厂只能创建一种具体产品
抽象工厂模式
抽象工厂模式,即Abstract Factory Pattern,提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。
比如说你需要一辆汽车,你可以直接从工厂里面提货就可以了,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。这就是工厂模式。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
**缺点:**每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
注意事项:作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
关联问题:Spring 中的工厂模式
关联问题: BeanFactory 和 FactoryBean 的区别是什么?
BeanFactory
是一个大工厂,是IOC容器的根基,有繁琐的bean
生命周期处理过程,可以生成出各种各样的Bean
FactoryBean
是一个小工厂,它自己也是一个Bean
,但是可以生成其他Bean
静态代理模式
给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
- 代理对象:起到中介作用,连接客户端和目标对象
- 例子:电脑桌面的快捷方式。电脑对某个程序提供一个快捷方式(代理对象),快捷方式连接客户端和程序,客户端通过操作快捷方式就可以操作那个程序
主要作用
通过引入代理对象的方式来间接访问目标对象
解决的问题
防止直接访问目标对象给系统带来的不必要复杂性。
优点
- 协调调用者和被调用者,降低了系统的耦合度
- 代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用
缺点
- 由于在客户端和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢;
- 实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。
动态代理模式(Proxy Pattern)
结构型模式
代理模式中的静态代理模式存在一些特点:
- 1个静态代理 只服务1种类型的目标对象
- 若要服务多类型的目标对象,则需要为每种目标对象都实现一个静态代理对象
在目标对象较多的情况下,若采用静态代理,则会出现 静态代理对象量多、代码量大,从而导致代码复杂的问题
实现原理
- 设计动态代理类(
DynamicProxy
)时,不需要显式实现与目标对象类(RealSubject
)相同的接口,而是将这种实现推迟到程序运行时由JVM
来实现
- 即:在使用时再创建动态代理类 & 实例
- 静态代理则是在代理类实现时就指定与目标对象类(
RealSubject
)相同的接口
- 通过
Java
反射机制的method.invoke()
,通过调用动态代理类对象方法,从而自动调用目标对象的方法
优点
- 只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复、多余代码
- 更强的灵活性
- 设计动态代理类(
DynamicProxy
)时,不需要显式实现与目标对象类(RealSubject
)相同的接口,而是将这种实现推迟到程序运行时由JVM
来实现- 在使用时(调用目标对象方法时)才会动态创建动态代理类 & 实例,不需要事先实例化
缺点
- 效率低
相比静态代理中 直接调用目标对象方法,动态代理则需要先通过Java
反射机制 从而 间接调用目标对象方法 - 应用场景局限
因为 Java 的单继承特性(每个代理类都继承了 Proxy 类),即只能针对接口 创建 代理类,不能针对类 创建代理类
即只能动态代理 实现了接口的类
应用场景
- 基于静态代理应用场景下,需要代理对象数量较多的情况下使用动态代理
AOP
领域
关联问题:spring中哪使用了代理模式
主要是在aop中使用了代理模式
创建代理时,会判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 判断目标类是否是接口或者目标类是否Proxy类型,若是则使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 使用CGLIB的方式创建代理对象
return new ObjenesisCglibAopProxy(config);
}
else {
// 上面条件都不满足就使用JDK的提供的代理方式生成代理对象
return new JdkDynamicAopProxy(config);
}
}
}
关联问题:Jdk动态代理和CGLIB 动态代理
相对于静态代理,JDK 动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但它同样有缺陷,就是动态代理的实现类需要类实现接口来完成代理的业务,也就是说它始终无法摆脱仅支持interface代理的桎梏,这是设计上的缺陷
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
关联问题:Spring中的代理选择原则
当Bean有实现接口时,Spring就会用JDK动态代理
当Bean没有实现接口时,Spring会选择CGLib代理
Spring可以通过配置强制使用CGLib代理,只需要在配置中加入<aop:aspectj-autoproxy roxy-target-clas=“true”>
如果目标对象实现了接口,且强制使用cglib代理,则会使用cglib代理
观察者模式(Observer)
行为型模式
观察者(Observer)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- 定义对象间的一种一对多的依赖关系;
- 当1个对象的状态发生改变时,所有依赖于它的对象都将得到通知 & 自动更新对应操作。
又称:发布 / 订阅模式
解决的问题:
常变对象 与不常变对象之间存在依赖关系的前提下,不常变对象 需随 常变对象经常改变逻辑的问题。即解耦 常变对象 与不常变对象之间的依赖关系
总结:
被观察者 (Observable)
通过 订阅(Subscribe)
按顺序发送事件 给观察者 (Observer)
, 观察者(Observer)
按顺序接收事件 & 作出对应的响应动作
关联问题:spring中观察者模式的使用
spring中Observer模式常用的地方是listener的实现。如ApplicationListener
模板方法模式(Template Method)
行为型模式
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
定义:
定义一个模板结构,将具体内容延迟到子类去实现。
这个模式的重点在于提供了一个固定算法框架,并让子类实现某些步骤
主要作用:
在不改变模板结构的前提下在子类中重新定义模板中的内容。
优点
- 提高代码复用性
将相同部分的代码放在抽象的父类中 - 提高了拓展性
将不同的代码放入不同的子类中,通过对子类的扩展增加新的行为 - 实现了反向控制
通过一个父类调用其子类的操作,通过对子类的扩展增加新的行为,实现了反向控制 & 符合“开闭原则”
缺点
引入了抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂度。
关联问题:spring中哪使用了模版方法模式?
JDBCTemplate、HibernateTemplate
JDBCTemplate JDBCTemplate是Spring对JDBC的封装,开发人员自己写SQL,需要注入dataSource。
HibernateTemplate 使用HibernateTemplate不用关心底层的数据库是哪个数据库,直接操作对象,需要注入sessionFactory
关联问题:jdk中哪使用了模版设计方法模式?
juc包中,自己实现一个锁,就是模版设计模式
例子:
9.22 | 手写一个排他锁,当多线程运行时只有线程名字为指定名字的线程能获取到锁
- 实现 lock 接口
- 写一个静态类,继承 AbstractQueuedSynchronizer
- 重写 aqs 里面的方法,完成需求
- aqs 里只有五个方法可以被重写
- tryAcquire tryRelease 排他锁 加锁、解锁
- tryAcquireShared tryReleaseShared 共享锁 加锁 解锁
- isHeldExclusively 当前线程是否被线程独占的方法
写代码经常会用到 comparable 比较器来对数组对象进行排序,我们都会实现它的 compareTo() 方法,之后就可以通过 Collections.sort() 或者 Arrays.sort() 方法进行排序了