Part1_精讲设计模式_模拟面试题QA

                                         Part1 精讲设计模式

                                                       模拟面试题QA

                                                                                                                                                                                                田超凡

                                                                                                                                                                                           20191115

转载请注明原作者

                                                                                                目录

1策略模式(Strategy)

2责任链模式(ResponsibilityChain)

3工厂模式(Factory)

4模板方法模式(Template)

5装饰者模式(Decorator)

6观察者模式(Observer)

7单例模式(Singleton)

8代理模式(Proxy)

9门面模式(Facade)

10状态模式(State)

11适配器模式(Adapter)

 

1 策略模式(Strategy)

1.1 为什么要使用设计模式

使用设计模式可以对现有的功能代码进行结构和组织层面的重构,也可以用来作为特定业务模块架构设计的基本参考依据,总的目的都是提高代码的可复用性、可扩展性、可维护性、可读性、可伸缩性,减少代码冗余。

 

1.2 什么是策略模式?策略模式应用场景

策略模式是指针对同一个行为使用不同的策略算法实现,解决多重if判断问题。不同的分支实现的策略不同,但是本质都是同一个行为,做的同一件事,只是处理事情的方式不同。

应用场景:订单支付-聚合支付方式的选择

 

1.3 请使用画图工具,画一个策略模式流程图

 

1.4 策略模式实现方式有那些?

使用抽象策略角色(Context Strategy)定义需要实现的共同行为

使用具体策略角色(Strategy)对相同的行为使用不同的策略算法实现。

 

1.5 策略Context上下文与策略的关系

上下文角色(抽象策略角色)定义的是每个策略角色需要实现的共同的行为标准,

具体策略角色用来对抽象策略角色中的行为提供不同的策略算法实现。

 

1.6 策略模式的优缺点有那些

优点:面向接口编程,提高代码可复用性

缺点:在复杂的业务场景中会有多个不同的抽象策略角色实现不同行为,加重代码结构复杂度,使得程序的体积变得更重。

 

2 责任链模式(ResponsibilityChain)

2.1什么是责任链模式

责任链模式实现了请求者和处理者之间的彻底解耦,对于一次请求可以有多个对象按自定义的顺序组装并进行处理(处理对象之间使用类似链表的数据结构来建立引用),可以灵活地增加处理者并加入责任链,并且对于请求者而言整个执行过程是不可见的,提高代码可扩展性,满足开闭原则。

 

2.2责任链模式应用场景

事件流、流程审批、ERP系统、工作流(Activity/JBPM/F2BPM)等

 

2.3责任链的底层实现原理

抽象处理器(Abstract Handle) 定义整个责任链模式中的处理器需要实现的共同行为规范,用来约束责任链模式处理器的实现行为,定义每个处理器对下一个处理器的引用。

具体处理器(Handle)实现每个环节具体业务处理,每个处理器定义下一个处理器的引用,当前处理器处理完毕之后交给下一个处理器继续处理。具体处理器都实现完各自的业务操作后,在工厂中实现类似链表的动态组装来构建责任链。

 

3 工厂模式(Factory)

  1. 什么是工厂模式?工厂模式有哪些实现方式?

使用工厂模式可以创建不同类型的实例,实现实例的创建和调用彻底解耦,对于不同类型实例的创建集中在抽象工厂中实现,实例的调用者只需要获取工厂创建的实例即可。工厂模式对外暴露已经创建好的对象实例,对象的创建封装在工厂内部进行实现,满足开闭原则(对扩展开放,对修改关闭)。

工厂模式的实现方式有很多,比如抽象工厂、静态工厂、工厂方法等。

 

4 模板方法模式(Template)

4.1什么是模版方法设计模式?模版方法设计应用场景有那些

模板方法模式定义了一个公用的算法骨架,彻底实现了代码的复用和灵活扩展解耦。使用模板类定义共用的算法骨架,相同的行为集中在模板类中进行实现,不同的行为延迟到模板类的子类进行实现,最后在模板类中进行动态组装,提高代码的可扩展性。

 

4.2模版方法与策略模式的区别

模板方法模式把相同的行为和通用的算法骨架集中到模板类中实现,不同的行为分散到子类中进行实现,最后在模板类中进行动态组装。模板方法模式相当于为不同的实现行为提供一套可以复用的方法模板,不同行为的实现都可以在此模板的基础上作延伸。

策略模式针对的是同一个行为,只是这个行为的具体实现策略方式不同。

 

4.3模板方法设计模式优缺点有那些?

