前言
在看狂神频道的时候偶然发现下图,感触颇深。特别在当今【程序 = 业务 + 框架】思想盛行的开发者中,夯实基础基础显得格外重要,因此开此专栏总结记录。
设计模式详解
-
设计模式的考察点,一般有2个:
- 一个是常用设计模式的实现,
- 另外一个是设计模式的使用场景。也就是每个设计模式用来解决什么样的问题,在什么场景下该使用什么样的设计模式。
-
最常见的设计模式有单例模式、工厂模式、代理模式,构造者模式、责任链模式、适配器模式、观察者模式等。
-
我们看一下详解设计模式的知识点分为三大类型,共23种:
-
创建型的有5种:
(抽 工 单 建 原)
- 抽象工厂模式、工厂方法模式、单例模式、建造者模式、原型模式。
-
结构型的有7种:
(有个长得不错的桥梁工程代理喜欢拿着组合的转接头去享受缘分)
- 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型的有11种:
(访问者写好策略备忘录,观察模板迭代的状态,命令中介解释责任链。)
解释:这句话讲的就是看房子的经过。
看房子的人就是访问者,看房前要写看房策略和备忘录,不能马马虎虎地去看房子。
去看房子的时候,要仔细观察楼板(模板)层叠(迭代)的状态。
看完房子,命令中介解释清楚产权的责任链。
- 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式。忘录模式状态模式、访问者模式、中介者模式和解释器模式。
-
-
面试中对于设计模式,你应该明白不同设计用来解决什么样的场景问题。对于常用的设计模式能够灵活运用。
常用模式
常用的设计模式有:单例模式、工厂模式、代理模式、构造者模式、责任链模式、适配器模式、观察者模式等,
接下来逐个模式进行讲解
重点:单例模式
-
学习博客:厚积薄发打卡Day28 :狂神说Java之彻底玩转单例设计模式
我引用我自己哈哈
-
首先是单例模式,这个模式在实际业务中经常会用到,也是设计模式中的主要考察点。这里我只介绍线程安全的单例模式的实现方式。
-
静态初始化(饿汉式)
-
实现的思路就是在类初始化时。成单例实例的创建,因此不会产生并发问题。这种方式下,不管是否会使用到这个单例,都会创建这个单例。
-
创建步骤:
- 私有构造器
- 类的内部创建对象
- 向外暴露一个静态的公共方法
-
上代码:
public class HungrySingleton { //1.私有构造器 private HungrySingleton() { } //2.类的内部创建对象 private final static HungrySingleton HUNGRYSINGLE = new HungrySingleton(); //3.向外暴露一个静态的公共方法 public static HungrySingleton getInstance() { return HUNGRYSINGLE; } //测试 public static void main(String[] args) { //单线程测试: HungrySingleton instance1 = HungrySingleton.getInstance(); HungrySingleton instance2 = HungrySingleton.getInstance(); System.out.println(instance1 == instance2); //true //多线程测试: new Thread(() -> { HungrySingleton instanceA = HungrySingleton.getInstance(); System.out.println(instanceA);//HungrySingleton@626213bf }).start(); new Thread(() -> { HungrySingleton instanceB = HungrySingleton.getInstance(); System.out.println(instanceB);//HungrySingleton@626213bf }).start(); //多线程测试非单例: new Thread(() -> { NoneSingleton noneSingleton1 = new NoneSingleton(); System.out.println(noneSingleton1);//NoneSingleton@531ebd8d }).start(); new Thread(() -> { NoneSingleton noneSingleton2 = new NoneSingleton(); System.out.println(noneSingleton2);//NoneSingleton@6bd95d5d }).start(); } } //非单例测试类 class NoneSingleton { }
-
-
双重检查(懒汉式)
-
第二种实现方式是双重检查,也叫做懒汉方式。只有在真正用到这个单例实例的时候才会创建。如果没有使用,就不会创建这个方式,必然会面对多个线程,同时使用实例时的并发问题。
-
为了解决并发访问问题,通过synchronize 的或者lock 进行双重检查,保证只有一个线程能够创建实例,这里需要注意内存可见性引起的并发问题,必须使用volatile关键字,修饰单例变量
-
实现步骤:
- 私有化构造函数
- 创建对象容器(因为new并不是一个原子性操作,需要加锁实例化)
- 对外提供加锁的静态实例化方法
-
上代码(DCL:Double Check Lock)
public class LazySingletonWithDCL { //1. 私有化构造函数 private LazySingletonWithDCL() { System.out.println(Thread.currentThread().getName() + " ->OK"); } //2. 创建对象容器(因为new并不是一个原子性操作,需要加锁实例化) private volatile static LazySingletonWithDCL lazySingleton; //3. 对外提供加锁的静态实例化方法 public static LazySingletonWithDCL getInstance() { synchronized (LazySingletonWithDCL.class) { if (lazySingleton == null) { lazySingleton = new LazySingletonWithDCL(); } } return lazySingleton; } //开启多条线程实例化LazySingletonWithDCL public static void main(String[] args) { for (int i = 0; i < 100; i++) { // new Thread(()->{LazySingletonWithDCL.getInstance();}).start(); new Thread(LazySingletonWithDCL::getInstance).start(); } } } //-------------打印结果------------------- //Thread-0 ->OK
-
看个反例(WithoutDcl)
public class LazySingleton { //1.私有化构造函数 private LazySingleton() { System.out.println(Thread.currentThread().getName()+" ->OK"); } //2.创建对象(容器) private static LazySingleton lazyMan ; //3.对外提供静态实例化方法,判断在对象为空的时候创建 public static LazySingleton getInstance(){ //用的时候再加载 if (lazyMan == null) { lazyMan = new LazySingleton(); } return lazyMan; } //开启多条线程实例化LazySingletonWithDCL public static void main(String[] args) { for (int i = 0; i < 100; i++) { // new Thread(()->{LazySingletonWithDCL.getInstance();}).start(); new Thread(LazySingleton::getInstance).start(); } } } //--------------打印结果:-------------- /* Thread-0 ->OK Thread-5 ->OK Thread-4 ->OK Thread-3 ->OK Thread-2 ->OK Thread-1 ->OK */
-
-
JDK例子:Runtime
public class Runtime { //2.类的内部创建对象 private static Runtime currentRuntime = new Runtime(); //3.开发对外的实例化方法 public static Runtime getRuntime() { return currentRuntime; } //1.私有化构造方法 private Runtime() {} .... }
-
单例注册表
-
spring 中的bean 的单例模式就是通过单例注册表方式实现的
运用Map的key不能重复与的特性,实现单例。
-
详见Spring源码:
-
AbstractBeanFactory中
doGetBean()
// Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
-
DefaultSingletonBeanRegistry中
getSingleton() addSingleton()
方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { //省略... } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } /** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
-
记得要将设计模式与实际业务场景进行结合来体现,对设计模式的理解和应用能力。
工厂模式
-
学习博客:
-
这个是创建不同类型实例常用的方式,例如spring 中的各种bean是由不同的工厂类进行创建的
厚积薄发打卡Day47: [itcast] GoF23设计模式之 <自定义Spring IOC> (上)
BeanFactory解析
Spring中Bean的创建是典型的工厂模式,通过简单工厂模式+配置文件的方式实现这一系列的Bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。
其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,观察其源码:
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; //根据bean的名称获取IOC容器中的的bean对象 Object getBean(String name) throws BeansException; //根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了 <T> T getBean(String name, Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType, Object... args) throws BeansException; <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType); <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType); //判断容器中是否包含指定名称的bean对象 boolean containsBean(String name); //根据bean的名称判断是否是单例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; boolean isPrototype(String name) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String name) throws NoSuchBeanDefinitionException; String[] getAliases(String name); }
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心Bean是如何定义及怎样加载的,以bean的产生结果为导向,正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的,所以后续会有注册类来为 BeanFactory加载bean。
-
Calendar类:
代理模式
-
学习博客:厚积薄发打卡Day36 :[itcast] GoF23通俗易懂的设计模式之 <代理模式>
从火车站卖票的例子对比说明了动态代理和静态代理
-
主要用在不适合或者不能直接引用另一个对象的场景,可以通过代理模式对被代理的对象进行访问行为的控制,Java的代理模式分为静态代理和动态代理
-
静态代理:是指在编译时就已经创建好了代理类
- 例如我们在源代码中编写的类
-
动态代理:只在JVM 运行过程中动态创建的代理类:使用动态代理的方法
-
有JDK动态代理,CGLIB
-
可以看到在
newProxyInstance()
方法中需要由Class<?>[] interfaces
参数,也就是说必须要定义接口,才能对接口进行代理。假如没有接口的情况下,这时就应该使用CGLib来实现动态代理:
CGLIB(Code Generation Library)详解
CGLIB是一个功能强大,高性能的代码生成包。其被广泛应用于AOP框架(Spring、dynaop)中,用以提供方法拦截操作。Hibernate作为一个比较受欢迎的ORM框架,同样使用CGLIB来代理单端(多对一和一对一)关联(延迟提取集合使用的另一种机制)。CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib
-
-
-
代理和装饰者的区别
静态代理和装饰者模式的区别:
- 相同点:
- 都要实现与目标类相同的业务接口
- 在两个类中都要声明目标对象
- 都可以在不修改目标类的前提下增强目标方法
- 不同点:
- 目的不同
- 装饰者是为了增强目标对象
- 静态代理是为了保护和隐藏目标对象
- 获取目标对象构建的地方不同
- 装饰者是由外界传递进来,可以通过构造方法传递
- 静态代理是在代理类内部创建,以此来隐藏目标对象
- 目的不同
- 相同点:
-
面试时遇到这个问题,可以举个动态代理的例子:
-
比如dubbo中,是使用jdk的动态代理,通过反射把远程请求进行封装。使服务看上去就像在使用本地的方法。
-
jdk动态代理在mybatis中的应用,主要通过
MapperProxyFactory
实现,代理模式(以及动态代理在mybatis中的应用)如果使用过Mybatis,我们就会发现Mybatis的使用非常简单,首先定义一个dao接口,然后编写一个与dao接口的对应的配置文件,java对象与数据库字段的映射关系和dao接口对应的sql语句都是以配置的形式写在配置文件中,非常的简单清晰
但是笔者在使用的过程中就曾经有过这样的疑问,dao接口是怎么和mapper文件映射起来的呢?只有一个dao接口又是怎么以对象的形式来实现数据库的读写操作呢?相信有疑问的肯定不止我一个人,当然,在看了上面两节之后,应该很容易猜到可以通过代理模式来动态的创建dao接口的代理对象,并通过这个代理对象来实现数据库的操作。
-
Spring中的AOP实现
-
责任链模式
- 责任链模式有点像工厂的流水线链上每一个节点,完成对对象的某一种处理
- 例如Netty框架在处理消息时使用的pipeline,就是一种责任链模式
适配器模式
-
类似于我们常见的转接头,把两种不匹配的对象来进行适配,也可以起到对两个不同的对象进行解耦的作用
-
例如我们常用的日志处理框架SLF4J,如果我们使用了SLF4J,就可以跟Log4J或者Logback,这种具体的日志实现框架进行解耦
-
通过不同适配器将SLF4J与Log4J等实现框架进行适配,完成日志功能的使用
-
适配器模式在 SpringMVC 中的经典使用体现在它的核心方法 doDispatch 方法中,再来看一个 Spring MVC 中的 HandlerAdapter 类,它也有多个子类,类图如下。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Etw8sTET-1627302709199)(厚积薄发打卡Day74 :【MSUP】Java语言特性与设计模式(上)/5-20092G24T2227.png)]
位于org.springframework.web.servlet包中的DispatcherServlet是servlet接口的实现类,作用是处理请求并返回结果。在servlet容器接收到一个请求时,servlet容器会针对这个请求创建一个servletRequest和servletRespones对象,相应的处理方法会通过servletRequest中携带的参数对应处理请求,再通过servletRespones对象商城请求的响应结果。
DispatcherServlet类的doDispatch方法是处理请求的核心逻辑,截取部分内容如下:
try { //对请求做类型转换 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // 根据请求信息找到相应的Handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 根据handleru想你找相应的HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); }
-
观察者模式
-
也被称作发布订阅模式,适用于一个对象的某个行为,需要触发一系列的事件场景
- 例如GRPC 中stream 流式的请求的处理,就是通过观察者模式实现的。
- 或者举个 redis 例子:Redis 发布订阅
-
博客学习:厚积薄发打卡Day45: [itcast] GoF23通俗易懂的设计模式之 <观察者模式>
-
JDK中的应用:
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable类
-
值得注意的时,void notifyObservers(Object arg) 方法:通常越晚加入集合的观察者越先得到通知:深入查看源码得知时倒序通知。
2,Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
public interface Observer { void update(Observable o, Object arg); }
3,举例应用:
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。
-
警察:
public class Police implements Observer { private String name; public Police(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } @Override public void update(Observable o, Object arg) { System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!"); } }
-
小偷:
public class Thief extends Observable { //小偷名字 private String name; public Thief(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void steal() { System.out.println("小偷:渣渣们,老子来偷东西了!"); super.setChanged(); //changed = true super.notifyObservers(); } }
-
警察抓小偷:
-
-
Spring中的应用:
-
Spring之事件监听(观察者模型)【波波烤鸭.著】
-
Spring中事件监听的结构
-
总结:
- Spring中的事件监听使用的是观察者模式
- 所有事件需要继承ApplicationEvent父类
-
所有的监听器需要实现ApplicationListener接口
- 事件发布需要通过ApplicationContext中的publisherEvent方法实现
- 监听器的注册是ApplicationEventMulticaster提供的,但我们并不需要实现。
-
扩展:
-
-
-
Servlet中的Listener
在说Servlet中的Listener之前,
先说说观察者模式的另一种形态——事件驱动模型。与上面提到的观察者模式的主题角色一样, 事件驱动模型包括事件源, 具体事件, 监听器, 具体监听器。
Servlet中的Listener就是典型的事件驱动模型。
-
构造者模式
- 适用于一个对象,有很多复杂的属性,需要根据不同情况创建不同的具体对象
- 例如创建一个SpringBuilder对象时,可以使用builder 方式
- 建造者模式-各框架应用场景穷举