软件的设计模式

一、先明确:设计模式的核心原则(SOLID)

学习设计模式前,先掌握这 5 个基本原则,所有设计模式都是围绕这些原则设计的:

  1. 单一职责(SRP):一个类只做一件事,修改原因只有一个。
  2. 开闭原则(OCP):对扩展开放,对修改关闭(新增功能靠扩展,而非改原有代码)。
  3. 里氏替换(LSP):子类可以替换父类,且不影响程序运行。
  4. 接口隔离(ISP):不要强迫客户端依赖它不需要的接口(拆分大接口为小接口)。
  5. 依赖倒置(DIP):依赖抽象,而非具体实现(面向接口编程)。

二、创建型模式(5 种:解决 "对象创建" 问题)

创建型模式的核心是封装对象的创建过程,降低创建逻辑和业务逻辑的耦合。

1. 单例模式(Singleton)
  • 核心思想:保证一个类在程序中只有一个实例,且提供全局访问点。

  • 适用场景

    • 资源密集型对象(数据库连接池、线程池、Redis 客户端),避免重复创建消耗资源;
    • 全局配置类、日志工具类、缓存管理器。
  • 常见实现方式对比

|------------|-------------|-------------------|------|-----|
| 实现方式 | 优点 | 缺点 | 线程安全 | 懒加载 |
| 饿汉式 | 简单、无线程安全问题 | 类加载时就创建,可能浪费内存 | 是 | 否 |
| 懒汉式(双重检查锁) | 懒加载、性能优 | 代码稍复杂(需 volatile) | 是 | 是 |
| 静态内部类 | 懒加载、线程安全、简洁 | 无法传参 | 是 | 是 |
| 枚举 | 绝对线程安全、防反射 | 灵活性低(无法懒加载) | 是 | 否 |

代码示例(静态内部类,推荐)

java 复制代码
public class ConfigManager {
    // 私有构造方法,禁止外部实例化
    private ConfigManager() {}

    // 静态内部类,类加载时不会初始化,实现懒加载
    private static class SingletonHolder {
        private static final ConfigManager INSTANCE = new ConfigManager();
    }

    // 全局访问点
    public static ConfigManager getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 示例方法:获取配置
    public String getConfig(String key) {
        return "配置值:" + key;
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        ConfigManager cm1 = ConfigManager.getInstance();
        ConfigManager cm2 = ConfigManager.getInstance();
        System.out.println(cm1 == cm2); // 输出:true(同一实例)
    }
}
  • 注意事项:单例模式会导致类的耦合度升高,且不利于单元测试(无法 mock),避免滥用。
2. 工厂模式(Factory)

分为简单工厂工厂方法抽象工厂,逐层抽象:

(1)简单工厂(不属于 GoF,但最常用)
  • 核心:一个工厂类根据参数创建不同的产品实例,隐藏创建细节。
  • 场景:产品类型少、创建逻辑简单(如创建不同类型的日志器:FileLogger、ConsoleLogger)。
(2)工厂方法(核心)
  • 核心:定义创建对象的接口,让子类决定实例化哪个类(将创建逻辑延迟到子类)。
  • 场景:产品类型可能扩展(如电商系统的支付方式:支付宝、微信支付、银行卡支付,后续可能加银联支付)。
  • 代码示例:
java 复制代码
// 抽象产品:支付接口
public interface Payment {
    void pay(double amount);
}

// 具体产品1:支付宝支付
public class AliPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount + "元");
    }
}

// 具体产品2:微信支付
public class WeChatPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount + "元");
    }
}

// 抽象工厂:支付工厂接口
public interface PaymentFactory {
    Payment createPayment();
}

// 具体工厂1:支付宝工厂
public class AliPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new AliPay();
    }
}

// 具体工厂2:微信支付工厂
public class WeChatPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new WeChatPay();
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 选择支付宝支付
        PaymentFactory factory = new AliPayFactory();
        Payment payment = factory.createPayment();
        payment.pay(100); // 输出:支付宝支付:100.0元

        // 扩展:新增银联支付,只需加UnionPay类和UnionPayFactory类,无需改原有代码(符合开闭原则)
    }
}
  • 优点:符合开闭原则,扩展新产品只需加新工厂和产品类;
  • 缺点:产品和工厂成对增加,类数量增多。
