23种设计模式的具体实现

单例模式

指一个类只有一个实例,且该类能自行创建这个实例的一种模式

spring

将bean的scope设置为Singleton时,则获取spring对象时,就能实现单例模式,获取的都是一个对象

原型模式

用一个已经创建的实例作为原型,通过复制原型对象来创建一个核原型相同或相似的对象

原型模式的难点是clone的实现,虽然Java原生支持Object#clone接口,但要求子类需要实现Cloneable接口,并重写clone方法,由于clone得到的对象必须是完全的独立的,这就要求clone方法需要支持深度拷贝,深度拷贝得考虑类中的可变属性循环依赖,这几乎是难以实现的。网上还流行说用序列化的形式实现clone,这也不靠谱,首先对象序列化存在性能问题,其次在Java中如果要实现标准对象序列化,需要实现Serializable接口,类的属性也要求支持序列化,这个要求就几乎不可能实现,因为有时候我们类的属性是来自第三方系统。

所以Spring并没有使用原型设计模式来实现prototype,只是蹭了个概念。

参考

(6条消息) Spring prototype 是采用原型模式吗?_FanYien的博客-CSDN博客

简单工厂模式

一个创建产品对象的工厂接口,将产品对象的实例创建工作推迟到具体子工厂类当中

也叫静态工厂方法

对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。

工厂方法模式

父类定义了创建对象的接口,但是由子类来具体实现,工厂方法让类把实例化的动作推迟到了子类当中。

BeanFactory 是底层的Ioc容器,ApplicationContext 继承了它,并增加了一些特性。在实现方面,ApplicationContext 组合了一个 BeanFactory 的实现,使用这个实现对象来完成一些操作。

BeanFactory和FactoryBean的区别

BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,

FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式(如果想了解装饰模式参考:修饰者模式(装饰者模式,Decoration)我们可以在getObject()方法中灵活配置。其实在Spring源码中有很多FactoryBean的实现类.

spring的使用

工厂方法模式在Spring底层被广泛的使用,陈某今天举个最常用的例子就是AbstractFactoryBean

这个抽象工厂很熟悉了,这里不再讨论具体的作用。其实现了FactoryBean接口,这个接口中getObject()方法返回真正的Bean实例。

AbstractFactoryBean中的getObject()方法如下:

public final T getObject() throws Exception {
    //单例,从缓存中取,或者暴露一个早期实例解决循环引用
  if (isSingleton()) {
   return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
  }
    //多实例
  else { 
      //调用createInstance
   return createInstance();
  }
 }
  //创建对象
  protected abstract T createInstance() throws Exception;
  • 从以上代码可以看出,创建对象的职责交给了createInstance这个抽象方法,由其子类去定制自己的创建逻辑。

其实与其说AbstractFactoryBean是抽象工厂类,不如说FactoryBean是真正的抽象工厂类,前者只是对后者的一种增强,完成大部分的可复用的逻辑。比如常用的sqlSessionFactoryBean只是简单的实现了FactoryBean,并未继承AbstractFactoryBean,至于结论如何,具体看你从哪方面看了。

抽象工厂模式

是一种为访问类提供一个创建一组相关活互依赖对象的接口,并且访问类无需指定所需要产品的具体类就能得到同组的不同等级的产品的结构模式

spring中的BeanFactory对应的角色问就是抽象工厂,具体工厂有多个,这里列举两个ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。

抽象产品在spring中比较特殊,因为spring中具体的产品的创建最终是由反射实现的,就当class类为抽象产品吧.具体产品就是项目中交由spring管理的各种类

package com.liuyi;
​
import com.liuyi.entity.Person;
import com.liuyi.entity.User;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
​
/**
 * @ClassName Test
 * @description:
 * @author:liuyi
 * @Date:2020/11/10 22:00
 */
public class Test {
    public static void main(String[] args) {
        BeanFactory ctx = new ClassPathXmlApplicationContext("spring.xml");
        User user = (User)ctx.getBean("user");
        System.out.println(user);
        BeanFactory ctx2 = new FileSystemXmlApplicationContext("classpath:spring.xml");
        Person person = (Person)ctx2.getBean("person");
        System.out.println(person);
    }
}

从代码中可以看到获取bean对象都可以使用beanFactory的getBean方法,也就是说客户端不依赖具体的实现,并且我们可以通过不同的具体工厂(产品等级结构)

生成不同的对象(产品族)

建造模式

如何将类或对象按某种布局组成更大的结构

StringBuilder符合建造者模式的思想,但与传统的建造者模式略有不同。

对于String来说,它提供的操作应该是对于字符串本身的一些操作,而不是创建或改变一个字符串。

而对于StringBuilder来说,它种注重的就是建造的过程,比如果append(),insert()方法。

对比String与StringBuilder的提供的方法,可以明显的发现,String的方法只是提供字符串“应该”提供的服务,比如说求字串等等。

StringBuilder则更注重建造一个怎样的字符串,StirngBuilder提供的方法基本都是在改变字符串本身,例如:往某个位置插入某些字符、字符串的逆置等等。

使String的实例成为一个不可变的对象。
如果String提供了例如StringBuilder中的append()方法,String对象就是成为了可变对象。(在学习JavaSE的时候,就知道String的实例是不可变的,本质原因是String没有提供方法,而是将这些方法抽象出来一个StringBuilder)

String不是为了创建字符串。
听着好像有点矛盾,就像上面说的,使用构造方法有时候只是创建一个“外壳”。(我们通常使用的StringBuilder时,就是先new一个“空壳”,然后调用各种方法向这个“壳子”中填数据)

指挥者其实就是程序员,或者说是程序员编写的自定义类

代理模式

由于某些原因需要给对象提供一个代理以控制该对象的访问.这是对象不适合或者不能直接引用目标对象,代理对象作为访问和目标对象之间的中介

Spring中的AOP就是动态代理的实现

可以用作日志,事务,访问统计等场景.

适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作.

DispatcherServlet中的doDispatch方法,是将请求分发到具体的controller,因为存在很多不同类型的controller,常规处理是用大量的if...else...,来判断各种不同类型的controller

如果还需要添加另外的controller,就需要再次添加if...else...,程序就会难以维护,也违反了开闭原则 -- 对扩展开放,对修改关闭。

因此,spring定义了一个适配器接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了。

桥接模式

将抽象与实现分离,使他们可以独立变化

Jdbc 的 Driver 接口,如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver,Oracle 的Driver,这些就可以当做实现接口类

享元模式

用共享技术来有效支持大量细粒度对象的复用.它通过共享已经存在的对象来大幅度减少以需要创建的对象数量,避免大量相似类的开销,从而提高系统的资源利用率

String

String代表不可变的字符序列,而不是基本数据类型,

  • jdk1.6:之前 有永久代,静态变量存放在永久代上,字符串常量池存放在永久代,静态变量也存放在永久代
  • 1.7:有永久带,但逐步去永久代,字符串常量池,静态变量移除,保存在堆中
  • 1.8:之后无永久代,字段方法,常量保存在本地内存的元空间,但字符串常量池静态变量仍在堆

用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。

String类是由final修饰的,当以字面量的形式创建String变量时,JVM会在编译期间就把该字面量"hello"放到字符串常量池中,在Java启动的时候就已经加载到内存中了。这个字符串常量池的特点就是有且只有一份相同的字面量。如果有其他相同的字面量,则JVM返回这个字面量的引用;如果没有相同的字面量,则在字符串常量池中创建这个字面量并返回它的引用。

intern()方法能使一个位于堆中的字符串在运行期间动态地加入字符串常量池(字符串常量池的内容是在程序启动的时候就已经加载好了的)。如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用;否则,复制一份该字面量到字符串常量池并返回它的引用。因此

public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = "he" + "llo";
        String s4 = "hel" + new String("lo");
        String s5 = new String("hello");
        String s6 = s5.intern();
        String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8;
        String s10 = "h" + "ello";
​
        System.out.println(s1 == s2); //true 由于s2指向的字面量"hello"在常量池中已经存在(s1先于s2),于是JVM就返回这个字面量绑定的引用,所以s1==s2。
        System.out.println(s1 == s3); //true s3中字面量的拼接其实就是"hello",JVM在编译期间就已经对它进行了优化,所以s1和s3也是相等的。
        System.out.println(s1 == s4); //false s4中的new String("lo")生成了两个对象:lo和new String("lo")。lo存在于字符串常量池中,new String("lo")存在于堆中,String s4 = "hel" + new String("lo")实质上是两个对象的相加,编译器不会进行优化,相加的结果存在于堆中,而s1存在于字符串常量池中,当然不相等。
        System.out.println(s1 == s5); //false
        System.out.println(s4 == s5); //false s4和s5的结果都在堆中,不用说,肯定不相等。
        System.out.println(s1 == s6); //true s5.intern()方法能使一个位于堆中的字符串在运行期间动态地加入字符串常量池(字符串常量池的内容是在程序启动的时候就已经加载好了的)。如果字符串常量池中有该对象对应的字面量,则返回该字面量在字符串常量池中的引用;否则,复制一份该字面量到字符串常量池并返回它的引用。因此s1==s6输出true。
        System.out.println(s1 == s9); //false 同 s1 == s4
    }

组合模式

将对象组合成树形结构来表示整体/部分的关系,允许用户以相同的方式处理单独对象和组合对象

个人理解:vue的组件树模式就是组合模式的典型

