Spring中涉及的设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
Spring中涉及的设计模式主要有:
- 简单工厂模式。
- 工厂方法模式。
- 单例模式。
- 适配器模式。
- 包装类模式。
- 代理模式。
- 观察者模式。
- 策略模式。
- 模板模式。
代理模式(Proxy)
代理模式(Proxy)是为其他对象提供一种代理以控制对这个对象的方法。
- 代理类在运行时创建的代理称之为动态代理。
在Spring中,代理类在AOP的横向切面技术中有使用。
- 在Spring中使用JdkDynamicAopProxy(基于JDK自带的代理模式)和Cglib2AopProxy(CGlib实现的代码)。
简单工厂模式(Simple Factory)
简单工厂模式介绍
简单工程模式又称之为静态工厂方法模式(Static Factory Method),属于创建性模式。
- 在简单工厂模式中,可以根据参数的不同返回不同类的实例。
- 简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同父类。
简单工厂模式包含以下角色:
- Factory:工厂角色。
- 是简单工厂模式的核心,负责实现创建所有具体产品类的实例,工厂类可以被外界直接调用,创建所需的产品对象。
- Product:抽象产品角色。
- 是所有具体产品角色的父类或者接口,负责描述所有实例所共有的公共方法。
- ConcreteProduct:具体产品角色。
- 继承自抽象产品角色,一般为多个,是简单工厂模式的创建模板,工厂返回的都是该角色的某一具体产品。
简单工厂模式的优缺点:
- 优点:只需要传入一个正确的参数,就可以获取所需要的对象而无须知道创建的细节。
- 缺点:工厂类的职责相对过重,增加一个产品需要修改工厂类的判断逻辑,违背开闭原则。
设计模式原则-开闭原则(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("java");
//JavaVideo的具体产品实现类
video.produce();
}
}
Spring中的使用案例
Spring中BeanFactory就是简单工厂模式的体现,根据传入一个唯一的表示来获取Bean对象。
- 配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!--根标签-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--依赖注入方式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>
</beans>
- Java代码的调用:
public class App {
public static void main(String[] args) {
//获取IOC的容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过容器来获取当前的对象(通过无参构造来实例对象方法)
User user = (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指定静态方法名称。
<?xml version="1.0" encoding="UTF-8"?>
<!--根标签-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--通过静态工厂方法创建对象,直接使用class来执行静态类,Factory-method指定方法就行-->
<bean id="user1" class="com.tulun.Factory" factory-method="getUserBean"/>
</beans>
单例模式
单例模式的介绍
单例模式具有的特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给其他对象提供这一唯一实例。
- 保证类的对象在内存中的唯一性。
代码示例
/**
* 单例实现-饿汉式
* 为什么方法是静态的:
* 不能new对象却想调用类中方法,方法必然是静态的,
* 静态方法只能调用静态成员,所以对象也是静态的。
*
* 为什么对象的访问修饰符是private,不能是public 吗?
* 不能,如果访问修饰符是Public,则Single.s也可以得到该类对象,
* 这样就造成了不可控。
*
* 加载类的什么周期有关,static修饰的静态变量,类,在类加载时已经被初始化了,
* getInstance是在使用阶段操作,安全是有类加载机制保证
*
* 特点:线程安全,无法实现实例懒加载策略。
*/
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;
}
}
/**
* 单例模式-全局锁式
*
* 特点:线程安全,且实现了懒加载策略,但是线程同步时效率不高(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)。
适配器模式分为两种:类适配器和对象适配器。
- 类适配器模式使用继承的方式。
- 对象适配器模式使用的组合模式。
适配器模式中存在三个角色:
- 目标角色:客户所期望得到的接口。
- 源角色:需要进行适配的接口。
- 适配器角色:模式的核心,适配器将源接口转换成目标角色。
适配器模式的优缺点:
-
优点:
- 1、有更好的复用性,系统直接使用现有的类,通过对现有的类适配就可以达到更好的复用。
- 2、有更好的扩展性,实现适配器,可以调用自己开发的功能。
-
缺点:过多的使用适配器会是系统比较凌乱,明明调用A接口,内部却适配成了B接口。
在Java中最主要的使用就是IO流。
代码演示
/**
* 源角色的接口
*/
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、策略模式满足“开-闭原则”,当增加新的策略模式时,不需要修改上下文类的代码,上下文就可以引用新的策略的实例。
代码示例
//抽象策略类
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的常见的实现类 | 说明 |
---|---|
ClassPathXmlApplicationContext(常用) | 加载类路径下的配置文件,要求配置文件必须在类路径下 |
FileSystemXmlApplicationContext | 可以加载磁盘任意路径下的配置文件(必须要有访问权限) |
XmlWebApplicationContext | 在Web环境下读取配置信息 |
AnnotationContigApplicationContext | 用于读取注解创建容器 |
不同资源文件即是具体的策略类,ApplicationContext是抽象策略类(环境类),通过实例化的对象,可以直接通过getBean来获取IOC管理的对象实例,而屏蔽了对象实例的位置。