文章目录
- 设计模式
- 设计模式分类
- UML类图
- 设计模式的原则
- 常用设计模式
- 创建型设计模式
- 单例设计模式
- 饿汉模式
- 懒汉模式(线程不安全)
- 懒汉模式(线程安全)- Synchronized
- 懒汉模式(线程安全)- Double Check
- 懒汉模式(线程安全)- 静态内部类
- 枚举
- 总结
- 工厂设计模式
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
- 建造者设计模式(Builder)
- 结构型设计模式
- 代理设计模式(Proxy)
- 静态代理
- 动态代理
- 行为型设计模式
- 责任链
设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式代表了最佳的实践,通常被有经验的软件开发人员所使用。
- 借助于设计模式可以编写出非常高效的代码,可复用性以及稳健性都会比较强
- 有助于阅读源码框架
设计模式分类
GoF(4人组)设计模式共有23种,根据用途的不同,设计模式可以分为:创建型、结构型、行为型三种。
- 创建型模式
- 由无到有的设计模式,是为了创建应用程序的实例
- 例如:单例模式、工厂模式、建造者模式
- 结构型模式
- 在已有的实例的基础上,做了一些额外的事情
- 例如:代理模式
- 行为型模式
- 多个类或者实例存在的一定的关系
- 例如:责任链模式
UML类图
UML
全称Unified Modeling Language
,是用来进行软件设计的可视化建模工具。
见知乎链接:https://zhuanlan.zhihu.com/p/109655171
eg:
public class UserService {
private String username;
public String password;
Integer age;
public void sayUsername(String username) {
}
public String bye(String name1, Integer days) {
return null;
}
}
设计模式的原则
设计原则按照字母手写简写可以概括为SOLID
原则。
- 单一职责原则(
S
ingle Responsibility Principle)- 让每个类的功能单一 ,尽量使得每个类只负责整个软件的功能模块中的一个
- 开放封闭原则(
O
pen Close Principle)- 对新增开发,对修改封闭
- 已有的代码直接进行修改是有很大风险的,如果有新需求,可以在已有的代码进行进一步扩展
- 里氏替换原则(
L
iskov Substitution Principle)- 凡是父类出现的地方,都可以替换为其子类;子类继承父类,尽量不要重写父类的方法
- eg:
- 迪米特法则(
L
east Knowledge Principle)- 又叫作最少知道原则,指的是一个类/模块对于其他的类/模块有越少的了解越好
- 接口分离原则(
I
nterface Segregation Principle)- 不要写大接口(大:接口中的方法多),否则会给实现类带来负担
- 依赖倒置原则(
D
ependency Inversion Principle)- 开发过程中,先开发接口,在开发实现类
- 具体:实现类(抽象类的子类)
- 抽象:接口和抽象类
- 开发过程中,先开发接口,在开发实现类
常用设计模式
创建型设计模式
单例设计模式
- 保证一个类只有一个实例对象,并提供了一个访问该实例对象的全局节点
- eg:在整个应用程序中,如果要获得MySingleton实例,始终获得的都是同一个
- 单例设计模式分为懒汉模式(懒加载)和饿汉模式(立即加载)
- 懒加载:使用的时候才获得实例
- 立即加载:使用之前已经获得实例
- 单例设计模式的设计原则:
-
- 构造方法私有
-
- 定义一个成员变量(私有静态的成员变量),用来接收私有构造方法构造的实例
-
- 提供一个静态方法供外部类调用这个实例
-
饿汉模式
- 特点:不支持延时加载(懒加载),获取对象速度比较快;但是如果对象比较大,或者一直没有去使用,那么比较浪费内存空间。
// 单例设计模式:在整个应用程序中,如果要获得MySingleton实例,始终获得的都是同一个
// 单例手写:是面试过程中常见的问题
/** 1. 构造方法私有
* 2. 定义一个成员变量(私有静态的成员变量),用来接收私有构造方法构造的实例
* 3. 提供一个静态方法供外部类调用这个实例
*/
public class MySingleton {
// new MySingleton();
// 定义了一个实例,要初始化这个实例
// final是不让你去额外修饰它
private static final MySingleton instance = new MySingleton();
private MySingleton(){}
// 调用getInstance方法之前已经完成了实例化
public static MySingleton getInstance(){
return instance;
}
}
懒汉模式(线程不安全)
/**
* 懒加载(线程不安全):在完成instance == null这句判断之后,做了线程的切换,导致线程不安全
*/
public class MySingleton2 {
private static MySingleton2 instance;
private MySingleton2() {
}
public static MySingleton2 getInstance() {
// 第一次使用getInstance方法的时候初始化instance
// 如何识别是不是第一次使用instance
// 第一次使用instance的时候是null
if (instance == null) {
instance = new MySingleton2();
}
return instance;
}
}
懒汉模式(线程安全)- Synchronized
- 效率低
- 因为执行这个方法需要排队
public class MySingleton3 {
private static MySingleton3 instance;
private MySingleton3() {
}
// 使用synchornized关键字即可
public static synchronized MySingleton3 getInstance() {
if(instance == null){
instance = new MySingleton3();
}
return instance;
}
}
懒汉模式(线程安全)- Double Check
public class MySingleton4 {
private static MySingleton4 instance;
private MySingleton4() {}
public static MySingleton4 getInstance() {
// double check:做了两次非空的判断
if (instance == null) {
synchronized (MySingleton4.class) {
// 如果这里的instance == null不判断的话,仍然有线程切换导致创建多次实例的风险
if (instance == null) {
instance = new MySingleton4();
}
}
}
return instance;
}
}
懒汉模式(线程安全)- 静态内部类
- 静态内部类的静态代码块的加载时机,使用静态内部类的时候才执行里面的静态代码块
- 可以把实例化的这部分代码放到静态代码块的内部中
/**
* 懒汉模式:静态内部类的方式进行加载
* 静态代码块中的内容只会执行一次,所以是线程安全的
*/
public class MySingleton5 {
private MySingleton5() {
}
static class Inner {
private static MySingleton5 instance;
static {
instance = new MySingleton5();
}
private static MySingleton5 getInstance() {
return instance;
}
}
// 使用该方法,才会触发静态内部类的静态代码块的初始化 -> 懒加载
public static MySingleton5 getInstance() {
return Inner.getInstance();
}
}
枚举
public enum Singleton6 {
INSTANCE;
public static Singleton6 getInstance(){
return INSTANCE;
}
}
总结
- 饿汉式:在类加载时期,便已经将
instance
实例对象创建了;所以这种方式是线程安全的方式,但是不支持懒加载。 - 懒汉式:该种方式支持懒加载,但是要么不是线程安全,要么虽然是线程安全,但是需要频繁释放锁、抢夺锁,并发量较低。
- 双重检查:既可以实现懒加载,又可以实现高并发的需求。这种方式比较完美,但是代码有一些复杂。
- 静态内部类:使用该种方式也可以解决懒加载以及高并发的问题,代码实现起来比双重检查也是比较简洁。
- 枚举:最简单、最完美的实现方式。
工厂设计模式
- 工厂中一定会提供一个返回实例的方法。其中核心的好处是封装(隐藏)生产的具体细节
- 工厂类或接口的命名方式,通常为XXXFactory
简单工厂模式
- 只要一个工厂(函数)就可以了,那么只需要传入不同的参数,就可以返回不同的产品(实例),这种模式就叫简单工厂模式。
- 未满足开闭原则
eg:
public class SimpleFactoryExecution {
public static void main(String[] args) {
// 非简单工厂模式
withoutFactory();
// 简单工厂模式
withFactory();
}
private static void withFactory() {
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
SimpleAodiFactory simpleAodiFactory = new SimpleAodiFactory();
Aodi aodi = simpleAodiFactory.create(s);
aodi.run();
}
private static void withoutFactory() {
// Aodi aodi = new A4();
// aodi.run();
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
Aodi aodi = null;
// 下面这部分属于生产的细节,要把生产细节隐藏起来
if("A4".equals(s)){
aodi = new A4();
}else if("A5".equals(s)){
aodi = new A5();
}else{
aodi = new Q5();
}
aodi.run();
}
}
public class SimpleAodiFactory {
// static 增不增加都是可以的
public Aodi create(String s){
Aodi aodi = null;
if("A4".equals(s)){
aodi = new A4();
}else if("A5".equals(s)){
aodi = new A5();
}else{
aodi = new Q5();
}
return aodi;
}
}
工厂方法模式
- 核心思想:创建工厂接口,增加不同的实现类之后,它里面的工厂方法就是不用的实现
- 要求:把工厂定义为接口或抽象类,通过不同的实现类实现不同实例的生产
FactoryBean
eg:
// 接口类
public interface AodiFactory {
public Aodi create();
}
public class A4Factory implements AodiFactory{
@Override
public Aodi create() {
return new A4();
}
}
public class FactoryMethodExecution {
public static void main(String[] args) {
AodiFactory aodiFactory = new A4Factory();
aodiFactory.create();
}
}
抽象工厂模式
- 抽象工厂生产的一系列的产品
- 上面两种工厂模式生产的产品比较单一
eg:
建造者设计模式(Builder)
- 开发过程中会遇到这样一个场景,它的名字不叫
XXXBuilder
,但是它是采用建造者设计模式的思想来完成 - eg:
StringBuilder
是建造者设计模式 - 建造者模式也叫作生成器模式,就是分步骤创建复杂对象
- 建造者设计模式的代码风格:
-
- 首先要创建要生产的实例(仅仅是执行了构造方法)
-
- 会提供很多设置属性值的方法
-
- 会提供返回实例的方法(方法名通常是
build
)
- 会提供返回实例的方法(方法名通常是
-
eg:
@Data
public class Phone {
private String battery;
private String screen;
private String os;
private String camera;
private String color;
// 通过@Data提供了getter/setter方法,以及我们打印的时候用的toString方法
}
public class PhoneBuilder {
// 当我创建PhoneBuilder实例的时候,同时会创建一个Phone的实例
// 定义一个全局变量,意味着使用一个Builder实例的方法,其实对同一个Phone做参数设置
private Phone phone = new Phone();
public PhoneBuilder color(String color) {
this.phone.setColor(color);
return this;
}
public PhoneBuilder battery(String battery) {
this.phone.setBattery(battery);
return this;
}
public PhoneBuilder screen(String screen) {
this.phone.setScreen(screen);
return this;
}
public PhoneBuilder os(String os) {
this.phone.setOs(os);
return this;
}
public PhoneBuilder camera(String camera) {
this.phone.setCamera(camera);
return this;
}
// 返回实例的方法
// 虽然这个方法叫建造,但其实在创建Builder实例的时候,要建造的实例已经实例化了
public Phone build() {
return this.phone;
}
}
// 使用
public class UseBuilder {
public static void main(String[] args) {
PhoneBuilder builder = new PhoneBuilder();
// 方法的连续调用
Phone phone = builder.battery("4000mha")
.camera("1080P")
.color("尊贵黑")
.screen("4K高清")
.os("Android")
.build();
System.out.println("phone = " + phone);
}
}
eg:参考StringBuilder的代码风格:
private static void builder1() {
StringBuilder sb = new StringBuilder();
sb.append("hello").append(" world").append("!");
String s = sb.toString();
System.out.println(s);
}
结构型设计模式
代理设计模式(Proxy)
- 增强:满足基本的需求之外,还做了额外的事情(通用的事情)
- 核心特点:
-
- 代理类存在和委托类一样的方法(这个一样指的是外观上)
-
- 代理类执行方法的过程中,一定会执行委托类的方法
-
- 代理模式最大的优点:
- 可以不更改目标类代码的前提下,扩展目标类代码的功能。
静态代理
- 委托类、目标类(target):
UserServiceImpl
- 代理类:
UserServiceProxy
- 要保证代理类和委托类提供的方法的外观完全一样:
-
- 实现和委托类相同的接口
-
- 继承委托类,重写委托类中的方法
-
- 代理类中要调用委托类的方法
- 静态代理最大的缺点:
- 代码较为冗余,每代理一个类,便要手动编写一个代理类
- 代理对象和目标类对象均实现了接口,如果接口发生了修改,不仅目标类需要更改,代理类也需要同步发生修改,维护成本变高了很多
eg:
public interface UserService {
public int insert(String name);
public int remove(int id);
}
public class UserServiceImpl implements UserService{
@Override
public int insert(String name) {
System.out.println("执行UserServiceImpl的insert方法");
return 0;
}
@Override
public int remove(int id) {
System.out.println("执行UserServiceImpl的remove方法");
return 0;
}
}
// 方法1:代理类实现了和委托类相同的接口
public class UserServiceProxy implements UserService{
private UserService userService = new UserServiceImpl();
@Override
public int insert(String name) {
// 应该执行UserServiceImpl的insert方法
int insert = userService.insert(name);
return insert;
}
@Override
public int remove(int id) {
// 应该执行UserServiceImpl的remove方法
int remove = userService.remove(id);
return remove;
}
}
// 方法2:代理类继承了委托类
public class UserServiceProxy1 extends UserServiceImpl{
@Override
public int insert(String name) {
// 调用父类的方法 -> 调用了委托类的方法
return super.insert(name);
}
@Override
public int remove(int id) {
return super.remove(id);
}
}
public class Execution {
public static void main(String[] args) {
withoutProxy();
withProxy();
}
private static void withProxy() {
// 1. 获得代理对象
UserService userService = new UserServiceProxy();
// 2. 使用代理对象调用方法,它的外观跟委托类是一模一样的
userService.insert("1");
}
private static void withoutProxy() {
UserServiceImpl userService = new UserServiceImpl();
userService.insert("1");
}
}
动态代理
- 静态代理的代理类需要自己手动去写,动态代理的代理类不用自己手动去写
- 分类:
JDK
动态代理- 会自动生成代理类,与
UserServiceProxy
类似(代理类和委托类实现了相同的接口) - 效率比较高
- 委托类必须实现接口
- 会自动生成代理类,与
cglib
动态代理- 会自动生成代理类,与
UserServiceProxy1
类似(代理类继承自委托类) - 委托类可以不实现接口
- 会自动生成代理类,与
JDK动态代理
JDK
动态代理,即JDK
给我们提供的动态生成代理类的方式,无需引入第三方jar
包,但是使用JDK
动态代理有一个先决条件,那就是目标类对象必须实现了某个接口;如果目标类对象没有实现任何接口,则JDK
动态代理无法使用。- 生成的代理类中的所有的方法都会指向同一个方法:InvocationHandler的invoke方法,需要程序员来实现InvocationHandler(可以直接写实现类、也可以使用匿名内部类)的invoke方法
eg:
public interface UserService {
public String sayHello(String name);
}
public class UserServiceImpl implements UserService{
@Override
public String sayHello(String name) {
String result = "hello " + name;
System.out.println(result);
return result;
}
}
@Data
public class ProxyGenerator {
// 目标类,委托类对象
Object target;
// 返回代理对象
public Object generator() {
// JDK动态代理的代理对象生成
// 新增一个代理的对象
/**
* Proxy.newProxyInstance( 1 , 2 , 3);
* 1. 类加载器
* 2. 委托类接口的数组
* 3. InvocationHandler 指导代理对象中的方法做何种增强
* 返回值:代理对象
*/
UserService proxy = (UserService) Proxy.newProxyInstance
(ProxyGenerator.class.getClassLoader(),
UserServiceImpl.class.getInterfaces(),
new CustomInvocationHandle(target));
proxy.sayHello("zs");
return proxy;
}
// 匿名内部类实现
public Object generator2() {
UserService proxy = (UserService) Proxy.newProxyInstance
(ProxyGenerator.class.getClassLoader(),
UserServiceImpl.class.getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object invoke = method.invoke(target, args);
return invoke;
}
});
return proxy;
}
}
@AllArgsConstructor
@NoArgsConstructor
public class CustomInvocationHandle implements InvocationHandler {
Object instance;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");
Object invoke = method.invoke(instance, args);
System.out.println("关闭事务");
return invoke;
}
}
public class JdkProxyExecution {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyGenerator proxyGenerator = new ProxyGenerator();
proxyGenerator.setTarget(userService);
UserService proxy = (UserService) proxyGenerator.generator();
UserService proxy2 = (UserService) proxyGenerator.generator2();
proxy.sayHello("zs");
}
}
开启事务
hello zs
关闭事务
开启事务
hello zs
关闭事务
cglib动态代理
- 生成的代理类中的所有的方法都会指向同一个方法:InvocationHandler的invoke方法,需要程序员来实现InvocationHandler(可以直接写实现类、也可以使用匿名内部类)的invoke方法
- 主要原因在于
Cglib
扩展的代理类会继承自目标类。所以这也要求我们的目标类不能是final
修饰。
eg:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CglibProxyGenerator {
Object target;
public Object generator(){
// 第一个参数传委托类的class
Object proxy = Enhancer.create(UserServiceImpl.class, new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] objects)
throws Throwable {
System.out.println("开始事务");
Object invoke = method.invoke(target, objects);
System.out.println("结束事务");
return invoke;
}
});
return proxy;
}
}
public class CglibProxyExecution {
public static void main(String[] args) {
CglibProxyGenerator generator = new CglibProxyGenerator();
generator.setTarget(new UserServiceImpl());
UserService proxy = (UserService) generator.generator();
proxy.sayHello("zs");
}
}
开始事务
hello zs
结束事务
动态代理小结:
- 代理类中一定会包含和委托类外观一致的方法,该方法中一定会有委托类方法的调用
- 静态代理:(
instance
、super调用
) - 动态代理:
method.invoke
- 静态代理:(
- JDK动态代理的委托类一定要实现接口,JDK代理对象只能使用接口来接收(猫不能接收狗)
- 代理类实现了和委托类相同的接口
- Cglib动态代理,接口和实现类都可以接收
- 代理类继承委托类
- 使用动态代理,所有的方法都会指向
InvocationHandler
的invoke
方法- 真正需要程序员开发的内容:提供InvocationHandler的实现类(定义实现类或匿名内部类),实现其中的invoke方法
- 动态代理的优势在于:实现方法的通用的增强,把委托类中出现的相同的内容给提取出来
面试问题:代理类和委托类之间的关系是什么? 分JDK动态代理和Cglib动态代理两方面回答
行为型设计模式
责任链
责任链是一种行为设计模式,允许请求沿着链进行发送。收到请求后,每个处理者均可对请求进行处理或者将其传递给链上的下一个处理者。
eg:
将一些共性的部分放置在一个基类中,其中提供的成员变量next
能够维护顺序关系,通过调用其提供的setNext
方法完成顺序关系的维护,handle
方法能够提供不同的
@Data
public abstract class AbstractHandler {
AbstractHandler next;
public void handle(){
handleCore();
if (next != null) {
next.handle();
}
}
protected abstract void handleCore();
}
/**
* 1. 包含处理方法
* 2. 执行完当前处理方法,要执行下一个处理器的处理方法
* 3. 内部可以通过成员变量指向下一个处理器
*/
@Data
public class Handler1 extends AbstractHandler {
//AbstractHandler next;
/*public void handle(){
handleCore();
if (next != null) {
next.handle();
}
}*/
public void handleCore() {
System.out.println("Handler1的handle");
}
}
@Data
public class Handler2 extends AbstractHandler {
//AbstractHandler next;
/*public void handle(){
handleCore();
if (next != null) {
next.handle();
}
}*/
public void handleCore() {
System.out.println("Handler2的handle");
}
}
@Data
public class Handler3 extends AbstractHandler {
//AbstractHandler next;
/*public void handle(){
handleCore();
if (next != null) {
next.handle();
}
}*/
public void handleCore() {
System.out.println("Handler3的handle");
}
}
public class ChainExecution {
public static void main(String[] args) {
Handler1 handler1 = new Handler1();
Handler2 handler2 = new Handler2();
Handler3 handler3 = new Handler3();
Handler4 handler4 = new Handler4();
handler1.setNext(handler2);
handler2.setNext(handler3);
handler3.setNext(handler4);
// handler1 -> handler2 -> handler3
// handler1.handle -> handler2.handle -> handler3.handle
handler1.handle();
}
小结:
- 责任链模式降低了系统之间的耦合性,提升了系统的可扩展性。
- 在很多中间件、框架的内部大量地使用了该种设计模式,比如Filter的执行过程等。