(3)抽象工厂
  • 核心:创建一系列相关 / 依赖的产品族(而非单个产品),如 "小米工厂" 生产小米手机、小米电视;"华为工厂" 生产华为手机、华为电视。
  • 场景:产品有明确的 "族" 划分,且族内产品需配套使用(如 GUI 框架:Windows 风格的按钮、文本框;Mac 风格的按钮、文本框)。
3. 建造者模式(Builder)
4. 原型模式(Prototype)

三、结构型模式(7 种:解决 "类 / 对象组合" 问题)

核心是通过合理的类 / 对象组合,优化结构,提高复用性和扩展性。

1. 适配器模式(Adapter)
  • 核心:将复杂对象的构建与表示分离,一步一步构建复杂对象(如组装电脑:CPU、内存、硬盘分步选)。

  • 场景:对象属性多、可选参数多(如订单对象、用户对象、HTTP 请求对象)。

  • 代码示例(链式调用,Java 常用):

    java 复制代码
    // 复杂对象:电脑
    public class Computer {
        // 必选参数
        private String cpu;
        private String memory;
        // 可选参数
        private String hardDisk;
        private String graphicsCard;
    
        // 私有构造,只能通过Builder创建
        private Computer(Builder builder) {
            this.cpu = builder.cpu;
            this.memory = builder.memory;
            this.hardDisk = builder.hardDisk;
            this.graphicsCard = builder.graphicsCard;
        }
    
        // 建造者类
        public static class Builder {
            // 必选参数(构造方法强制传入)
            private String cpu;
            private String memory;
            // 可选参数
            private String hardDisk;
            private String graphicsCard;
    
            public Builder(String cpu, String memory) {
                this.cpu = cpu;
                this.memory = memory;
            }
    
            // 链式设置可选参数
            public Builder hardDisk(String hardDisk) {
                this.hardDisk = hardDisk;
                return this;
            }
    
            public Builder graphicsCard(String graphicsCard) {
                this.graphicsCard = graphicsCard;
                return this;
            }
    
            // 构建对象
            public Computer build() {
                return new Computer(this);
            }
        }
    
        @Override
        public String toString() {
            return "Computer{" +
                    "cpu='" + cpu + '\'' +
                    ", memory='" + memory + '\'' +
                    ", hardDisk='" + hardDisk + '\'' +
                    ", graphicsCard='" + graphicsCard + '\'' +
                    '}';
        }
    }
    
    // 测试
    public class Test {
        public static void main(String[] args) {
            // 分步构建:必选参数+可选参数
            Computer computer = new Computer.Builder("i7-13700K", "16GB")
                    .hardDisk("1TB SSD")
                    .graphicsCard("RTX 4070")
                    .build();
            System.out.println(computer);
            // 输出:Computer{cpu='i7-13700K', memory='16GB', hardDisk='1TB SSD', graphicsCard='RTX 4070'}
        }
    }
  • 优点:参数清晰,避免构造方法参数过多;可灵活构建不同配置的对象;

  • 缺点:代码量增加,适合复杂对象。

  • 核心:通过复制(克隆)已有对象创建新对象,避免重复初始化(如从一个基础用户对象克隆出多个不同属性的用户)。

  • 场景:创建对象成本高(如对象需读取数据库 / 网络数据)、需批量创建相似对象。

  • 关键:实现Cloneable接口,重写clone()方法(浅克隆 / 深克隆)。

  • 核心:将一个类的接口转换成客户端期望的另一个接口,让不兼容的类可以一起工作(如充电器转接头)。

  • 场景:

    • 集成第三方库,但其接口与现有系统不兼容;
    • 重构老代码,不想修改原有逻辑,只需适配新接口。
  • 分类:类适配器(继承)、对象适配器(组合,推荐)。

  • 代码示例(对象适配器):

java 复制代码
// 现有系统期望的接口:支付回调
public interface PaymentCallback {
    void onSuccess(String orderId);
}

// 第三方支付的接口(不兼容)
public class ThirdPayCallback {
    // 第三方的回调方法名和参数不同
    public void callback(String orderNo, int status) {
        if (status == 1) {
            System.out.println("第三方回调:订单" + orderNo + "支付成功");
        }
    }
}

// 适配器:将第三方接口适配成系统期望的接口
public class PayCallbackAdapter implements PaymentCallback {
    // 组合第三方对象
    private ThirdPayCallback thirdPayCallback;

    public PayCallbackAdapter(ThirdPayCallback thirdPayCallback) {
        this.thirdPayCallback = thirdPayCallback;
    }

