设计模式在面试中的高频考点
这块在面试中一般是以场景的形式提问,比如问你的项目里有用到设计模式吗?或者是结合Spring问你知道Spring中常见的设计模式吗?或者其他的考点中涉及到设计模式相关的,面试官都有可能问的,所以这块在面试中相对是比较灵活的。
1、你了解的设计模式有哪些?
回答:总的设计模式有23种,可以分为三大类。(建议在面试的时候说几个自己熟悉的,比如单例模式、工厂模式、模板模式等)
创建型模式(共五种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式(共七种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式(共十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
在问Spring的时候可能会问到这样一个问题:在Spring框架中都用到了哪些设计模式,并举例说明?
- 工厂设计模式 : Spring使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 - 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式: Spring 中的 Bean 默认都是单例的。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配
Controller
。
2、设计模式的原则有哪些?
回答:设计模式共有六大原则,分别是:单一职责原则、开闭原则、里氏代换原则、依赖倒转原则、接口隔离原则、迪米特法则。
单一职责:一个类只负责一个功能领域中相应的职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
开闭原则:软件实体应该对扩展开放,对修改关闭。其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
里氏代换原则:通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
依赖倒转原则:这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。
接口隔离原则:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。所以上文中多次出现:降低依赖,降低耦合。
迪米特法则:为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。
3、你比较了解哪些设计模式?
回答:这里大家一定要去找几个设计模式看看,比如单例、工厂和模板这些,这里就以单例模式给大家分享一下。
追问:写一下单例模式的代码?
懒汉式-线程不安全
public class Singleton { private static Singleton Instance; private Singleton() { } public static Singleton GetInstance() { if (Instance == null) { Instance = new Singleton(); } return Instance; } } 优缺点: 私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。 线程不安全,多线程情况下会多次创建实例。
饿汉式-线程安全
public class Singleton { private static Singleton Instance = new Singleton(); private Singleton() { } public static Singleton GetInstance() { return Instance; } } 优缺点: 采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。 但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
懒汉式-线程安全
private static Singleton Instance; public static synchronized Singleton getInstance() { if (Instance == null) { Instance = new Singleton(); } return Instance; }
双重校验锁-线程安全
public class Singleton { private volatile static Singleton Instance; private Singleton() { } public static Singleton getInstance() { if (Instance == null) {//先判断实例是否存在,不存在再加锁 synchronized (Singleton.class) {//由于Instace实例有没有被创建过实例不知道,只能对其clss加锁 if (Instance == null) {//当多个线程的时候,只有一个进入,避免多次创建对象! Instance = new Singleton(); } } } return Instance; } }
追问:这里面试官可能会问使用volatile的好处?
Instance 采用 volatile 关键字修饰也是很有必要的, Instance = new Singleton();
这段代码其实是分为三步执行:
- 为 uniqueInstance 分配内存空间
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 geteInstance() 后发现 Instance 不为空,因此返回 Instance,但此时 Instance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
推荐阅读
#高频知识点汇总##学习路径#