必备设计模式(代码中使用率超级高)
基本八股
23 种设计模式分为哪三大类
创建型模式(Creational Patterns)
用于创建对象的模式,避免代码中出现大量 new 操作和复杂的创建逻辑,同时隐藏对象创建的逻辑,它的目的是解耦对象的创建和使用。
-
单例模式:数据库连接池、线程池。
-
工厂方法模式:日志记录器。
-
建造者模式:复杂对象的分步构建,如 HTML 文档生成器。
结构型模式(Structural Patterns)
用于处理对象组合的结构,关注类与对象的组合。目的是通过组合对象或类的方式,形成更大的结构。
-
适配器模式: 旧系统的兼容升级。
-
装饰器模式: 动态为对象增加功能,如 Java IO 流。
-
代理模式: 实现访问控制,如 RPC 调用。
行为型模式(Behavioral Patterns)
用于定义对象如何协作以完成单个对象无法单独实现的任务,目的是定义类和对象间的通信方式。
-
观察者模式: 事件驱动系统,如订阅-发布系统。
-
责任链模式: 日志级别过滤、请求处理链。
-
策略模式:支付方式选择(如微信、支付吧、信号卡)。
什么是单例模式
类的实例在整个程序生命周期内只存在一个,它提供一个全局访问点,让所有代码可以访问同一个实例,它可以延迟实例化,在需要时才创建实例。
控制对象的实例化,防止创建多个实例,从而节省资源并保证行为一致性。
常见使用场景
-
日志记录器:在多线程或分布式环境中确保日志记录器唯一性。
-
全局配置:用于保存程序的配置信息,所有模块都可以访问和使用同一个配置实例。
-
资源管理:需要共享的资源如数据库连接池、线程池等,确保只有一个实例管理这些资源。
public class Logger {
private static final Logger instance = new Logger();
private Logger() {}
public static Logger getInstance() {
return instance;
}
public void log(String message) {
System.out.println("Log: " + message);
}
}
Logger logger = Logger.getInstance();
logger.log("This is a mianshiya log message.");
单例模式有哪几种实现?如何保证线程安全
-
饿汉式:实例在类加载时就创建,线程安全,但如果线程初始化较重或没有被使用会浪费资源。
-
懒汉式: 在首次访问时创建,节约资源,但需要确保线程安全。
-
枚举单例(Java 特有):通过枚举实现单例,简单且防止反射和序列化攻击。
-
静态内部类:利用类加载机制实现懒加载和线程安全,推荐使用。
-
双重检查锁定:在懒汉式的基础优化,直接加锁效率太低,双重检查锁只在第一次检查实例为空时加锁,提高性能。
饿汉式(线程安全,类加载时初始化)
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
懒汉式(线程不安全,需改进)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双重检查锁定(线程安全,推荐):
public class Singleton {
private static volatile Singleton instance; // 使用 volatile 防止指令重排
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查:避免不必要的同步
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查:确保实例唯一
instance = new Singleton();
}
}
}
return instance;
}
}
为什么双重检查锁定需要 volatile 关键字
在 Java 中,volatile 修饰符用于防止指令重排序,从而确保双重检查锁定的正确性。
instance = new Singleton() 是一个非原子操作,它分为以下三步:
-
分配内存空间。
-
初始化对象。
-
将对象的引用赋值给 instance 。
在没有 volatile 的情况下,编译器和 CPU 可能会对这些步骤进行重排序(比如执行顺序变成1 →3 → 2)。此时,另一个线程可能会在instance 被赋值后,但对象尚未完成初始化时访问它,从而导致错误。所以将 instance 声明为 volatile,可以禁止指令重排序,确保对象的初始化过程对所有线程可见。
枚举单例
public enum Singleton {
INSTANCE;
public void bizMethod() {
// 一些业务逻辑方法
}
}
//使用
Singleton singleton = Singleton.INSTANCE;
singleton.bizMethod();
静态内部类(线程安全,推荐):
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
工厂模式和抽象工厂模式有什么区别
工厂模式
关注的是创建单一类型对象,定义一个抽象方法,由子类实现具体对象的实例化。
工厂方法模式定义了一个创建对象的抽象方法,一个具体的工厂类负责生产一种产品,如果需要添加新的产品,仅需新增对应的具体工厂类而不需要修改原有的代码实现。
// 产品接口
public interface Product {
void use();
}
// 具体产品实现
public class ConcreteProduct implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProduct");
}
}
// 工厂接口
public abstract class Factory {
public abstract Product createProduct();
}
// 具体工厂实现
public class ConcreteFactory extends Factory {
@Override
public Product createProduct() {
return new ConcreteProduct();
}
}
// 使用工厂方法创建产品
public class Client {
public static void main(String[] args) {
Factory factory = new ConcreteFactory();
Product product = factory.createProduct();
product.use();
}
}
抽象工厂模式
关注的是创建一组相关对象,提供一个接口来创建一组相关的或互相依赖的对象,而无需指定它们的具体类。
而抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,简单来说不是仅生产一个产品,而是一个系列产品。听起来可能有点抽象,我们还是看一下例子。
// 抽象产品A
public interface ProductA {
void use();
}
// 具体产品A1
public class ConcreteProductA1 implements ProductA {
@Override
public void use() {
System.out.println("Using ConcreteProductA1");
}
}
// 具体产品A2
public class ConcreteProductA2 implements ProductA {
@Override
public void use() {
System.out.println("Using ConcreteProductA2");
}
}
// 抽象产品B
public interface ProductB {
void eat();
}
// 具体产品B1
public class ConcreteProductB1 implements ProductB {
@Override
public void eat() {
System.out.println("Eating ConcreteProductB1");
}
}
// 具体产品B2
public class ConcreteProductB2 implements ProductB {
@Override
public void eat() {
System.out.println("Eating ConcreteProductB2");
}
}
// 抽象工厂
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
// 具体工厂1
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
// 具体工厂2
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
// 使用抽象工厂创建产品
public class Client {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
ProductB productB1 = factory1.createProductB();
productA1.use();
productB1.eat();
AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
ProductB productB2 = factory2.createProductB();
productA2.use();
productB2.eat();
}
}
什么是责任链模式
使用场景
Spring 拦截器的 Chain 也是责任链模式,还有日志系统(如不同级别的日志记录),审批流程(如多级审批)。
-
请求需要多个处理器:例如日志记录的不同级别处理。
-
动态指定处理流程:请求的处理方式不固定,依赖于运行时的链条结构。
-
消除条件分支:用责任链代替代码中的
if-else或switch-case语句。
责任链的组成
-
处理器接口(Handler):定义处理请求的通用方法和设置下一个处理器的方法。
-
具体处理器(ConcreteHandler):实现处理器接口,并处理具体的请求。
// 处理器接口
abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(String request);
}
// 具体处理器A
class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(String request) {
if ("A".equals(request)) {
System.out.println("ConcreteHandlerA handled request: " + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("No handler for request: " + request);
}
}
}
// 具体处理器B
class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(String request) {
if ("B".equals(request)) {
System.out.println("ConcreteHandlerB handled request: " + request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
} else {
System.out.println("No handler for request: " + request);
}
}
}
// 客户端
public class Main {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setNextHandler(handlerB);
handlerA.handleRequest("A"); // Output: ConcreteHandlerA handled request: A
handlerA.handleRequest("B"); // Output: ConcreteHandlerB handled request: B
handlerA.handleRequest("C"); // Output: No handler for request: C
}
}
什么是策略模式
使用场景
-
多种算法可互换:需要动态选择算法,例如排序算法的选择。
-
避免条件语句:通过策略模式替代代码中大量的
if-else或switch语句。 -
与上下文独立:客户端不需要知道具体的算法实现细节,只需依赖抽象策略。
支付系统:支持多种支付方式(如微信、支付宝、信用卡)。
数据压缩:提供不同的压缩算法。
日志策略:根据日志级别动态选择记录策略。
策略模式的组成
-
策略接口(Strategy):定义算法的通用接口。
-
具体策略(ConcreteStrategy):实现具体的算法。
-
上下文类(Context):持有策略接口的引用,调用具体策略方法。
// 策略接口
interface Strategy {
void execute();
}
// 具体策略A
class ConcreteStrategyA implements Strategy {
@Override
public void execute() {
System.out.println("Executing Strategy A");
}
}
// 具体策略B
class ConcreteStrategyB implements Strategy {
@Override
public void execute() {
System.out.println("Executing Strategy B");
}
}
// 上下文类
class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
if (strategy != null) {
strategy.execute();
} else {
System.out.println("No strategy set");
}
}
}
// 客户端
public class Main {
public static void main(String[] args) {
Context context = new Context();
Strategy strategyA = new ConcreteStrategyA();
Strategy strategyB = new ConcreteStrategyB();
context.setStrategy(strategyA);
context.executeStrategy(); // Output: Executing Strategy A
context.setStrategy(strategyB);
context.executeStrategy(); // Output: Executing Strategy B
}
}
什么是装饰器模式
给原始类增强功能,是一种结构型设计模式,用于在不改变现有类的情况下动态地为其增加新功能。通过将对象嵌套在装饰器中,可以实现功能扩展,同时保留原始对象的行为。
装饰器模式的组成
-
组件接口(Component):定义对象的通用接口,确保装饰器和被装饰对象具有一致接口。
-
具体组件(Concrete Component):实现基础功能的类。
-
装饰器(Decorator):持有组件接口的引用,并实现与组件一致的接口。
-
具体装饰器(Concrete Decorator):扩展装饰器,添加具体功能。
// 组件接口
interface Component {
void operation();
}
// 具体组件
class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}
// 装饰器抽象类
abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
// 具体装饰器A
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("ConcreteDecoratorA added behavior");
}
}
// 具体装饰器B
class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
System.out.println("ConcreteDecoratorB added behavior");
}
}
// 客户端
public class Main {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decoratorA = new ConcreteDecoratorA(component);
Component decoratorB = new ConcreteDecoratorB(decoratorA);
decoratorB.operation();
// Output:
// ConcreteComponent operation
// ConcreteDecoratorA added behavior
// ConcreteDecoratorB added behavior
}
}
Java 中的 I/O 类库中的装饰器实现举例
import java.io.*;
public class IOExample {
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
DataInputStream dis = new DataInputStream(bis);
while (dis.available() > 0) {
System.out.println(dis.readLine());
}
dis.close();
}
}
FileInputStream 用来读取文件流,然后被 BufferedInputStream 装饰,提供了缓存的功能。然后再被 DataInputStream 装饰,提供了按数据类型读取的功能。
多个装饰器叠加在一起,实现了多个功能的增强,且不会增加类的实现复杂度,每个修饰器仅需关注自己的加强功能即可,提高代码的灵活性和可维护性。
什么是模板方法模式
-
抽象类(AbstractClass):定义模板方法,包含算法骨架,声明需要子类实现的抽象方法。
-
具体类(ConcreteClass):实现抽象方法,为算法的某些步骤提供具体实现。
-
模板方法(TemplateMethod):调用一系列步骤方法,构造算法的完整逻辑。
// 抽象类
abstract class DataProcessor {
// 模板方法
public final void process() {
readData();
processData();
writeData();
}
protected abstract void readData(); // 读取数据
protected abstract void processData(); // 处理数据
protected void writeData() { // 写入数据
System.out.println("Writing data to output.");
}
}
// 具体实现类A
class CSVDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading data from CSV file.");
}
@Override
protected void processData() {
System.out.println("Processing CSV data.");
}
}
// 具体实现类B
class JSONDataProcessor extends DataProcessor {
@Override
protected void readData() {
System.out.println("Reading data from JSON file.");
}
@Override
protected void processData() {
System.out.println("Processing JSON data.");
}
}
// 客户端
public class Main {
public static void main(String[] args) {
DataProcessor csvProcessor = new CSVDataProcessor();
csvProcessor.process();
DataProcessor jsonProcessor = new JSONDataProcessor();
jsonProcessor.process();
}
}
什么是建造者模式
建造者模式通过分离构建过程与对象表示,解决了复杂对象构造的灵活性和可维护性问题。结合 Lombok 的 @Builder注解,开发者可以以极简的代码实现该模式,同时享受链式调用、默认值配置等高级特性。
扫盲八股
你认为什么才是好的代码?
高内聚低耦合、可测试、性能优良、遵循一定的代码规范和设计原则。
SOLID 原则、DRY(Don't Repeat Yourself 不要重复自己)、KISS(keep it simple stupid 保持简单)、YAGNI(你并不需要它)等软件设计原则。
是单一职责原则
一个类或者模块只负责完成一个职责,它要求我们不要设计大而全的类、而是要设计功能单一即粒度小的类。这样设计出来的代码就符合高内聚、低耦合,且易于扩展和维护,但要注意避免拆分的过于细致,那样反倒会降低内聚性,影响代码的可维护性。
什么是开闭原则 (Open/Closed Principle)
对扩展开放、对修改关闭。也要避免过度设计,预判非常未来的变换点,而将目前的代码搞得太复杂,提示的编码成本。
比如我们需要新增一个功能,这时需要修改代码,如果实现这个功能是通过新增模块、方法、类的方式,而不是修改代码中已有的模块、方法和类、这就是所谓的对扩展开放,对修改关闭。
什么是接口隔离原则
客户端(指调用者或者使用者)不应该被迫依赖它不使用的方法,即一类对另一个类应该建立在最小接口上。
什么是依赖倒置原则
它主要有两点:
抽象不应该依赖细节,但是细节应该依赖抽象。高层模块不应该依赖底层模块,二者都应该依赖其抽象。
我们的 web 应用被 tomcat 调用执行,因此 tomcat(高层)处理数据依赖了我们的 web 应用(底层)代码。
但其实不是的,实际上我们都依赖同一个“抽象”,也就是 Servlet 规范,例如 SpringMVC 就是利用了 DispatcherServlet。
什么是里氏替换原则
子类对象可以替换父类出现的任何地方,并且保证原来的程序逻辑正常且不被改变。换一个角度来理解就是:子类需要继承父类设计的初衷,也就是父类定义的协议或者说约定,它不能违背这个约定。
什么是迪米特法则或者最小知识原则 (Principle of Least Konwledge)
简单理解就是不应该有直接依赖关系的类,不要有依赖,有依赖关系的尽量只依赖必要的接口。该知道的需要知道,不该知道的一律不要知道。
更多推荐



所有评论(0)