优点:把共用的部分集中实现,把不同的部分延迟到不同的子类中进行实现,提高代码可复用性和可扩展性。

缺点:过多的模板类会增大结构复杂程度,并且由于具体的模板类需要继承抽象模板类,不同的实现行为都遵循模板类定义的规范,一旦这个规范随着业务的发展需要发生变动,会导致所有继承模板类的具体实现产生大量改动,逐步破坏了模板方法设计模式本身的优势,加大代码的维护难度。

 

5 装饰者模式(Decorator)

5.1什么是装饰模式?装饰模式应用场景

装饰者模式可以在不改变原有功能代码的基础上,使用各类装饰者来对原有功能(被装饰者)功能进行灵活扩展,提高代码可扩展性,比继承的方式更加灵活。

 

5.2请画出装饰模式的流程图

5.3装饰模式与责任链模式区别

装饰者模式是在不改变原有代码的基础上,根据业务场景创建各类装饰者角色对原有代码进行动态扩展,每个装饰者定义上一级装饰者的引用,如果没有上级装饰者则直接定义被装饰者的引用,从而实现功能动态扩展,每一个装饰者都定义的是完全不同的行为功能实现。装饰者都是不同的行为功能实现。

责任链模式针对的是同一个行为,只是同一个行为的处理器不同,严格按照处理器的处理顺序实现对同一个行为按顺序处理,每个处理器定义下一个处理的引用,当前处理器处理完之后交给下一个处理器继续处理,直到执行完整个责任链。

5.4装饰模式底层实现原理

装饰者模式有4种角色组成:

抽象被装饰者(Abstract Decorated):定义需要动态扩展功能的原功能实现规范

被装饰者(Decorated):继承抽象被装饰者,需要动态扩展功能的原功能具体实现

抽象装饰者(Abstract Decorator):继承抽象被装饰者,定义需要扩展的功能规范

装饰者(Decorator):扩展功能的具体实现

装饰者和被装饰者通过继承方式定义相同的行为,然后在被装饰者方法实现的基础上进行动态功能扩展,装饰者和被装饰者的实现彻底解耦(作为被装饰者而言,原功能方法不被破坏,对于装饰者而言,装饰者调用被装饰者方法不关心内部具体实现),对外只表现的是装饰者对被装饰者的二次包装和功能扩展,内部的具体实现封装在装饰者和被装饰者内部进行具体实现,满足开闭原则(对扩展开放,对修改关闭)。通过抽象角色来对具体角色的实现进行统一规范和约束,使得具体的被装饰者和装饰者也可以动态进行扩展,提高代码的可扩展性。

 

6 观察者模式(Observer)

6.1 什么是观察者模式? 观察者模式如何实现?

观察者模式就是在对象之间建立一对多的依赖关系,这样当一个对象信息发生变化时,其他所有关联它的对象都会接收到消息并实时同步和更新数据。

观察者模式也可以理解为消息发布订阅模式,主题角色发布消息后,所有订阅主题角色的观察者角色都会收到发布的消息并实时更新和同步数据。

观察者模式主要有4种角色组成:

抽象主题角色(Abstract Subject):定义所有具体主题角色需要实现的行为规范。

具体主题角色(Subject):对抽象主题角色中的规范进行具体实现,管理所有订阅主题的观察者,实现观察者订阅和取消订阅,当内部消息发生变化时通知所有观察者。

抽象观察者角色(Abstract Observer):定义具体观察者角色需要实现的行为规范。

具体观察者角色(Observer):订阅主题角色,接收主题角色发布的消息并进行相应的数据同步和业务处理。

 

6.2 观察者模式应用场景有那些

观察者模式实现方式有三种:

  1. .手动创建各类观察者模式角色实现观察者模式
  2. .基于JDK原生观察者模式API实现观察者模式
  3. .基于Spring监听器实现观察者模式

观察者模式应用场景:Zookeeper节点事件通知、MQ消息队列、Spring监听器、SpringCloud Bus消息总线等等

 

6.3 JDK中实现观察者模式 Observable 与Observer区别

(1).自定义被观察者角色(主题角色)需要继承Observable,Observable是一个默认的主题角色实现,可以直接重写notifyObservsers(Object msg)方法通知所有订阅主题的观察者,通过setChanged()修改发布状态并开启消息推送。

在Observable中,已经提供了一个Vector<Observer>存放观察者,默认提供了addObserver()、removeObserver()等方法来实现观察者订阅和取消订阅。

  1. .自定义观察者角色需要实现Observer接口,重写updateState(Observable observable,Object msg)接收订阅的主题角色发布的消息并进行进一步处理。

 

