设计模式详解
一、什么是设计模式?
定义 :设计模式是在软件设计中,针对特定场景 的通用、可复用的解决方案 。它不是具体的代码,而是最佳实践的总结和指导思想。
通俗理解:就像建筑领域的"户型图"或"结构模板",针对不同的居住需求(场景),有成熟的解决方案。
设计模式的三个层次:
-
创建型模式 :对象创建的优化方案
-
单例模式、工厂模式、建造者模式、原型模式等
-
解决"如何创建对象"的问题
-
-
结构型模式 :类和对象的组合方案
-
适配器模式、装饰器模式、代理模式、组合模式等
-
解决"如何组合类和对象"的问题
-
-
行为型模式 :对象间的通信协作方案
-
观察者模式、策略模式、责任链模式、模板方法模式等
-
解决"对象如何交互和分配职责"的问题
-
设计模式的六大原则(SOLID原则):
| 原则 | 含义 | 例子 |
|---|---|---|
| 单一职责 | 一个类只负责一项职责 | UserService 只处理用户相关逻辑 |
| 开闭原则 | 对扩展开放,对修改关闭 | 通过接口新增实现,不改旧代码 |
| 里氏替换 | 子类可以替换父类 | List list = new ArrayList() |
| 接口隔离 | 建立专用接口,不建立庞大接口 | 多个小接口代替一个大接口 |
| 依赖倒置 | 依赖抽象,不依赖具体 | 依赖接口,不依赖具体实现类 |
| 迪米特法则 | 最少知道原则 | 只与直接朋友通信 |
二、单例模式(Singleton Pattern)
核心思想 :确保一个类只有一个实例,并提供全局访问点。
应用场景
-
需要频繁创建销毁的对象
-
工具类对象(配置读取、日志记录、线程池)
-
重量级资源(数据库连接池、缓存管理器)
五种实现方式(从基础到高级)
1. 饿汉式(线程安全)
public class Singleton1 {
// 1. 私有构造
private Singleton1() {}
// 2. 静态实例,类加载时就创建
private static final Singleton1 instance = new Singleton1();
// 3. 全局访问点
public static Singleton1 getInstance() {
return instance;
}
}
优点:线程安全,实现简单
缺点:类加载时就创建,可能浪费资源(如果从未使用)
2. 懒汉式(线程不安全)
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
// 有线程安全问题:多个线程可能同时创建多个实例
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
3. 懒汉式(线程安全,方法同步)
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {}
// 同步整个方法,性能较差
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
4. 双重检查锁(DCL,推荐)
public class Singleton4 {
// 使用 volatile 防止指令重排序
private static volatile Singleton4 instance;
private Singleton4() {}
public static Singleton4 getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton4.class) {
if (instance == null) { // 第二次检查
instance = new Singleton4();
}
}
}
return instance;
}
}
为什么用 volatile:
-
防止指令重排序:
instance = new Singleton()分3步:-
分配内存空间
-
初始化对象
-
将instance指向分配的内存
-
-
如果没有volatile,步骤2和3可能重排序,其他线程可能拿到未完全初始化的对象
5. 静态内部类(最优实现)
public class Singleton5 {
private Singleton5() {}
// 静态内部类,在第一次使用时才会加载
private static class SingletonHolder {
private static final Singleton5 instance = new Singleton5();
}
public static Singleton5 getInstance() {
return SingletonHolder.instance;
}
}
优点:
-
线程安全(JVM保证类加载的线程安全)
-
延迟加载(调用getInstance时才加载内部类)
-
实现简单
6. 枚举单例(最安全)
public enum Singleton6 {
INSTANCE; // 这就是单例实例
public void doSomething() {
// 业务方法
}
}
// 使用:Singleton6.INSTANCE.doSomething();
优点:
-
绝对防止反射攻击
-
绝对防止序列化问题
-
线程安全
-
实现最简单
推荐 :日常开发用静态内部类 ,需要绝对安全用枚举。
三、工厂模式
核心思想 :将对象的创建和使用分离。定义一个创建对象的接口,让子类决定实例化哪个类。
三种工厂模式对比
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 简单工厂 | 一个工厂类创建所有产品 | 产品种类少,变化不频繁 |
| 工厂方法 | 每个产品对应一个工厂 | 产品种类多,需要扩展 |
| 抽象工厂 | 创建产品族 | 需要创建相关或依赖的对象家族 |
1. 简单工厂模式
// 1. 产品接口
interface Car {
void run();
}
// 2. 具体产品
class Benz implements Car {
public void run() { System.out.println("奔驰在跑"); }
}
class BMW implements Car {
public void run() { System.out.println("宝马在跑"); }
}
// 3. 简单工厂
class CarFactory {
public static Car createCar(String type) {
if ("Benz".equals(type)) {
return new Benz();
} else if ("BMW".equals(type)) {
return new BMW();
}
throw new IllegalArgumentException("未知汽车类型");
}
}
// 使用
public class Client {
public static void main(String[] args) {
Car car = CarFactory.createCar("Benz");
car.run(); // 输出:奔驰在跑
}
}
缺点:新增产品需要修改工厂类,违反开闭原则
2. 工厂方法模式
// 1. 产品接口
interface Car {
void run();
}
// 2. 具体产品
class Benz implements Car {
public void run() { System.out.println("奔驰在跑"); }
}
class BMW implements Car {
public void run() { System.out.println("宝马在跑"); }
}
// 3. 工厂接口
interface CarFactory {
Car createCar();
}
// 4. 具体工厂
class BenzFactory implements CarFactory {
public Car createCar() {
return new Benz();
}
}
class BMWFactory implements CarFactory {
public Car createCar() {
return new BMW();
}
}
// 使用
public class Client {
public static void main(String[] args) {
// 需要奔驰
CarFactory benzFactory = new BenzFactory();
Car benz = benzFactory.createCar();
benz.run();
// 需要宝马
CarFactory bmwFactory = new BMWFactory();
Car bmw = bmwFactory.createCar();
bmw.run();
}
}
优点:符合开闭原则,新增产品只需新增工厂
3. 抽象工厂模式
解决"产品族"创建问题:比如要创建一个"家庭套餐",包含汽车、沙发、电视等配套产品。
// 1. 抽象产品族
interface Car {
void run();
}
interface Sofa {
void sit();
}
// 2. 具体产品族A:现代风格
class ModernCar implements Car {
public void run() { System.out.println("现代汽车在跑"); }
}
class ModernSofa implements Sofa {
public void sit() { System.out.println("坐在现代沙发上"); }
}
// 3. 具体产品族B:古典风格
class ClassicCar implements Car {
public void run() { System.out.println("古典汽车在跑"); }
}
class ClassicSofa implements Sofa {
public void sit() { System.out.println("坐在古典沙发上"); }
}
// 4. 抽象工厂
interface FurnitureFactory {
Car createCar();
Sofa createSofa();
}
// 5. 具体工厂
class ModernFactory implements FurnitureFactory {
public Car createCar() { return new ModernCar(); }
public Sofa createSofa() { return new ModernSofa(); }
}
class ClassicFactory implements FurnitureFactory {
public Car createCar() { return new ClassicCar(); }
public Sofa createSofa() { return new ClassicSofa(); }
}
// 使用
public class Client {
public static void main(String[] args) {
// 创建一套现代风格的家具
FurnitureFactory modernFactory = new ModernFactory();
Car modernCar = modernFactory.createCar();
Sofa modernSofa = modernFactory.createSofa();
modernCar.run();
modernSofa.sit();
}
}
四、面试回答要点
单例模式回答模板
单例模式确保一个类只有一个实例。我常用静态内部类 方式实现,它既线程安全又能延迟加载。如果需要绝对安全,会用枚举 方式,因为它能防止反射和序列化攻击。实现时要注意构造器私有 、静态实例变量 、全局访问点三个关键点。
工厂模式回答模板
工厂模式将对象的创建和使用分离,有三种形式:
简单工厂:一个工厂类创建所有产品,适合简单场景但违反开闭原则。
工厂方法:每个产品对应一个工厂,符合开闭原则,是Spring中BeanFactory的思想基础。
抽象工厂:创建产品族,适合需要创建一整套相关对象的场景。
工厂模式的核心价值 是解耦,让客户端不关心具体产品的创建细节。
实际应用场景
-
单例模式:Spring的Bean默认是单例,MyBatis的SqlSessionFactory,日志工具Logger
-
工厂方法:Spring的BeanFactory,Java的Calendar.getInstance()
-
抽象工厂:JDBC连接工厂,可以创建Connection、Statement等一组相关对象
记住:设计模式是解决特定问题的工具,不要为了用模式而用模式,关键是理解其设计思想。