如果将一个.vue文件看做一个类的话,完全符合树形结构以表示 "部分-整体提的层次结构"在操做对象中用户对父级组件也就是这个组合对象操作使用时就能跟操作一个对象一样

模板模式

定义一个操作中的算法骨架,而讲算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤
AQS就是模板模式
Spring只是对事务进行了扩展和封装使用,现在看看在内部它是如何工作的。

PlatformTransactionManager
PlatformTransactionManager是一个接口,它定义的方法如下:

// 获取事务
   TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
   // 提交事务
   void commit(TransactionStatus status) throws TransactionException;
   // 回滚事务
   void rollback(TransactionStatus status) throws TransactionException;
​

看到这个类的定义,基本上对事务的功能有一个初步的掌握,完全就是平时写事务步骤的关键方法啊!

AbstractPlatformTransactionManager实现了platformTransactionManager接口,但是这个抽象类知识定义了一个骨架,抽象出doBegin(),doCommit(),doRollback()这三个抽象方法让子类去实现。

DataSourceTransactionManager是JDBC事务的实现类它继承了AbstractPlatformTransactionManager类,并实现了doBegin(),doCommit(),doRollback()这三个关键的方法。

DataSourceTransactionManager有一个成员是DataSource,在事务配置时,要传这个属性过去。

doBegin() 中有一个关键的代码:con.setAutoCommit(false);
doCommit() 中有一个关键的代码:con.commit();
doRollback() 中有一个关键的代码:con.rollback();
看到这里,有没有很熟悉的感觉呢?!

4. TransactionTemplate
TransactionTemplate是手动事务编程的模板类,它是如何来完成的呢?它有一个重要的方法:execute()方法,它里面定义了事务操作的基本骨架,剔除一些异常处理的代码,看看最关键的代码并作注释。

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
            // 这里主要调用了doBegin()方法
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {
                // 回调子类中的具体实现逻辑,处理业务逻辑
                result = action.doInTransaction(status);
            }catch(Exception e){
                // 调用doRollback()方法
                rollbackOnException(status, ex);
            }
            // 调用doCommit()方法
            this.transactionManager.commit(status);
            return result;
    }

策略模式

定义了一些列算法,并将每个算法封装起来,使他们可以相互替换,切算法的变化不会影响使用算法的客户.

比如自己定义俩种排序算法,客户可以根据不同的需要来调用不同的排序算法,这就是策略模式

命令模式

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分隔开.这样俩者之间通过命令对象进行沟通

类似mysql这样数据库

都是使用命令行与数据库进行交流的,这就是典型的命令模式

职责链模式

为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

其中我们可以看到,在springMVC中,DispatcherServlet这个核心类中使用到了HandlerExecutionChain这个类,他就是责任链模式实行的具体类。在DispatcherServlet的doDispatch这个方法中,我们可以看到它贯穿了整个请求dispatch的流程:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
​
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
​
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
​
            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                // 获取该请求的handler,每个handler实为HandlerExecutionChain,它为一个处理链,负责处理整个请求
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
​
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
​
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 责任链执行预处理方法,实则是将请求交给注册的请求拦截器执行
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 实际的执行逻辑的部分,也就是你加了@RequestMapping注解的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
​
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                // 责任链执行后处理方法,实则是将请求交给注册的请求拦截器执行
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 处理返回的结果,触发责任链上注册的拦截器的AfterCompletion方法,其中也用到了HandlerExecutionChain注册的handler来处理错误结果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            // 触发责任链上注册的拦截器的AfterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

从上面的代码中我们可以看到,HandlerExecutionChain主要负责请求的拦截器的执行和请求的处理,但是他本身不处理请求,只是将请求分配给在链上注册的处理器执行,这是一种责任链的实现方式,减少了责任链本身与处理逻辑之间的耦合的同时,规范了整个处理请求的流程

具体流程

状态模式

对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时的行为

观察者模式

一个对象存在一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

spring中的监听器就是观察者模式的实现

spring中的监听器的使用(注解和非注解的方式) - 随意的世界 (nasuiyile.cn)

中介者模式

定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且独立地改变它们之间的交互

ssm中从控制器调用service层调用模型层再返回给控制器,交给视图层.这就是中介者模式

迭代器模式

提供一个对象来顺序访问聚合对象内的一系列数据,而不暴露聚合对象的内部表示.

java中的每一个Collection的具体实现类都有一个迭代器Iterator

可以获取集合中的迭代器,然后调用hasNext不停取下一个值对集合进行遍历,直到遍历结束

java中的for循环遍历collection的时候,底层其实也是这个方法

备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,一百你以后当需要时能恢复到原先保存的状态

比如在一个java对象使用之前用Serializable将对象序列化,在特定时间可以反序列化对象,获取到之前保存的状态

Last modification:January 6, 2023
如果觉得我的文章对你有用,请随意赞赏