    @Override
    public void onSuccess(String orderId) {
        // 适配参数,调用第三方方法
        thirdPayCallback.callback(orderId, 1);
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 客户端只需调用统一的PaymentCallback接口,无需关心第三方实现
        PaymentCallback callback = new PayCallbackAdapter(new ThirdPayCallback());
        callback.onSuccess("ORDER_123456");
        // 输出:第三方回调:订单ORDER_123456支付成功
    }
}
2. 装饰器模式(Decorator)
  • 核心:动态地给对象添加额外功能,且不改变其原有结构(如给咖啡加奶、加糖,基础咖啡 + 装饰器 = 不同口味)。
  • 场景:需灵活扩展对象功能,且不想通过继承实现(继承会导致类爆炸)。
  • 对比继承:继承是 "静态" 扩展(编译期确定),装饰器是 "动态" 扩展(运行期可选)。
  • 代码示例:
java 复制代码
// 抽象组件:咖啡
public interface Coffee {
    double cost(); // 价格
    String getDesc(); // 描述
}

// 具体组件:基础咖啡(黑咖啡)
public class BlackCoffee implements Coffee {
    @Override
    public double cost() {
        return 10; // 基础价格10元
    }

    @Override
    public String getDesc() {
        return "黑咖啡";
    }
}

// 抽象装饰器:继承Coffee,组合Coffee
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

// 具体装饰器1:加奶
public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return coffee.cost() + 2; // 奶加2元
    }

    @Override
    public String getDesc() {
        return coffee.getDesc() + "+牛奶";
    }
}

// 具体装饰器2:加糖
public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public double cost() {
        return coffee.cost() + 1; // 糖加1元
    }

    @Override
    public String getDesc() {
        return coffee.getDesc() + "+糖";
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 动态组合:黑咖啡+奶+糖
        Coffee coffee = new BlackCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SugarDecorator(coffee);

        System.out.println(coffee.getDesc()); // 输出:黑咖啡+牛奶+糖
        System.out.println(coffee.cost()); // 输出:13.0
    }
}
3. 代理模式(Proxy)
  • 核心:为另一个对象提供一个替身,控制对它的访问(如中介、经纪人)。
  • 分类:
    • 静态代理:手动编写代理类;
    • 动态代理:运行时生成代理类(JDK 动态代理、CGLIB);
    • 场景:权限控制、日志记录、缓存、远程调用(RPC)、懒加载。
  • 代码示例(JDK 动态代理,日志记录):
java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 业务接口
public interface UserService {
    void addUser(String username);
}

// 真实业务类
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户:" + username);
    }
}

// 动态代理处理器:添加日志功能
public class LogProxyHandler implements InvocationHandler {
    // 目标对象
    private Object target;

    public LogProxyHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强:记录日志
        System.out.println("日志:调用" + method.getName() + "方法,参数:" + args[0]);
        // 调用目标方法
        Object result = method.invoke(target, args);
        // 后置增强:可添加其他逻辑
        System.out.println("日志:" + method.getName() + "方法调用完成");
        return result;
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 真实对象
        UserService userService = new UserServiceImpl();
        // 创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new LogProxyHandler(userService)
        );
        // 调用代理方法
        proxy.addUser("张三");
        // 输出:
        // 日志:调用addUser方法,参数:张三
        // 添加用户:张三
        // 日志:addUser方法调用完成
    }
}

四、行为型模式(11 种:解决 "对象交互" 问题)

核心是规范对象之间的通信方式,降低耦合,提高交互的灵活性。

1. 策略模式(Strategy)
  • 核心:定义一系列算法,将每个算法封装起来,使它们可互相替换(如排序算法:冒泡、快排、归并,可按需切换)。
  • 场景:多个算法解决同一类问题,需动态切换(如电商的优惠策略:满减、折扣、优惠券)。
  • 代码示例:
java 复制代码
// 抽象策略:优惠接口
public interface DiscountStrategy {
    double calculate(double amount); // 计算优惠后价格
}

// 具体策略1:满减(满100减20)
public class FullReduceStrategy implements DiscountStrategy {
    @Override
    public double calculate(double amount) {
        return amount >= 100 ? amount - 20 : amount;
    }
}

// 具体策略2:折扣(8折)
public class DiscountStrategyImpl implements DiscountStrategy {
    @Override
    public double calculate(double amount) {
        return amount * 0.8;
    }
}

// 上下文:订单(持有策略,调用策略)
public class Order {
    private DiscountStrategy strategy;