6.4 观察者模式底层的实现原理

观察者模式底层实现方式是在每个主题角色中定义容器来存放所有订阅该主题的观察者,在观察者中暴露方法接收主题角色发布的消息,通过在主题角色中定义订阅、取消订阅、消息推送方法实现事件监听和数据同步。当主题角色中的内容发生改变时会通知所有观察者,观察者就可以接收到主题角色发布的消息并在各自内部进行相应处理,观察者模式最大的特点就是实现了消息推送和数据同步,通过在对象之间建立一对多的关联,实现了对象之间的级联通信,提高了代码的可复用性和可扩展性,实现对象和对象之间的实时交互。

 

6.6 Spring监听器如何实现观察者模式的?

Spring监听器是基于内部的事件驱动处理机制实现观察者模式的。

自定义的主题角色继承ApplicationEvent类,通过构造函数注入需要发布的消息。自定义的观察者角色实现ApplicationListener接口,重写onApplicationEvent方法,当使用Spring上下文触发主题角色对应的事件时,所有实现ApplicationListener接口并且泛型类型和主题角色类型匹配的观察者都会实时监听到主题角色发布的消息。

 

7 单例模式(Singleton)

7.1 什么是单例模式

单例模式指的是在应用程序运行期间,同一个类型的实例有且只有一个(一个JVM中同一个类型的实例只有一个)。

单例模式的特点:

  1. .单例类有且仅有一一个实例
  2. .单例类必须自己创建自己实例
  3. .单例类必须允许其他对象访问这个实例

 

7.2 单例模式有那些创建方式

单例模式常用的实现方式有:

  1. .懒汉模式(线程不安全)
  2. .懒汉模式(线程安全)
  3. .饿汉模式
  4. .静态内部类
  5. .双重校验锁
  6. .枚举
  7. .容器类加载(使用容器类管理不同单例类的实例)

注意:单例类内部必须使用私有构造函数防止外部创建单例实例,私有构造函数是单例模式的最有效体现和最根本特征。

 

7.3 饿汉式与懒汉式区别

懒汉模式指的是类创建时不初始化单例实例,在需要获取单例实例的时候初始化单例实例,类加载速度快,获取对象速度慢,线程不安全。

饿汉模式指的是类初始化的时候就创建单例实例,类加载速度慢,获取对象速度快,线程安全,启动效率低。

 

7.4 双重检验锁两个if判断作用

双重校验锁可以保证在多线程并发获取单例类实例的时候不会破坏单例模式,双重校验锁通过两个判断和上锁来进一步确保懒汉模式创建单例的线程安全性问题,最外层if是判断读取的时候单例类实例是否已经创建,如果没有创建,则使用synchronized上锁,在锁的内部会进行第二次if判断,因为有可能出现一个线程抢到锁但是还没有读取实例就过早释放,另一个线程此时立即抢到了锁并写入实例的情况,这种情况发生的可能性很小,但是在高并发的访问情况下应该绝对避免这种问题破坏单例,此时内部锁的if判断表示单例此时如果还是没有创建则直接创建单例实例。双重校验锁的实现过程一句话概括就是,单例读不加锁,读是线程安全的,单例写要加锁。

 

7.5 静态内部类方式与双重检验锁的区别

静态内部类集懒汉模式和饿汉模式的优点于一身,静态内部类确保了线程安全性,同时实现了延迟加载。

双重校验锁只是对线程不安全的懒汉模式进行的一次包装,使得懒汉模式在线程高并发访问下可以运行更加稳定,防止单例模式被破坏。

 

7.6 如何破坏一个单例

可以使用反射和序列化破坏单例

反射显式获取单例类的构造函数并修改可见性,重复创建实例

序列化把单例实例写入磁盘文件,读取的时候再进行反序列化,反序列化后得到的是新的实例

防止单例模式被破坏,推荐使用枚举实现单例,枚举具有先天的单例安全性,可以防止反射和序列化破坏单例,不需要我们显式指定拦截规则,JDK反射工具类已经帮我们拦截了。

防止反射破坏单例模式,还可以在单例类私有构造函数加入判断,如果单例已经创建则直接抛出异常,不允许重复创建单例实例

防止序列化破坏单例模式,可以在单例类中新增一个readResolve方法,强制返回当前单例类的单例实例,防止反序列化的时候破坏单例模式。

 

7.7 Java序列化的作用有那些

Java序列机制分为两种:

