基本八股

23 种设计模式分为哪三大类

创建型模式(Creational Patterns)

用于创建对象的模式,避免代码中出现大量 new 操作和复杂的创建逻辑,同时隐藏对象创建的逻辑,它的目的是解耦对象的创建和使用。

  • 单例模式:数据库连接池、线程池。

  • 工厂方法模式:日志记录器。

  • 建造者模式:复杂对象的分步构建,如 HTML 文档生成器。

结构型模式(Structural Patterns)

用于处理对象组合的结构,关注类与对象的组合。目的是通过组合对象或类的方式,形成更大的结构。

  • 适配器模式: 旧系统的兼容升级。

  • 装饰器模式: 动态为对象增加功能,如 Java IO 流。

  • 代理模式: 实现访问控制,如 RPC 调用。

行为型模式(Behavioral Patterns)

用于定义对象如何协作以完成单个对象无法单独实现的任务,目的是定义类和对象间的通信方式。

  • 观察者模式: 事件驱动系统,如订阅-发布系统。

  • 责任链模式: 日志级别过滤、请求处理链。

  • 策略模式:支付方式选择(如微信、支付吧、信号卡)。

什么是单例模式

类的实例在整个程序生命周期内只存在一个,它提供一个全局访问点,让所有代码可以访问同一个实例,它可以延迟实例化,在需要时才创建实例。

控制对象的实例化,防止创建多个实例,从而节省资源并保证行为一致性。

常见使用场景
  1. 日志记录器:在多线程或分布式环境中确保日志记录器唯一性。

  2. 全局配置:用于保存程序的配置信息,所有模块都可以访问和使用同一个配置实例。

  3. 资源管理:需要共享的资源如数据库连接池、线程池等,确保只有一个实例管理这些资源。

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() 是一个非原子操作,它分为以下三步:

  1. 分配内存空间。

  2. 初始化对象。

  3. 将对象的引用赋值给 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 也是责任链模式,还有日志系统(如不同级别的日志记录),审批流程(如多级审批)。

  1. 请求需要多个处理器:例如日志记录的不同级别处理。

  2. 动态指定处理流程:请求的处理方式不固定,依赖于运行时的链条结构。

  3. 消除条件分支:用责任链代替代码中的 if-elseswitch-case 语句。

责任链的组成
  1. 处理器接口(Handler):定义处理请求的通用方法和设置下一个处理器的方法。

  2. 具体处理器(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
    }
}

什么是策略模式

使用场景
  1. 多种算法可互换:需要动态选择算法,例如排序算法的选择。

  2. 避免条件语句:通过策略模式替代代码中大量的 if-elseswitch 语句。

  3. 与上下文独立:客户端不需要知道具体的算法实现细节,只需依赖抽象策略。

支付系统:支持多种支付方式(如微信、支付宝、信用卡)。

数据压缩:提供不同的压缩算法。

日志策略:根据日志级别动态选择记录策略。

策略模式的组成
  1. 策略接口(Strategy):定义算法的通用接口。

  2. 具体策略(ConcreteStrategy):实现具体的算法。

  3. 上下文类(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
    }
}

什么是装饰器模式

给原始类增强功能,是一种结构型设计模式,用于在不改变现有类的情况下动态地为其增加新功能。通过将对象嵌套在装饰器中,可以实现功能扩展,同时保留原始对象的行为。

装饰器模式的组成
  1. 组件接口(Component):定义对象的通用接口,确保装饰器和被装饰对象具有一致接口。

  2. 具体组件(Concrete Component):实现基础功能的类。

  3. 装饰器(Decorator):持有组件接口的引用,并实现与组件一致的接口。

  4. 具体装饰器(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 装饰,提供了按数据类型读取的功能

多个装饰器叠加在一起,实现了多个功能的增强,且不会增加类的实现复杂度,每个修饰器仅需关注自己的加强功能即可,提高代码的灵活性和可维护性。

什么是模板方法模式

  1. 抽象类(AbstractClass):定义模板方法,包含算法骨架,声明需要子类实现的抽象方法。

  2. 具体类(ConcreteClass):实现抽象方法,为算法的某些步骤提供具体实现。

  3. 模板方法(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)

简单理解就是不应该有直接依赖关系的类,不要有依赖,有依赖关系的尽量只依赖必要的接口。该知道的需要知道,不该知道的一律不要知道。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