    // 动态设置策略
    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    // 计算最终价格
    public double getFinalPrice(double amount) {
        return strategy.calculate(amount);
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        Order order = new Order();
        double originalAmount = 100;

        // 选择满减策略
        order.setStrategy(new FullReduceStrategy());
        System.out.println("满减后价格:" + order.getFinalPrice(originalAmount)); // 输出:80.0

        // 切换为折扣策略
        order.setStrategy(new DiscountStrategyImpl());
        System.out.println("折扣后价格:" + order.getFinalPrice(originalAmount)); // 输出:80.0
    }
}
2. 观察者模式(Observer)
  • 核心:定义对象间的一对多依赖,当一个对象(被观察者)状态变化时,所有依赖它的对象(观察者)都会收到通知并自动更新(如公众号推送、事件监听)。
  • 场景:消息通知、事件驱动、实时数据更新(如股票价格变动通知)。
  • 代码示例(Java 内置 Observer/Observable):
java 复制代码
import java.util.Observable;
import java.util.Observer;

// 被观察者:公众号
public class PublicAccount extends Observable {
    private String title; // 文章标题

    public void publishArticle(String title) {
        this.title = title;
        // 标记状态变化
        setChanged();
        // 通知所有观察者
        notifyObservers(title);
    }
}

// 观察者:用户
public class User implements Observer {
    private String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + "收到公众号推送:" + arg);
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 创建公众号
        PublicAccount account = new PublicAccount();
        // 添加观察者(订阅)
        account.addObserver(new User("张三"));
        account.addObserver(new User("李四"));

        // 发布文章(触发通知)
        account.publishArticle("Java设计模式详解");
        // 输出:
        // 李四收到公众号推送:Java设计模式详解
        // 张三收到公众号推送:Java设计模式详解
    }
}
3. 模板方法模式(Template Method)
  • 核心:定义一个算法的骨架,将算法的可变步骤延迟到子类实现(如泡茶 / 泡咖啡:烧水、冲泡、倒出,其中 "冲泡" 步骤不同)。
  • 场景:多个子类有共同的算法骨架,仅部分步骤不同(如数据库操作:连接、执行 SQL、关闭连接,其中 "执行 SQL" 可变)。
  • 代码示例:
java 复制代码
// 抽象模板:饮品制作
public abstract class DrinkTemplate {
    // 模板方法:算法骨架(final防止子类重写)
    public final void makeDrink() {
        boilWater(); // 固定步骤1:烧水
        brew(); // 可变步骤:冲泡
        pourInCup(); // 固定步骤2:倒入杯中
        addCondiments(); // 可选步骤:加调料(钩子方法)
    }

    // 固定步骤
    private void boilWater() {
        System.out.println("烧开水");
    }

    private void pourInCup() {
        System.out.println("倒入杯中");
    }

    // 抽象步骤:子类实现
    protected abstract void brew();

    // 钩子方法:可选实现(默认空)
    protected void addCondiments() {}
}

// 具体模板1:咖啡
public class Coffee extends DrinkTemplate {
    @Override
    protected void brew() {
        System.out.println("冲泡咖啡粉");
    }

    @Override
    protected void addCondiments() {
        System.out.println("加奶和糖");
    }
}

// 具体模板2:茶
public class Tea extends DrinkTemplate {
    @Override
    protected void brew() {
        System.out.println("冲泡茶叶");
    }

    // 茶不加调料,使用默认钩子方法
}

// 测试
public class Test {
    public static void main(String[] args) {
        DrinkTemplate coffee = new Coffee();
        System.out.println("制作咖啡:");
        coffee.makeDrink();

        System.out.println("\n制作茶:");
        DrinkTemplate tea = new Tea();
        tea.makeDrink();
    }
}

总结


  • 设计模式的核心是 "解耦":创建型解耦 "对象创建",结构型解耦 "类 / 对象组合",行为型解耦 "对象交互",最终目标是代码可维护、可扩展。
  • 选择模式的关键是场景
    • 需控制对象创建:单例、工厂、建造者;
    • 需适配 / 扩展对象功能:适配器、装饰器、代理;
    • 需规范对象交互:策略、观察者、模板方法;
  • 避免过度设计:设计模式是 "工具",不是 "教条",简单场景用简单代码,只有当代码出现耦合、难以扩展时,再引入设计模式。
相关推荐
杨充19 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充19 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充19 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充19 小时前
2.面向对象的特性
设计模式
杨充19 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充19 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充20 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式