- 设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
设计模式七大原则
- 单一职责原则:一个类应该只负责一项职责。提高可读性维护性、降低耦合。
- 接口隔离原则:类之间的依赖关系应该建立在最小的接口上。例如实现类只能用到接口的部分方法,为了降低冗余提高可维护性,对原接口进行合理的拆分和组合。
- 依赖倒转原则:高层模块不应该直接依赖于低层模块,两者都应该依赖于抽象。即详细应该依赖于抽象。在调用链上,调用者属于高层,被调用者属于低层。例如controller层不能直接依赖于service层,他们都应该依赖于抽象,即service接口。
- 里氏代换原则:子类引用指向父类对象后功能未变,子类中尽量不要重写父类的方法。
- 开闭原则:软件对扩展开放,对修改关闭。提高扩展性和可维护性。
- 迪米特法则:最少知道原则,一个类对自己依赖的类知道的越少越好,只与直接的朋友(成员变量,方法参数,方法返回值的类)通信。
- 合成复用原则:尽量使用聚合或组合的方式,而不是使用继承。也就是把需要用到的类作为本类的参数、成员变量、局部变量。
设计模式类型
- 创建型模式:用于对象的创建,如:工厂模式、单例模式。
- 结构性模式:描述对象之间的组合关系,如:代理模式、外观模式。
- 行为型模式:描述对象之间的通信以及责任分配,如:策略模板、模板方法模式、观察者模式。
工厂模式
工厂模式解决了对象的创建过程的灵活性和可维护性问题,允许在不暴露对象创建逻辑的情况下,统一由工厂类创建对象并返回,从而降低了代码的耦合性。
优点:避免直接使用new关键字创建对象带来的耦合性,方便后期维护;将对象的创建逻辑封装到一个工厂类中,提高了代码的复用性;降低耦合性、提高复用性。
分类:
- 简单工厂:简单工厂模式相当于是一个工厂中有各种产品,创建在一个类中,客户无需知道具体产品的名称,只需要知道产品类所对应的参数即可。但是工厂的职责过重,而且当类型过多时不利于系统的扩展维护。【提前把所有同类对象放入到工厂这个类里面,工厂只提供同类对象的参数接口,调用者只需传入具体的参数,在工厂内部选择具体的实现】
- 工厂方法:工厂方法模式Factory Method,又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节
- 抽象工厂:抽象工厂简单地说是工厂的工厂,抽象工厂可以创建具体工厂,由具体工厂来产生具体产品。
-
简单工厂、工厂方法、抽象工厂的区别:
- 简单工厂:用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。
- 工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。
- 抽象工厂:用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族。
单例模式
- 单列模式:保证一个类只有一个实例,并且只提供一个取得对象实例的静态方法。
- 使用场景:需要频繁的进行创建和销毁对象、创建对象时耗时过多或耗费资源过多但又经常用到的对象、工具类对象......
- 优点:
-
- 节省资源:只有一个实例,避免重复创建对象,从而节省了资源,提高了系统性能。
- 管理全局变量:单例模式可以用于管理全局状态和变量,方便在整个系统中共享数据。
- 分类:①饿汉式;②简单懒汉式(在方法声明时加锁);③DCL双重检验加锁(进阶懒汉式);④静态内部类(优雅懒汉式);⑤枚举。
- 饿汉式:还没用到就直接初始化了对象。
- 懒汉式:等到用到的时候才进行初始化。
- DCL双重检验加锁中使用volatile关键字修饰:
-
-
- 保证变量的可见性,修改后立即更新到内存中。
- 防止指令重排,用于保证对象被正确创建后才被其他线程正确使用。
-
- 饿汉式和懒汉式的对比:
-
- 执行效率:饿汉式没有加任何锁,执行效率相对较高;懒汉式内部一般加synchronized修饰,效率较低。
- 性能上:饿汉式在类加载的时候就初始化,不管是否使用都会实例化,占据内存空间,浪费内存;懒汉式在需要的时候才进行实例化,相对来说不浪费内存。
DCL中使用双重检查的方式可以减少锁的竞争,提高性能。
// 饿汉式单例模式
public class Singleton{
// 构造器私有化
private Singleton(){}
// 类的内部创建对象
private final static Singleton singleton = new Singleton();
// 向外暴露一个静态的公共方法
public static Singleton getInstance(){
return singleton;
}
}
// 简单懒汉式(加同步锁)
public class Singleton{
// 构造器私有化
private Singleton(){}
private static Singleton singleton = null;
public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
// DCL懒汉式
public class Singleton{
// 构造器私有化
private Singleton(){}
// 此处加volatile①禁止指令重排,②保证对共享变量的修改的可见性
private static volatile Singleton singleton = null;
public static Singleton getInstance(){
if(singleton == null){
// 第一次检查可能会有多个线程同时检查
// 类级别的锁对象,锁对象是全局的,对该类的所有实例都有效。
synchronized(Singleton.class){
if(singleton == null){ // 第二次检查,只有一个线程进入检查并创建实例
singleton = new Singleton();
}
// 使用双重检查的方式可以减少锁的竞争,提高性能。
}
}
return singleton;
}
}
// 内部类
public class Singleton{
// 构造器私有化
private Singleton(){}
private static class SingletonInstance{
private static final Singleton singleton = new Singleton();
}
private static Singleton getInstance(){
return SingletonInstance.singleton;
}
}
// 枚举类
public enum Singleton{
INSTANCE;
}
代理模式
代理模式概述:
- 代理模式属于结构型的模式。指一个对象本身不做实际的操作,而是通过其他对象来得到自己想要的结果。
-
静态代理
目标对象和代理对象实现相同的接口,在程序运行之前,代理类字节码.class就已经编译好了。
JDK动态代理
动态代理类与静态代理类最主要不同的是,代理类的字节码不是在程序运行前生成的,而是在程序运行时在虚拟机中程序自动创建的。
编写一个类实现InvocationHandler接口,然后重写invoke方法,这个invoke方法就是我们提供的代理方法。通过Proxy类的newProxyInstance()方法,传入invocationHandler接口的实现类等参数,返回一个代理对象。并且生成的代理类实现了原来那个类的所有接口,并对接口的方法【就是Invoke方法】进行了代理,我们通过代理对象调用这些方法的时候,底层会通过反射,调用我们实现的invoke方法。
其中invoke()方法中的method.invoke(target, args)就是利用Java反射机制在运行时动态地在内存中生成代理对象。
CGLIB动态代理
CGLIB动态代理可以直接代理类,JDK动态代理目标业务类必须实现接口。
简单原理:通过自定义实现拦截器接口(MethodInterceptor)的类,并重写intercept()用于拦截增强被代理类的方法【类似于JDK动态代理中的invoke()方法】。通过Enhancer 类的 create()创建简单的代理类。
CGLIB采用非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术的拦截所有父类的方法调用,顺势织入横切逻辑。(CGLIB在字节码的基础上,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)
JDK动态代理和CGLIB动态代理的对比:
- JDK动态代理只能代理实现了接口的类,而CGLIB可以代理未实现任何接口的类。
- JDK动态代理是实现了被代理对象所实现的接口;CGLIB是继承了被代理对象。
- JDK和CGLIB都是在运行期生成字节码,JDK是直接写Class字节码,CGLIB是使用ASM框架写Class字节码。
外观模式
外观模式->结构型模式
外观模式:也称为门面模式,隐藏系统的复杂性,并向客户端提供一个客户端可以访问系统的接口。它向现有的系统添加一个接口,用这一个接口来隐藏实际的系统的复杂性。使用外观模式,他外部看起来就是一个接口,其实他的内部有很多复杂的接口已经被实现。
例子:用户注册完之后,需要调用阿里短信接口,邮件接口,微信推送接口。
策略模式
策略模式是一种行为型模式。当在处理一个业务时,有多种处理方式,并且需要在运行时决定使用哪一种具体的实现时,就会使用策略模式。
使用场景:
- 一个系统需要动态地在几种算法中选择一种:我要一个支付模块,我要有微信支付、支付宝支付、银联支付等
- 如果一个对象有很多行为,如果不使用恰当的模式,这些行为就只好只用多重的条件选择语句来实现。
使用方式:
- 定义策略接口。
- 建立策略接口的具体实现类。
- 重新定义需要的服务,在服务里面传入具体的策略接口的实现类。
模板方法模式
行为型模式:用以描述对象之间的通信和责任分配。
模板方法模式:定义一个操作中的算法骨架(父类),将一些方法的实现延迟到子类中【重写】。实现子类在不改变该算法结构的前提下实现重定义该算法。
使用时机:实现一些操作,整体步骤很确定,其中只有一小部分需要改变,此时可以使用模板模式。将容易改变的部分抽象出来,供子类实现。
应用场景:
-
- AQS基于模板方法进行设计的,锁的实现需要继承AQS并重写它指定的方法。【AQS的模板方法将“管理同步状态的逻辑”提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。 】
- 数据库访问的封装。
- Junit单元测试。
优势:
可重用性和可维护性:由于算法的骨架在父类或者抽象类中实现,因此可以避免在每个子类中重复编写相同的代码,提高了代码的可重用性和可维护性;
可扩展性:由于子类只需要实现特定的部分而不需要修改算法的整体结构,因此可以提高代码的安全性和可扩展性;
一致性和稳定性:可以在父类或者抽象类中控制算法的结构和执行流程,从而保证算法的一致性和稳定性。
// 定义一个模板
package com.lijie;
//模板方法
public abstract class RestaurantTemplate {
// 1.看菜单
public void menu() {
System.out.println("看菜单");
}
// 2.点菜业务
abstract void spotMenu();
// 3.吃饭业务
public void havingDinner(){ System.out.println("吃饭"); }
// 3.付款业务
abstract void payment();
// 3.走人
public void GoR() { System.out.println("走人"); }
//模板通用结构
public void process(){
menu();
spotMenu();
havingDinner();
payment();
GoR();
}
}
// 具体模板方法子类
package com.lijie;
public class RestaurantGinsengImpl extends RestaurantTemplate {
void spotMenu() {
System.out.println("人参");
}
void payment() {
System.out.println("5快");
}
}
观察者模式
观察者模式属于行为型模式:描述对象之间的通信以及责任分配
观察者模式:又称为发布-订阅模式,定义对象之间的一种一对多的依赖关系,使得当一个对象的状态改变的时候,所有依赖它的对象都能够得到通知并实现自动更新。观察者模式主要用于1对N的通知。当一个对象的状态变化时,他需要及时告知一系列对象,令他们做出相应。
应用场景:
-
- 关联行为场景,关联行为是可拆分的,不是组合关系。
- 事件多级触发场景。
- 跨系统的消息交换场景:消息队列。
// 定义抽象观察者,每一个实现该接口的实现类都是具体观察者。
package com.lijie;
//观察者的接口,用来存放观察者共有方法
public interface Observer {
// 观察者方法
void update(int state);
}
// 定义具体观察者
package com.lijie;
// 具体观察者
public class ObserverImpl implements Observer {
// 具体观察者的属性
private int myState;
public void update(int state) {
myState=state;
System.out.println("收到消息,myState值改为:"+state);
}
public int getMyState() {
return myState;
}
}
// 定义主题。主题定义观察者数组,并实现增、删及通知操作。
package com.lijie;
import java.util.Vector;
//定义主题,以及定义观察者数组,并实现增、删及通知操作。
public class Subjecct {
//观察者的存储集合,不推荐ArrayList,线程不安全,
private Vector<Observer> list = new Vector<>();
// 注册观察者方法
public void registerObserver(Observer obs) {
list.add(obs);
}
// 删除观察者方法
public void removeObserver(Observer obs) {
list.remove(obs);
}
// 通知所有的观察者更新
public void notifyAllObserver(int state) {
for (Observer observer : list) {
observer.update(state);
}
}
}
// 定义具体的,他继承继承Subject类,在这里实现具体业务,在具体项目中,该类会有很多。
package com.lijie;
//具体主题
public class RealObserver extends Subjecct {
//被观察对象的属性
private int state;
public int getState(){
return state;
}
public void setState(int state){
this.state=state;
//主题对象(目标对象)值发生改变
this.notifyAllObserver(state);
}
}
// 运行测试
package com.lijie;
public class Client {
public static void main(String[] args) {
// 目标对象
RealObserver subject = new RealObserver();
// 创建多个观察者
ObserverImpl obs1 = new ObserverImpl();
ObserverImpl obs2 = new ObserverImpl();
ObserverImpl obs3 = new ObserverImpl();
// 注册到观察队列中
subject.registerObserver(obs1);
subject.registerObserver(obs2);
subject.registerObserver(obs3);
// 改变State状态
subject.setState(300);
System.out.println("obs1观察者的MyState状态值为:"+obs1.getMyState());
System.out.println("obs2观察者的MyState状态值为:"+obs2.getMyState());
System.out.println("obs3观察者的MyState状态值为:"+obs3.getMyState());
// 改变State状态
subject.setState(400);
System.out.println("obs1观察者的MyState状态值为:"+obs1.getMyState());
System.out.println("obs2观察者的MyState状态值为:"+obs2.getMyState());
System.out.println("obs3观察者的MyState状态值为:"+obs3.getMyState());
}
}
Spring中使用到的设计模式
Spring的AOP是如何实现的
Spring的AOP(面向切面编程)是通过动态代理实现的;在Spring中,AOP通过在运行时动态地将切面植入到目标对象的方法中,从而实现横切连接点的模块化。
JDK动态代理:基于接口的代理,通过java.lang.reflect.Proxy类和InvocationHandler接口实现。Spring使用JDK动态代理来代理实现了接口的目标对象。
CGLIB动态代理:基于继承的代理,通过CGLIB库生成目标对象的子类来实现代理。Spring使用CGLIB动态代理来代理没有实现接口的目标对象。
在Spring中,通过配置文件或注解来定义通知和切点从而构成切面,然后使用AOP代理将切面织入目标对象的方法中。当目标对象的方法被调用时,AOP代理会在方法执行前、执行后或抛出异常时执行切面的相关逻辑,实现横切关注点的功能,如日志记录、事务管理等。
JDK中常见的设计模式
使用策略模式的例子
Collections.sort(List<T> list, Comparator<? super T> c)方法,这个方法接受一个比较器Compartor参数,客户端在运行时可以传入一个比较器的实现,sort()方法中根据不同实现,按照不同的方式进行排序。