当前位置: 首页 > news >正文

设计模式相关面试题

写在前面

🔥我把后端Java面试题做了一个汇总,有兴趣大家可以看看!这里👉

⭐️在反复复习面试题时,我发现不同资料的解释五花八门,容易造成概念混淆。尤其是很多总结性的文章和视频,要么冗长难记,要么过于简略,导致关键知识点含糊不清。

⭐️为了系统梳理知识,我决定撰写一份面试指南,不只是简单汇总,而是融入个人理解,层层拆解复杂概念,构建完整的知识体系。我希望它不仅帮助自己更自信地应对面试,也能为同行提供清晰、实用的参考。


设计模式相关面试题

面试官:单例设计模式

单例设计模式是一种创建型设计模式,核心思想是保证一个类在整个应用中只有一个实例,并提供一个全局的访问入口。它常用于管理共享资源,比如配置、缓存或数据库连接等场景。接下来,我将详细介绍单例模式的实现过程,以常用的懒汉式加双重检查锁定为例说明其设计原理:

首先,为了避免外部直接创建对象,我们将该类的构造方法设为私有。这一步确保了实例只能在类内部被创建,从而杜绝了通过 new 关键字产生多个实例的可能性。

其次,在类内部定义一个私有的静态变量,该变量用于存储单例实例。初始状态下,它的值为 null,这意味着实例尚未被创建。

然后,我们提供一个公共的静态方法来获取该实例。该方法首先判断静态变量是否为 null,如果不为 null,则说明实例已存在,直接返回即可;如果为 null,则进入同步代码块。在同步块内,再次检查实例是否为 null,若仍为 null,则创建该实例。通过双重检查锁定的方式,我们既保证了多线程环境下的线程安全,又避免了每次访问实例时都进行同步操作带来的性能开销。

最后,当实例创建完成后,后续调用获取实例的方法时,都会直接返回该唯一的实例。这样不仅保证了全局唯一性,也提高了系统性能,因为同步操作只在第一次创建实例时执行。

(1)饿汉式(静态常量)
public class Singleton {private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static Singleton getInstance() {return INSTANCE;}
}

特点:类加载时立即初始化,线程安全。

优点:实现简单,无并发问题。

缺点:即使未使用也会占用资源,可能造成浪费。

(2)懒汉式(线程安全)
public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

特点:首次调用时初始化,通过synchronized保证线程安全。

缺点:每次调用getInstance()都会同步,性能较低。

(3)双重检查锁(Double-Check Locking)
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}

关键点:通过两次判空减少同步开销,volatile防止指令重排。

适用场景:高并发环境下的延迟初始化。

1. 为什么使用 private 修饰构造器?

  • 目的:防止外部直接创建对象。
  • 原因:单例模式要求整个程序中只能有一个实例,使用 private 修饰构造器可以确保对象只能在类内部创建。

2. 为什么 getInstance() 方法要写成 static 的?不写成 static 的会怎样?

  • 原因
    • getInstance() 方法负责提供唯一的实例,而类的静态方法可以在没有对象实例的情况下直接调用。
    • 如果不写成 static,调用 getInstance() 就需要先创建对象实例,这违背了单例模式的初衷。
  • 不写成 static 的后果
    • 需要实例化对象后才能调用该方法,而单例模式要求在首次调用时创建实例。
    • 无法确保对象是唯一的。
(4)静态内部类
public class Singleton {private Singleton() {}private static class Holder {static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return Holder.INSTANCE;}
}

优势:利用类加载机制保证线程安全,同时实现延迟初始化。

推荐场景:兼顾性能与安全性的通用方案。

(5)枚举单例
public enum Singleton {INSTANCE;// 添加业务方法
}

特点:天然线程安全,支持序列化,防止反射攻击。

最佳实践:Effective Java推荐的实现方式。


面试官:工厂设计模式

工厂设计模式是一种创建型设计模式,它主要用于封装对象的创建过程,从而使客户端不需要直接实例化具体的类,而是通过工厂来获取对象。这种模式提高了代码的灵活性和扩展性。接下来我会详细讲述工厂设计模式的实现过程。

当我们需要创建一个产品对象时,

