软件的设计模式

一、先明确:设计模式的核心原则(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();
    }
}

总结


  • 设计模式的核心是 "解耦":创建型解耦 "对象创建",结构型解耦 "类 / 对象组合",行为型解耦 "对象交互",最终目标是代码可维护、可扩展。
  • 选择模式的关键是场景
    • 需控制对象创建:单例、工厂、建造者;
    • 需适配 / 扩展对象功能:适配器、装饰器、代理;
    • 需规范对象交互:策略、观察者、模板方法;
  • 避免过度设计:设计模式是 "工具",不是 "教条",简单场景用简单代码,只有当代码出现耦合、难以扩展时,再引入设计模式。
相关推荐
休息一下接着来3 小时前
C++ 设计模式:Pimpl(Pointer to Implementation)
c++·算法·设计模式
小码过河.4 小时前
设计模式——原型模式
设计模式·原型模式
进击的小头4 小时前
为什么C语言也需要设计模式
c语言·开发语言·设计模式
小码过河.6 小时前
设计模式——外观模式
设计模式·外观模式
ipod7417 小时前
#设计模式
设计模式
小码过河.7 小时前
设计模式——命令模式
设计模式·命令模式
小码过河.7 小时前
设计模式——备忘录模式
设计模式·备忘录模式
代码or搬砖8 小时前
设计模式之单例模式
单例模式·设计模式
一条闲鱼_mytube8 小时前
智能体设计模式(四)模型上下文协议-目标设定与监控-异常处理与恢复
microsoft·设计模式