Spring中涉及的设计模式
Spring中涉及的设计模式主要有:
• 简单工厂模式(不算23种之一,只是一种简单的思想8)
• 工厂方法模式
• 单例模式
• 适配器模式
• 包装类模式
• 代理模式
• 观察者模式
• 策略模式
• 模板模式
代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的方法,代理类在运行时创建的代理称之为动态代理
在Spring中,AOP就是代理模式的一种实现,在扩展新的功能和方法时不需要去改变类,在SPring中使用JdkDynamicAopProxy(基于JDK自带的代理模式)和Cglib2AopProxy(CGlib实现的代码)
简单工厂模式(Simple Factory)通过不同的参数返回不同的实例
简单工厂介绍
简单工厂模式又称之为静态工厂方法模式(Static Factory Method),属于创建性模式
• 在简单工厂模式中,可以根据参数的不同返回不同类的实例
• 简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同父类
简单工厂模式包含以下角色:
Factory:工厂角色,可以是类也可以是接口
Product:抽象产品角色
ConcreteProduct:具体产品角色,均为Factory的子类
工厂角色:是简单工厂模式的核心,负责实现创建所有具体产品类的实例,工厂类可以被外界直接调用,创建所需的产品对象
抽象产品角色:是所有具体产品角色的父类或者接口,负责描述所有实例所共有的公共方法
具体产品角色:继承自抽象产品角色,一般为多个,是简单共产模式的创建膜表,工厂返回的都是该角色的某一具体产品
优缺点:
优点:只需要传入一个正确的参数,就可以后去所需要的对象而无须知道创建的细节
缺点:工厂类的职责相对过重,每增加一个具体的产品类就需要去在工厂类的判断逻辑里面去增加兼容,违背开闭原则
设计模式原则-开闭原则(Open close Principle)
一个软件实体如类,模块和函数应该对扩展开放,对修改关闭
即每当你需要在业务逻辑类中增加一些功能的时候,尽量不要去碰原来的业务功能代码,因为你若改变了之前的代码,由于整个项目中复杂的继承关系会导致影响到其他类中的功能
代码演示
/**
* 抽象产品角色:父类、接口
*/
public abstract class Video {
public abstract void produce();
}
/**
* 具体产品角色
*/
public class JavaVideo extends Video {
@Override
public void produce() {
System.out.println("java 课程视频");
}
}
/**
* 具体产品类型
* 特点:必须继承父类,重写方法
* 实现接口提供方法
*/
public class CVideo extends Video{
@Override
public void produce() {
System.out.println("C 课程视频");
}
}
/**
* 工厂角色
*/
public class VideoFactory {
//注意:工厂方法的返回类型,是video
public Video getVideo(String videoType){
if ("java".equalsIgnoreCase(videoType)){
return new JavaVideo();
} else if ("c".equalsIgnoreCase(videoType)) {
return new CVideo();
}
return null;
}
public Video getVideo(Class clazz){
Video video = null;
try {
video = (Video) clazz.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return video;
}
}
/**
* 测试类
*/
public class TestMain {
public static void main(String[] args) {
VideoFactory factory = new VideoFactory();
Video video = factory.getVideo("c");
//JavaVideo的具体产品实现类
video.produce();
}
}
Spring中的使用案例
Spring中BeanFactory就是简单工厂模式的体现,根据传入一个唯一的表示来获取Bean对象
配置如下:
<!--依赖注入方式1:有参构造-->
<bean id="user3" class="com.tulun.bean.User">
<!--id属性的注入-->
<constructor-arg name="id" value="22"></constructor-arg>
<!--name属性的注入-->
<constructor-arg name="name" value="zhangsan"></constructor-arg>
</bean>
<!--依赖注入方式2:set方法-->
<bean id="user4" class="com.tulun.bean.User" >
<!--id属性注入-->
<property name="id" value="12"/>
<!--name属性赋值-->
<property name="name" value="李四"/>
</bean>
Java代码的调用
//获取IOC的容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext1.xml");
User user1 = (User) applicationContext.getBean("user");
工厂方法模式(Factory Method)
其实就是把上面的工厂又单独分层,一个产品对应一个工厂
工厂方法模式介绍
通过定义工厂父类负责定义创建对象的公共方法,而子类负责生成具体的对象,将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成
由子类工厂决定应该实例化哪一个类
工厂方法模式中的角色:
抽象产品(Product):具体产品的父类,描述具体产品的公共接口
具体产品(Concrete Product:抽象产品的子类,工厂类创建的目标类,描述具体的产品
抽象工厂(Factory):具体工厂的父类,描述的是具体工厂的公共接口
具体工厂(Concrete Factory) :抽象工厂的子类,被外界调用 ,描述具体工厂,创建产品实例
解决了简单工厂的缺点:违背了开放-关闭原则
是因为工厂模式将具体产品的创建推迟到工厂类的子类(具体工厂)中
此时工厂类步子啊负责常见所有的产品,而是给出具体工厂必须实现的接口,工厂方法模式在添加新产品的时候就不需要修改工厂类逻辑而是添加新的工厂子类
缺点:每新增一个产品,除了新增产品类外,还需要提供与之对应的具体工厂类,系统中类个数将是成对出现,增加系统的复杂性
代码演示
/**
* 创建产品类,定义具体产品的公共接口
*/
public abstract class Video {
public abstract void produce();
}
/**
* 抽象工厂类:定义具体工厂的公共接口
*/
public abstract class Factory {
//子实现类来创建具体产品
public abstract Video structVideo();
}
/**
*具体产品-JAVA视频类
*/
public class JavaVideo extends Video{
@Override
public void produce() {
System.out.println("Java 视频课程");
}
}
/**
* 具体工厂-创建不同具体产品类的实例
*/
public class JavaFactory extends Factory{
@Override
public Video structVideo() {
return new JavaVideo();
}
}
/**
*具体产品-C视频类
*/
public class CVideo extends Video {
@Override
public void produce() {
System.out.println("C 课程视频");
}
}
/**
* 具体工厂类-创建不同具体产品类的实例
*/
public class CFactory extends Factory {
@Override
public Video structVideo() {
return new CVideo();
}
}
/**
* 测试工厂方法类
*/
public class TestFactoryMethod {
public static void main(String[] args) {
//看Java课程视频
JavaFactory javaFactory = new JavaFactory();
Video video = javaFactory.structVideo();
video.produce();
//看C课程视频
CFactory cFactory = new CFactory();
cFactory.structVideo().produce();
}
}
Spring中的使用案例
通过静态工厂实例化bean
public class Factory {
//创建User对象的静态方法
public static User getUserBean() {
return new User();
}
}
在配置文件中,将其纳入Spring容器管理,需要通过factory-method指定静态方法名称
<!--通过静态工厂方法创建对象,直接使用class来执行静态类,Factory-method指定方法就行-->
<bean id="user1" class="com.tulun.Factory" factory-method="getUserBean"/>
单例模式
单例模式的介绍
单例模式具有的特点:
• 单例类只能有一个实例
• 单例类必须自己创建自己的唯一实例
• 单例类必须给其他对象提供这一唯一实例
保证类的对象在内存中的唯一性
单例的特点:构造函数是私有的,并且有一个静态成员变量(也可以是静态块)以及对外提供一个方法,通过这个方法可以获取对象的实例
代码示例
饿汉式
特点:线程安全,无法实现实例懒加载策略。
1.为什么是线程安全的呢?
加载类的生命周期有关,static修饰的静态变量,类,在类加载时已经被初始化了,getInstance是在使用阶段操作,类已经被初始化好了,在JVM中相同的类只能被加载一次,安全是有类加载机制(如双亲委派模型)保证
2.为什么方法是静态的?
不能new对象却想调用类中方法,方法必然是静态的,静态方法只能调用静态成员,所以对象也是静态的。
3.为什么对象的访问修饰符是private,不能是public 吗?
不能,如果访问修饰符是Public,则Single.s也可以得到该类对象,这样就造成了不可控。
public class Single1 {
private static final Single1 s=new Single1();
private Single1(){}
public static Single1 getInstance(){
return s;
}
public static void main(String[] args) {
//类加载过程
//7步 static -使用-销毁对象
Single1 single1 = Single1.getInstance();
}
}
单例模式-懒汉式
特点:线程不安全,实现了实例懒加载策略。
懒汉式和饿汉式相比的区别就是懒汉式创建了延迟对象同时饿汉式的实例对象是被修饰为final类型。
优点:懒汉式的好处是显而易见的,它尽最大可能节省了内存空间。
缺点:在多线程编程中,使用懒汉式可能会造成类的对象在内存中不唯一,
虽然通过修改代码可以改正这些问题,但是效率却又降低了。
总结:
懒汉式在面试的时候经常会被提到,因为知识点比较多,而且还可以和多线程结合起来综合考量。
饿汉式在实际开发中使用的比较多。
public class Single2 {
private static Single2 s = null;
private Single2() { }
public static Single2 getInstance() {
//两个线程同时进行if (s == null)判断,则都会进入if条件吗,就会创建对个实例
if (s == null)
s = new Single2();
return s;
}
}
单例模式-全局锁式
其实就是把懒汉式对外开发的方法改成加锁的,但是syn所作为全局锁效率并不高
特点:线程安全,且实现了懒加载策略,但是线程同步时效率不高(synchronized)。
public class Single3 {
private static Single3 single3;
private Single3() {}
//synchronized修饰的是静态方法,锁类对象
public synchronized static Single3 getInstance() {
if (single3 == null)
single3 = new Single3();
return single3;
}
}
静态代码块式
特点:线程安全,类主动加载时才初始化实例,实现了懒加载策略,且线程安全。
public class Single4 {
private final static Single4 singleton4;
private Single4() { }
static {
singleton4 = new Single4();
}
public static Single4 getInstance() {
//使用之前将singleton4属性通过静态代码块实现
return singleton4;
}
public static void main(String[] args) {
//第一次调用Single4,JVM需要负责将Single4加载到内存中,在加载的过程处理静态代码块
Single4.getInstance();
//第二次调用Single4,JVM中已经存在Single4,直接使用getInstance
Single4.getInstance();
}
}
单例模式-双重校验锁式
- 特点:线程安全,且实现了懒加载策略,同时保证了线程同步时的效率。
- 但是volatile强制当前线程每次读操作进行时,保证所有其他的线程的写操作已完成。
- volatile使得JVM内部的编译器舍弃了编译时优化,对于性能有一定的影响。
效率会相对比较高因为并不是每次都加锁,而是没有被实例化出来时才会加锁,以后都不会再进行加锁操作了
public class Single5 {
private static volatile Single5 singleton5;
private Single5() {}
public static Single5 getInstance() {
if (singleton5 == null) {
synchronized (Single5.class) {
if (singleton5 == null) {
//内层if判断使用的时间(起作用时机)
//第一次两线程同时调用getInstance,都会进入外层if判断
//内层if判断是针对第二个进入synchronized代码块线程,此时第一个线程已经创建出对象
//第二个线程无需创建
singleton5 = new Single5();
}
}
}
return singleton5;
}
}
单例模式-静态内部类式(推荐)
【推荐】静态代码块的思路是一样的
- 特点:线程安全,不存在线程同步问题,
- 且单例对象在程序第一次 getInstance() 时主动加载 SingletonHolder 和其 静态成员 INSTANCE,
- 因而实现了懒加载策略。
public class Single6 {
private Single6() {}
private static class SingletonHolder {
private static final Single6 INSTANCE = new Single6();
}
public static Single6 getInstance() {
return Single6.SingletonHolder.INSTANCE;
}
}
单例模式-枚举方式(推荐方式)
- 特点:线程安全,不存在线程同步问题,且单例对象在枚举类型 INSTANCE
- 第一次引用时通过枚举的 构造函数 初始化,因而实现了懒加载策略。
- 这种方式是Effective Java作者 Josh Bloch 提倡的方式,
- 它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,
- 可谓是很坚强的壁垒啊。不过,由于JDK 1.5中才加入enum特性
public class Single7 {
private Single7() {}
enum SingletonEnum {
INSTANCE;
private final Single7 singleton7;
private SingletonEnum() {
singleton7 = new Single7();
}
}
public static Single7 getInstance() {
return SingletonEnum.INSTANCE.singleton7;
}
}
Spring中使用案例
Spring中默认的bean均是singleton,可以通过scope来定义
在SPring中的类AbstractBeanFactory中doGetBean中
适配器模式(Adapter Pattern)(类的兼容转换)
适配器模式的介绍
将一个接口或者类转化为客户所需要的另一个接口或者类,使接口不兼容的一些类可以一起工作,也称为为包装类(Wrapper)
适配器模式分为两种:类适配器和对象适配器
类适配器模式使用继承的方式
对象适配器模式使用的组合模式
适配器模式中存在3个角色:
目标角色:客户所期望得到的接口
源角色:需要进行适配的接口
适配器角色:模式的核心,适配器将源接口转换成目标角色
适配器模式的优缺点:
优点:
1、有更好的复用性,系统直接使用现有的类,通过对现有的类适配就可以达到更好的复用
2、有更好的扩展性,实现适配器,可以调用自己开发的功能
缺点:过多的使用适配器会是系统比较凌乱,明明调用A接口,内部却适配成了B接口
在Java中最主要的使用就是IO流中
代码演示
比如说需要把USB接口适配成TypeC接口,用户使用的是USB而在实际使用中要使用TypeC,USB为目标角色,TypeC为源角色
适配器类只需要继承源角色的类,即可以调用源角色中的方法,再去实现目标角色的接口,即要重写目标中的方法,在重写的目标方法中调用源角色的方法即可完成适配
/**
* 源角色的接口
*/
public interface TypeC {
void isTypeC();
}
/**
* 源角色接口的实现类
*/
public class TypeCImpl implements TypeC {
@Override
public void isTypeC() {
System.out.println("typec充电口");
}
}
/**
* 有个传统的Micro USB的充电线
* 用户要使用的角色:目标角色
*/
public interface MicroUSB {
void isMicroUSB();
}
/**
* 类适配器
* 整个适配器adapter,将MicroUSB 转化成typec
* 继承源角色的类实现目标角色的接口
* 继承源角色的实现类实现目标角色的接口
*/
public class Adapter extends TypeCImpl implements MicroUSB {
@Override
public void isMicroUSB() {
//typeC的实现
isTypeC();
}
public static void main(String[] args) {
MicroUSB usb = new Adapter();
usb.isMicroUSB();
}
}
/**
* 对象适配器
*/
public class ObjectAdapter implements MicroUSB {
//源角色的对象作为属性
private TypeC typeC;
public ObjectAdapter(TypeC typeC) {
this.typeC = typeC;
}
@Override
public void isMicroUSB() {
typeC.isTypeC();
}
public static void main(String[] args) {
//源对象
TypeC typeC = new TypeCImpl();
MicroUSB usb = new ObjectAdapter(typeC);
usb.isMicroUSB();
}
}
Spring中的使用
在Spring的AOP中,使用advice(通知)来增强被代理类的功能
主要研究而适配器在IO流中的使用,字节输入/输出流、字符输入/输出流
策略模式(Strategy Pattern)
策略模式的介绍
根据引用参数的不用去调用具体的策略
一个类的行为或其方法可以在运行时更改
这种类型的设计模式属于行为型模式
策略模式中有三个角色
抽象策略类(Strategy):定义了公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或者抽象类实现
具体策略类(Concrete Strategy):实现抽象策略定义的接口,提供具体的算法实现
环境类(Context):持有一个策略类的应用,最终给客户端调用
策略模式的优点:
1、上下文和具体策略时松耦合的关系,因此上下文只知道他使用某一个实现了Strategy接口类的实例,但是不知道具体的哪一个策略类
2、策略模式满足“开-闭原则”,当增加新的策略模式是,不需要修改上下文类的代码,上下文就可以引用新的策略的实例
代码示例
在环境类中声明抽象策略属性,并且设置带有参数的set策略注入方法,只要将具体的策略类作为参数靠set方法创建出来就可以调用其相应的具体的策略方法
//抽象策略类
public abstract class Strategy {
//策略方法
public abstract void strategyMethod();
}
//具体策略类A
public class ConcreteStrategyA extends Strategy {
@Override
public void strategyMethod() {
System.out.println("具体策略A的策略方法被访问!");
}
}
//具体策略类B
public class ConcreteStrategyB extends Strategy {
@Override
public void strategyMethod() {
System.out.println("具体策略B的策略方法被访问!");
}
}
//环境类
public class Context {
//持有策略对象的引用
private Strategy strategy;
public Strategy getStrategy() {
return strategy;
}
// 通过set方法将策略对象注入
public void setStrategy(Strategy strategy) {
this.strategy=strategy;
}
public void strategyMethod() {
strategy.strategyMethod();
}
}
//测试类
public class TestMain {
public static void main(String[] args) {
// 环境类对象
Context context = new Context();
//给定具体策略对象的应用
context.setStrategy(new ConcreteStrategyA());
context.strategyMethod();
// context.setStrategy(new ConcreteStrategyB());
// context.strategyMethod();
}
}
Spring中的使用案例
在Spring容器中注入不同类型的配置文件时,不同类型的配置信息的处理或者路径等不同,处理时的耦合性太高,通过IOC容器提供的处理不同类型资源的文件来达到实例化IOC容器
不同资源文件即是具体的策略类,ApplicationContext是抽象策略类(环境类),通过实例化的队形,可以直接通过getBean来获取IOC管理的对象实例,而屏蔽了对象实例的位置