首先,我们会定义一个抽象的产品接口或者抽象类,明确规定产品的公共行为和属性。这样,无论后续添加多少具体产品,客户端都可以通过同一接口来操作它们。

其次,我们实现具体的产品类,这些类分别实现了抽象产品接口,包含各自独特的业务逻辑和功能。

接着,我们定义一个工厂接口或者抽象工厂类,声明一个创建产品对象的方法。该方法的职责是隐藏具体产品对象的实例化过程,客户端只需要调用这个方法即可获得产品实例。

然后,我们实现具体的工厂类,它们根据传入的参数或内部逻辑,决定创建哪一种具体的产品对象。这样,具体产品的创建细节完全被封装在工厂内部,客户端无需关心对象的创建过程。

最后,当客户端需要一个产品时,它只需调用工厂提供的创建方法,获得对应的产品对象,并直接使用。这种方式不仅降低了客户端与具体产品实现之间的耦合,也方便了系统的扩展和维护。

(1)简单工厂模式(Simple Factory)

简单工厂模式并不是一种正式的设计模式,但它是最基础的形式。它通过一个工厂类根据传入的参数决定创建哪种具体产品。

public class SimpleFactory {public static Product createProduct(String type) {if ("A".equals(type)) {return new ConcreteProductA();} else if ("B".equals(type)) {return new ConcreteProductB();}throw new IllegalArgumentException("Unknown type");}
}// 抽象产品
public interface Product {void use();
}// 具体产品A
public class ConcreteProductA implements Product {@Overridepublic void use() {System.out.println("Using Product A");}
}// 具体产品B
public class ConcreteProductB implements Product {@Overridepublic void use() {System.out.println("Using Product B");}
}

特点:集中管理对象创建逻辑,易于理解和实现。

缺点:违反开闭原则,新增产品时需修改工厂类。

(2)工厂方法模式(Factory Method)

工厂方法模式通过定义一个创建对象的接口,让子类决定实例化哪个类。它将对象的创建延迟到子类中完成。

// 抽象工厂
public abstract class Factory {public abstract Product createProduct();
}// 具体工厂A
public class ConcreteFactoryA extends Factory {@Overridepublic Product createProduct() {return new ConcreteProductA();}
}// 具体工厂B
public class ConcreteFactoryB extends Factory {@Overridepublic Product createProduct() {return new ConcreteProductB();}
}// 抽象产品
public interface Product {void use();
}// 具体产品A
public class ConcreteProductA implements Product {@Overridepublic void use() {System.out.println("Using Product A");}
}// 具体产品B
public class ConcreteProductB implements Product {@Overridepublic void use() {System.out.println("Using Product B");}
}

特点:符合开闭原则,新增产品只需添加新的工厂和产品类。

缺点:每新增一个产品都需要增加对应的工厂类。

(3)抽象工厂模式(Abstract Factory)

抽象工厂模式提供了一组相关或依赖对象的创建接口,而无需指定它们的具体类。适用于需要创建一系列产品的场景。

// 抽象工厂
public interface AbstractFactory {ProductA createProductA();ProductB createProductB();
}// 具体工厂1
public class ConcreteFactory1 implements AbstractFactory {@Overridepublic ProductA createProductA() {return new ConcreteProductA1();}@Overridepublic ProductB createProductB() {return new ConcreteProductB1();}
}// 具体工厂2
public class ConcreteFactory2 implements AbstractFactory {@Overridepublic ProductA createProductA() {return new ConcreteProductA2();}@Overridepublic ProductB createProductB() {return new ConcreteProductB2();}
}// 抽象产品A
public interface ProductA {void use();
}// 具体产品A1
public class ConcreteProductA1 implements ProductA {@Overridepublic void use() {System.out.println("Using Product A1");}
}// 抽象产品B
public interface ProductB {void use();
}// 具体产品B1
public class ConcreteProductB1 implements ProductB {@Overridepublic void use() {System.out.println("Using Product B1");}
}

特点:支持创建一组相关的产品,适合复杂系统。

缺点:增加了系统的复杂性,可能需要大量的工厂和产品类。


面试官:生产者消费者设计模式

