设计模式常见八股文
单例模式有哪几种实现?如何保证线程安全?
单例模式有懒汉式、饿汉式、双重检查锁定、静态内部类和枚举单例
懒汉式: 通过使用 synchronized 来确保线程安全,但可能会影响性能。
饿汉式:实例在类加载时就创建,线程安全
双重检查锁定: 通过双重检查 instance == null 来减少锁的竞争,提升性能,并且需要使用 volatile 关键字保证实例的正确性。
静态内部类: 通过类加载机制确保线程安全,避免了不必要的资源浪费。
枚举单例: 使用枚举的特性,天生线程安全,推荐使用。
什么是策略模式?一般用在什么场景?
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以互相替换,且算法的变化不会影响使用它的客户代码(调用方)。
通过策略模式,可以消除代码中大量的 if-else 或 switch 语句,提升代码的灵活性和可维护性。
策略模式的核心特点:
算法封装:将算法的实现与使用算法的代码分离,通过封装提高代码的灵活性和可扩展性。
动态替换:可以在运行时选择和替换算法,适应不同场景的需求。
遵循开闭原则:增加新的策略时无需修改现有代码,只需新增一个实现即可。
应用场景:
多种算法可互换的场景:当需要动态选择算法时,比如排序算法、支付方式等。
避免复杂的条件语句:可以通过策略模式消除代码中大量的 if-else 或 switch 语句。
算法与上下文独立:当客户端只需要知道算法的抽象定义,而不需要了解具体实现时。
典型应用场景:
支付系统:支持多种支付方式(如微信、支付宝、信用卡),根据用户选择动态调用不同的支付算法。
数据压缩:提供不同的数据压缩算法,客户端可根据需求动态选择。
日志策略:根据日志级别动态选择记录策略,比如按文件输出、按数据库存储等。
什么是模板方法模式?一般用在什么场景?
模板方法模式是一种行为设计模式,它定义了一个算法的骨架,并允许子类在不改变算法结构的情况下重新定义某些步骤的具体实现。
模板方法模式主要通过一个抽象类来定义算法的结构,而具体步骤由子类实现。它的核心思想是:将不变的行为提取到抽象类中,将可变的行为留给子类来实现。
特点:
算法结构固定:模板方法在抽象类中定义一个算法的框架,框架是固定的,由多个方法组成。
子类负责实现细节:模板方法模式将具体的实现细节交给子类来实现,子类可以根据需求自定义某些步骤。
代码复用性高:抽象类中定义的公共逻辑可以被多个子类复用,而不用重复编写相同的代码。
遵循开闭原则:新增功能时,只需要扩展新的子类实现,不需要修改原有模板代码。
应用场景:
具有固定流程的业务逻辑:当一个算法的整体流程固定不变,而其中某些步骤的实现需要根据具体情况进行定制时。例如:制作饮料的过程(烧水 -> 冲泡饮料 -> 加调料 -> 搅拌)。
避免代码重复:多个类具有相似的逻辑,只是某些具体步骤不同时,可以通过模板方法模式提取公共逻辑,减少代码重复。
框架设计:在框架中定义通用的流程和操作规范,具体实现留给开发者扩展。常见于 Java 框架或 Web 框架中(如 Servlet 生命周期方法)。
工厂模式和抽象工厂模式有什么区别?
工厂模式关注的是创建单一类型对象,定义了一个抽象方法,让子类决定实例化哪个类,这样可以将对象的创建推迟到子类。
举例:比如你有一个“日志”系统,可以通过不同工厂生成“文件日志”或“数据库日志”。
抽象工厂模式关注的是创建一族相关的对象,提供了一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。
举例:你有一个“UI主题”系统,主题工厂会生成一整套的 UI 控件(按钮、文本框等),而每个主题的按钮和文本框需要保持风格一致。
代码示例:
工厂模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public interface Product { void use(); }
public class ConcreteProductA implements Product { @Override public void use() { System.out.println("Using Product A"); } }
public class ConcreteProductB implements Product { @Override public void use() { System.out.println("Using Product B"); } }
public abstract class Creator { public abstract Product createProduct(); }
public class ConcreteCreatorA extends Creator { @Override public Product createProduct() { return new ConcreteProductA(); } }
public class ConcreteCreatorB extends Creator { @Override public Product createProduct() { return new ConcreteProductB(); } }
public class Main { public static void main(String[] args) { Creator creatorA = new ConcreteCreatorA(); Product productA = creatorA.createProduct(); productA.use();
Creator creatorB = new ConcreteCreatorB(); Product productB = creatorB.createProduct(); productB.use(); } }
|
抽象工厂模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| public interface ProductA { void featureA(); }
public interface ProductB { void featureB(); }
public class ConcreteProductA1 implements ProductA { @Override public void featureA() { System.out.println("Feature A1"); } }
public class ConcreteProductA2 implements ProductA { @Override public void featureA() { System.out.println("Feature A2"); } }
public class ConcreteProductB1 implements ProductB { @Override public void featureB() { System.out.println("Feature B1"); } }
public class ConcreteProductB2 implements ProductB { @Override public void featureB() { System.out.println("Feature B2"); } }
public interface AbstractFactory { ProductA createProductA(); ProductB createProductB(); }
public class ConcreteFactory1 implements AbstractFactory { @Override public ProductA createProductA() { return new ConcreteProductA1(); }
@Override public ProductB createProductB() { return new ConcreteProductB1(); } }
public class ConcreteFactory2 implements AbstractFactory { @Override public ProductA createProductA() { return new ConcreteProductA2(); }
@Override public ProductB createProductB() { return new ConcreteProductB2(); } }
public class Main { public static void main(String[] args) { AbstractFactory factory1 = new ConcreteFactory1(); ProductA productA1 = factory1.createProductA(); ProductB productB1 = factory1.createProductB(); productA1.featureA(); productB1.featureB();
AbstractFactory factory2 = new ConcreteFactory2(); ProductA productA2 = factory2.createProductA(); ProductB productB2 = factory2.createProductB(); productA2.featureA(); productB2.featureB(); } }
|
谈谈你了解的最常见的几种设计模式,说说他们的应用场景
常见的设计模式:单例模式,工厂模式、抽象工厂模式、策略模式,模板模式等
单例模式:确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点。
配置管理:如全局配置类,应用程序中只能有一份配置数据。
日志管理:日志记录器通常是全局唯一的。
线程池:确保线程池的全局唯一性,避免重复创建资源。
数据库连接:共享同一个数据库连接对象,节约资源。
工厂模式: 定义一个接口或抽象类,由子类决定实例化具体的产品对象。它将对象的创建与使用分离。
日志系统:根据需求创建不同类型的日志,如文件日志、数据库日志。
解析器:根据文件类型创建不同的解析器,如 JSON 解析器、XML 解析器。
消息队列:创建不同类型的队列,如 Kafka、RabbitMQ。
抽象工厂模式: 提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。
UI 系统:创建一整套风格一致的控件(如按钮、文本框)。
数据库访问层:支持不同的数据库(如 MySQL、PostgreSQL),为每种数据库创建相应的操作对象。
策略模式: 定义一系列算法,将每种算法封装起来,并使它们可以相互替换。
支付系统:支持多种支付方式(微信支付、支付宝、银行卡)。
你认为好的代码应该是什么样的?
- 简洁易懂:避免冗余和复杂的实现,使用清晰且具有描述性的命名。
- 可读性:代码遵循一定的格式和规范,容易理解代码结构,且添加必要的注释
- 可维护性:代码应该易于修改和拓展,避免硬编码,遵循单一职责原则。
- 性能:在保证代码功能的前提下,选择合适的算法、数据结构和缓存策略提高性能。
- 良好的错误处理:代码应该能处理各种预期之外的情况,提供清晰的错误处理