(1)对象持久化序列化模式,通过JSON、字节码等技术把对象序列化为特定数据传输格式并保存到磁盘或者数据库等物理存储设备,反序列化的时候读取磁盘或数据库中的数据并转换为对象,对象持久化序列化模式用来实现对象数据的持久化。

 

(2)网络传输序列化模式:在TCP网络通信机制中,客户端把对象转换为字节的过程称作为序列化,服务器把字节转换为对象的过程称为反序列化,因为TCP传输协议默认传输数据格式都是字节,所以可以使用网络传输序列化模式定义一种数据传输规范来实现数据传输。

 

7.8枚举单例底层是如何实现的?(源码解析流程图)

枚举编译后实际上生成的还是类,继承了Enum枚举基类,枚举在static静态代码块中实现了枚举成员的初始化,枚举没有无参构造函数,只定义了一个带参构造函数。

 

7.9为什么反射无法初始化枚举对象

因为在Java反射工具类中初始化实例的时候进行了判断,如果是枚举类型的话会直接抛出异常,根本都不会调用枚举的构造函数。

 

8 代理模式(Proxy)

8.1 为什么需要使用代理模式,应用场景

代理模式实现了中介隔离,把代理实例和目标实例实现完全隔离开来,这样在使用代理实例调用目标实例的方法时就可以动态加入新的功能,进行增强处理。

目标实例方法的具体实现封装在目标实例对象所在类的内部,对于代理实例而言,只关心目标实例方法的调用和织入横切逻辑,不关心目标实例方法内部的具体实现,满足开闭原则(对扩展开放,对修改关闭)

 

8.2 代理模式实现的方式有那些

代理模式的实现方式分为静态代理和动态代理,动态代理针对代理目标类型的不同,实现方式又包括JDK动态代理和CGLIB动态代理

 

8.3 静态代理与动态代理区别

手动创建的代理类并指定代理织入规则就是静态代理

程序运行时根据指定的规则动态生成的代理类就是动态代理

 

8.4 动态代理实现方式有那些

JDK动态代理和CGLIB动态代理

 

8.5 JDK动态代理与CGLIB动态代理区别

JDK动态代理,基于反射创建代理目标的代理实例,动态生成JDK动态代理类并基于JavaCompile动态编译,反射获取JDK动态代理类的实例。

JDK动态代理代理目标只能是接口

CGLIB动态代理,基于ASM字节码处理框架实现动态生成代理类的class字节码文件实现动态代理。

CGLIB动态代理代理目标可以是任意Java类

8.6 纯手写JDK动态代理的思路

(1).创建JDK动态代理的目标接口和目标接口实现类

(2).创建自定义类加载器,根据动态编译后生成的class文件加载JDK动态代理类到类加载器

(3).创建自定义JDK动态代理生成类,基于反射动态生成代理目标接口的JDK动态代理类,基于JavaCompile动态编译技术编译动态代理类,最后调用类加载器把代理类加载到类加载器中,基于反射获取JDK动态代理类实例

(4).定义JDK动态代理核心处理器,实现InvocationHandle并重新invoke方法,在invoke方法中实现代理目标方法和代理方法的组合以及增强处理的织入。

定义创建JDK动态代理实例的方法,调用JDK动态代理生成类提供的方法创建JDK动态代理实例。

  1. .初始化JDK动态代理处理器,基于反射创建JDK动态代理实例,调用代理方法实现增强处理。

 

8.7实现扫包范围下,生成代理类

(1).基于递归算法实现类加载器,扫描指定包名下的所有需要代理的目标类

(2).批量基于反射创建动态代理类

(3).定义通用的动态代理处理器实现动态代理,定义代理方法执行主体和各类增强处理方法

(4).批量获取动态代理实例,调用代理方法实现动态代理

 

9 门面模式(Facade)

9.1什么是门面模式?门面模式的应用场景?

门面模式用来简化子系统的调用流程,通过抽取一个公用的门面角色来实现子系统的灵活调用,对于客户端而言只需要调用门面角色封装好的处理方法即可实现一次请求批量调用多个子系统,并且子系统的具体调用逻辑实现对于客户端调用是完全隔离的,具体的子系统调用内部实现已经封装到了门面角色中,门面角色只会把子系统调用处理结果返回给客户端,满足开闭原则(对扩展开放,对修改关闭),简化客户端对子系统的调用流程。

 

10 状态模式(State)

10.1 什么是状态模式,状态模式与策略模式的区别