生产者消费者设计模式是一种用于多线程环境下协调数据生产与消费的并发设计模式,通过引入一个共享缓冲区(如阻塞队列)实现生产者与消费者之间的解耦,从而提升系统的整体性能和资源利用率。接下来,我会讲述其生产与消费的详细过程。

当生产者生成数据时,

首先,生产者根据业务逻辑产生数据,并调用缓冲区提供的接口将数据放入队列中;如果缓冲区已满,生产者会被阻塞,等待空闲空间出现。

其次,消费者不断地从缓冲区中获取数据进行处理;当缓冲区为空时,消费者同样会进入等待状态,直至有新数据被生产者放入。

然后,通过内部的同步机制(如锁、信号量或者直接使用阻塞队列),确保在并发环境下生产者和消费者对共享数据的访问是安全的,不会出现数据竞争或丢失。

最后,随着数据不断地被生产和消费,整个系统实现了生产与消费的有效解耦,不仅提升了并发处理能力,还能灵活应对生产速度和消费速度不匹配的情况,从而保证系统在高并发场景下稳定运行。

(1)基于 BlockingQueue 的实现

Java 提供了线程安全的阻塞队列(如 LinkedBlockingQueue),可以简化生产者-消费者的实现。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class ProducerConsumerWithBlockingQueue {private static final int MAX_SIZE = 5;private final BlockingQueue<Integer> buffer = new LinkedBlockingQueue<>(MAX_SIZE);public void produce() throws InterruptedException {int value = 0;while (true) {buffer.put(value); // 如果缓冲区满,自动阻塞System.out.println("Produced: " + value);value++;Thread.sleep(1000); // 模拟生产耗时}}public void consume() throws InterruptedException {while (true) {int value = buffer.take(); // 如果缓冲区空,自动阻塞System.out.println("Consumed: " + value);Thread.sleep(1500); // 模拟消费耗时}}public static void main(String[] args) {ProducerConsumerWithBlockingQueue pc = new ProducerConsumerWithBlockingQueue();Thread producerThread = new Thread(() -> {try {pc.produce();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});Thread consumerThread = new Thread(() -> {try {pc.consume();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producerThread.start();consumerThread.start();}
}

特点:BlockingQueue 内部实现了同步机制,代码更简洁。

优点:减少了手动管理锁和条件变量的复杂性,性能更高。

http://www.dtcms.com/a/355131.html

相关文章:

  • 一个基于物理信息神经网络(Physics-Informed Neural Network, PINN)的多变量时间序列预测模型MATLAB代码
  • 消息队列核心问题解决方案:从丢失到重复消费的全方位保障
  • 力扣(LeetCode) ——965. 单值二叉树(C语言)
  • 化肥行业磷石膏粉尘专项环保解决方案​——从污染治理到资源循环的全流程突破
  • static 作用一:修饰全局变量
  • [高并发系统设计] - 搭建高并发高可用的系统 - 学习与探究
  • 美图设计室-AI帮你做设计
  • Windows系统安装stata软件教程
  • 【高等数学】第十章 重积分——第三节 三重积分
  • 如何在API高并发中玩转资源隔离与限流策略?
  • 为什么选择 TDengine?
  • nginx的诞生背景、核心优势、与 Apache 的对比
  • 测试设备:高效维修问题的思维模式与日常提升指南
  • STM32——PWR
  • GitHub宕机处理
  • 向宋江学管理
  • Node.js的特性
  • 【动态规划】简单多状态 dp 问题
  • Day16_【机器学习概述】
  • Windows 7-11通用,这工具让电脑提速300%
  • 【Word】用 Python 轻松实现 Word 文档对比并生成可视化 HTML 报告
  • 《Rust 深度解析:核心概念、产业应用与生态全景报告》
  • 沙箱操作指南
  • 系统设计原则
  • 【Selenium】UI自动化测试框架设计:从项目结构到Base-Page层的最佳实践
  • 1-python 自定义模板导出文档-基础实现
  • 【cursor】提示词示范
  • 瑞芯微RK3568与君正X2600e平台Linux系统CS创世SD NAND应用全解析与驱动架构详解
  • springboot Apache PDFBox 通过路径参数读取文件信息流,并转化为pdf 在网页预览
  • 云计算学习100天-第31天