笔记整理自尚硅谷的韩顺平的设计模式,本文重点介绍常用的几种设计模式
单例模式所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类 只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
//饿汉式(静态变量)
class Singleton {
//1. 构造器私有化, 外部能new
private Singleton() {
}
//2.本类内部创建对象实例
private final static Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始 至终从未使用过这个实例,则会造成内存的浪费
//饿汉式(静态变量)
class Singleton {
//1. 构造器私有化, 外部能new
private Singleton() {
}
//2.本类内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
会在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优 缺点和上面是一样的
3、懒汉式( (线程不安全 )补充:jvm虚拟机在加载类的链接阶段的准备步骤中对static final修饰的变量直接赋值,在初始化阶段对静态代码块进行初始化。
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 起到了Lazy Loading的效果
- 线程不安全,只能在单线程中使用
// 懒汉式(线程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 解决了线程不安全的问题
- 同步方法效率较低
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
//同时保证了效率, 推荐使用
public static synchronized Singleton getInstance() {
if(instance == null) {
//实例化代码只用执行一次,后面再次访问时,直接return实例化对象,
//也避免的反复进行方法同步
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 线程安全;延迟加载;效率较高 ,推荐使用
// 静态内部类完成, 推荐使用
class Singleton {
private static volatile Singleton instance;
//构造器私有化
private Singleton() {}
//写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
public static synchronized Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
- 静态内部类在Singleton类被装载式不会立即初始化,而是在需要实例化式,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化
- 类的静态属性只会在第一次加载类的时候初始化 ,保证了线程安全
//使用枚举,可以实现单例, 推荐
enum Singleton {
INSTANCE; //属性
public void sayOK() {
System.out.println("ok~");
}
}
- 不仅能避免多线程同步问题,而 且还能防止反序列化重新创建新的对象
在框架中的应用在Spring中,Bean 可以被定义为两种模式:Prototype(多例)和Singleton(单例),Spring Bean默认是单例模式。
public class DefaultSingletonBeanRegistry {
//使用了线程安全容器ConcurrentHashMap,保存各种单实例对象
private final Map singletonObjects = new ConcurrentHashMap;
protected Object getSingleton(String beanName) {
//先到HashMap中拿Object
Object singletonObject = singletonObjects.get(beanName);
//如果没拿到通过反射创建一个对象实例,并添加到HashMap中
if (singletonObject == null) {
singletonObjects.put(beanName,
Class.forName(beanName).newInstance());
}
//返回对象实例
return singletonObjects.get(beanName);
}
}
先到 HashMap去拿单实例对象,没拿到就创建一个添加到 HashMap。
将多段代码的共性行为抽象到接口中去定义,具体的实现由子类实现父类后去定义。最后,通过一个工厂类去根据传参来选择返回对应的实例化对象。‘
- 定义了一个创建对象的类,由这个类来封装实例化对象的行为
- 简单工厂模式是由一 个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族 中最简单实用的模式
存在的问题:
如果想要拓展程序,必须对工厂类进行修改,违背了开闭原则
- 定义了一个创建对象的抽象方法,由子类决定要实例化的类
- 工厂方 法模式将对象的实例化推迟到子类
优点:
可以定义一个创建对象的抽象方法并创建多个不同的工厂类实现该抽象方法,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码
缺点:
客户端需要创建类的具体的实例,当类的实例发生变化后,用户也需要跟着变化。
- 定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。
- 将工厂抽象成两层,AbsFactory(抽象工厂) 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇, 更利于代码的维护和扩展
1、简单工厂
Spring 中的 BeanFactory 就是简单工厂模式的体现,BeanFactory 是 Spring IOC 容器中的一个核心接口。我们可以通过它的具体实现类(比如ClassPathXmlApplicationContext)来获取Bean:
BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
FlyFish flyFishBean = (FlyFish) bf.getBean("flyfishBean");
使用者不需要自己来new对象,而是通过工厂类的方法getBean来获取对象实例,这是典型的简单工厂模式,只不过Spring是用反射机制来创建Bean的。
2、工厂方法
Spring中的FactoryBean可以理解为工厂Bean。
我们定义一个类FlyFishFactoryBean来实现FactoryBean接口,主要是在getObject方法里new一个FlyFish对象。这样我们通过getBean(id) 获得的是该工厂所产生的FlyFish的实例,而不是FlyFishFactoryBean本身的实例,像下面这样:
BeanFactory bf = new ClassPathXmlApplicationContext("spring.xml");
FlyFish flyFishBean = (FlyFish) bf.getBean("flyfishBean");
模板模式
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),z 在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法 实现,但调用将以抽象类中定义的方式进行。
简单说,模板方法模式 定义一个操作中的算法的骨架,而将一些步骤延迟到子 类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
优点:
(1) 既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不 变,同时由子类提供部分步骤的实现。
(2) 实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用
(3) 算法只存在于一个地方,也就是在父类中,容易修改。需要修改算 法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
缺点:每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。
Spring Bean的创建过程就涉及到了模板模式,它也体现了Spring的扩展性。利用模板模式,Spring能让用户定制Bean的创建过程。
实际上,这里的模板模式的实现,并不是标准的抽象类的实现方式,而是有点类似 Callback回调的实现方式,也就是将要执行的函数封装成对象(比如,初始化方法封装成 InitializingBean 对象),传递给模板(BeanFactory)来执行。
适配器模式适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
通过多重继承目标接口和被适配者类方式来实现适配
将USB接口转为VGA接口
AdatperUSB2VGA 首先继承USBImpl获取USB的功能,其次,实现VGA接口,表示该类的类型为VGA。这样在调用属于VGA的projection方法时其实是在使用USB的showPPT的方法。
public class AdapterUSB2VGA extends USBImpl implements VGA {
@Override
public void projection() {
super.showPPT();
}
}
对象适配器
基本思路和类的适配器模式相同 ,只是将Adapte类作修改,不是继承USB类,而是持有USB类的实例,以解决兼容性的问题。
public class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
}
根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系。对象适配器使用组合替代继承, 解决了类适配器必须继承USB的局限性问题,也不再要求VGA必须是接口。
接口适配器模式当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
public abstract class AdapterUSB2VGA implements VGA {
USB u = new USBImpl();
@Override
public void projection() {
u.showPPT();
}
@Override
public void b() {
};
@Override
public void c() {
};
}
AdapterUSB2VGA实现,不用去实现b()和c()方法。
public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
public void projection() {
super.projection();
}
}
框架中的应用
在Spring MVC中,Controller有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。如果后面要扩展Controller,就得修改原来的代码,这样违背了开闭原则
Spring创建了一个适配器接口(HandlerAdapter)使得每一种Controller有一种对应的适配器实现类,让适配器代替执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了
代理模式提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能
例如用户通过中介完成租房的一系列操作(看房、交押金、租房、清扫卫生),代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。
分类:
静态代理: 在编译时就已经实现,编译完成后代理类是一个实际的class文件
动态代理: 在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
Spring 的 AOP 采用的是动态代理的方式
Spring 通过动态代理对类进行方法级别的切面增强,动态生成目标对象的代理类,并在代理类的方法中设置拦截器,通过执行拦截器中的逻辑增强了代理方法的功能,从而实现 AOP。
参考链接:
23 种设计模式详解(全23种)
面试官:说说Spring用到了哪些设计模式?