状态模式适用于同一个对象的不同状态需要实现不同行为的情况,可以用来解决不同状态的多重if判断问题,每一个分支表示一个不同的状态需要执行的操作,不同状态之间需要实现的行为完全不同。

状态模式和策略模式的区别:

状态模式:对象的不同状态需要实现完全不同的行为,每一个分支都是一个不同的状态,不同状态需要做不同的事,每个分支的目标不同。

策略模式:对于同一个行为使用不同的策略算法实现,每一个分支都是一个不同的实现策略算法,但是整体是一个相同的行为,表示的是做同一件事,但是做这件事的实现方式不同,每个分支的目标相同,只是实现方式不同。

简单来说,状态模式每个分支做的都是不同的事,策略模式做的都是同一件事,只是每个分支使用不同的行为策略实现这个目标。

 

11 适配器模式(Adapter)

11.1 什么是适配器模式?适配器模式原理

适配器模式用来解决项目新老版本接口兼容问题,可以在老版本和新版本之间使用适配器充当媒介,兼容旧版本和新版本,在尽可能保证旧版本功能实现不变的前提下,兼容新版本加入的新功能,实现可插拔式的功能扩展。

适配器模式中有3个主要角色:

  1. .适配器源(Adapter Source):对应的是需要适配的旧版本,需要扩展功能的来源。
  2. .适配器(Adapter):在适配器源和适配器目标中充当媒介,兼容适配器源和适配器目标的功能,在保证适配器源功能尽可能不变的前提下,兼容适配器目标的功能。
  3. .适配器目标(Adapter Target):对应的是扩展功能后的新版本,实现扩展功能。

 

11.2 适配器的应用场景

MyBatis三级缓存和日志系统、Spring拦截器和处理器、数据源适配器、SpringBoot/SpringCloud整合第三方组件、新老项目迭代和版本升级。

 

11.4 MyBatis日志系统源码如何基于适配器模式实现的?

MyBatis默认内置的日志系统实现了MyBatis运行时的日志收集,随着MyBatis框架的发展壮大,MyBatis开始兼容越来越多的第三方日志框架,如log4j、logback、slf4j等等,MyBatis兼容内置的日志和第三方日志框架底层实现原理其实就是基于适配器模式实现的。

MyBatis把内置的Log作为适配器源,把各种需要兼容的第三方日志框架作为适配器目标,使用一个内置的Logger作为适配器,使用工厂模式创建各类需要兼容的第三方日志框架核心处理器,在自带的Log框架和第三方需要整合的日志框架之间建立兼容适配关系,使得MyBatis得以兼容越来越多的第三方日志框架。MyBatis日志系统适配器的实现方式采用的是对象适配器模式,日志适配器类实现了适配器目标(各类第三方日志接口),构造柱入自带日志Log的处理器实例(构造柱入适配器源),然后在适配器类中定义了各类适配方法适配第三方日志系统。由于使用的是适配器模式,提高了MyBatis日志系统兼容的可扩展性,可以随着第三方日志的迭代和版本升级兼容更多的日志系统,并且根本不会关心和影响到第三方日志系统内部的具体实现,满足开闭原则(对兼容适配器目标开放,对修改适配器源关闭)。

 

11.5 适配器模式和装饰者模式有什么区别?

适配器模式用来解决项目新老版本接口兼容问题,可以实现不同服务接口之间的功能整合,就适配器模式的作用目标点而言,建立适配器源和适配器目标之间的兼容关系,适配器源和适配器目标在大多数场景中都对应的是多人协同开发中完全不同的服务或子项目,因此适配器模式更加适用于兼容不同的服务或者子系统,使用组合的形式来实现功能适配和兼容。

装饰者模式是保证原功能实现不变的前提下,随着业务场景的不同创建各类装饰者对被装饰者中的功能进行动态扩展,装饰者模式的作用目标点-被装饰者和装饰者对应的一般是同一个子系统、服务、模块中的功能扩展和升级,因为装饰者模式功能的扩展是基于被装饰者已经存在的功能实现的,并且装饰者和被装饰者实现功能非常相似,可以理解为装饰者是对被装饰者的补充扩展。正是基于这个原因,我们一般会把装饰者模式应用在同一个服务或模块中的功能扩展和升级。

综上所述,适配器模式适用于不同子系统、服务之间的新老接口兼容和扩展升级,装饰者模式适用于同一个子系统、服务或模块中的新老接口兼容和扩展和升级。

 

转载请注明原作者

 

 

 

 

 

 

发布了100 篇原创文章 · 获赞 10 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_30056341/article/details/103078100
今日推荐