设计模式与函数式编程:策略模式与Lambda、命令模式与函数对象、观察者与Stream

说实话,这个话题我琢磨了很久。设计模式是面向对象的经典,函数式编程又是现代Java的标配。两者怎么结合?很多人觉得这是两条路,其实不然。

我个人习惯把函数式编程看作设计模式的「语法糖」。你想想看,策略模式的核心不就是把算法传来传去吗?Lambda表达式正好干这个。命令模式不就是封装操作吗?函数对象天生就是干这个的。观察者模式呢?Stream的流式处理本质上就是一种观察。

好,咱们一个一个来看。

策略模式与Lambda

先说说策略模式。我记得刚入行那会儿,写策略模式都是先定义一个接口,然后写一堆实现类,再用工厂或者上下文去调用。代码量不小,但逻辑其实很简单。

举个例子,一个支付系统,有多种支付方式:

// 传统方式
interface PaymentStrategy {
    void pay(int amount);
}

class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("微信支付:" + amount);
    }
}

// 调用
PaymentStrategy strategy = new AlipayStrategy();
strategy.pay(100);

这段代码没毛病,但太啰嗦了。如果策略只有一两行逻辑,你写一个完整的类,是不是有点杀鸡用牛刀?

用Lambda改写一下:

// 函数式接口
@FunctionalInterface
interface PaymentStrategy {
    void pay(int amount);
}

// 直接使用Lambda
PaymentStrategy alipay = amount -> System.out.println("支付宝支付:" + amount);
PaymentStrategy wechat = amount -> System.out.println("微信支付:" + amount);

alipay.pay(100);
wechat.pay(200);

你看,代码量直接砍半。而且逻辑更清晰——策略就是一段行为,不是一堆类。

我的经验: 如果策略数量固定且逻辑简单,用Lambda就够了。但如果策略有复杂的状态或依赖,还是老老实实写类吧。我在项目中就踩过这个坑——用Lambda写了一个复杂的折扣策略,结果调试的时候根本看不清逻辑。

命令模式与函数对象

命令模式,说白了就是把「操作」封装成一个对象。这样你可以把操作排队、记录、撤销。

传统写法是这样的:

interface Command {
    void execute();
}

class LightOnCommand implements Command {
    private Light light;
    public LightOnCommand(Light light) { this.light = light; }
    public void execute() { light.on(); }
}

class LightOffCommand implements Command {
    private Light light;
    public LightOffCommand(Light light) { this.light = light; }
    public void execute() { light.off(); }
}

每个命令一个类,如果命令多了,类爆炸。你想想看,一个遥控器有20个按钮,就得写20个类?

用函数对象(其实就是Lambda或方法引用)来改写:

// 命令接口变成函数式接口
@FunctionalInterface
interface Command {
    void execute();
}

// 直接传方法引用
Command lightOn = light::on;
Command lightOff = light::off;

// 或者传Lambda
Command lightOn = () -> light.on();
Command lightOff = () -> light.off();

// 放入队列
List<Command> commands = Arrays.asList(lightOn, lightOff);
commands.forEach(Command::execute);

嗯,这里要注意一点。命令模式的核心是「封装请求」,而函数对象天然就是封装好的行为。所以用Lambda代替命令类,逻辑上完全说得通。

避坑指南: 我曾经在项目中用Lambda实现命令模式,结果发现Lambda无法序列化。如果你的命令需要持久化或者网络传输,记得用传统的类实现Serializable。Lambda虽然可以序列化,但坑很多,不建议在生产环境这么干。

观察者模式与Stream

观察者模式,经典的生产者-消费者模型。传统写法:

interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    private String name;
    public ConcreteObserver(String name) { this.name = name; }
    public void update(String message) {
        System.out.println(name + " 收到消息:" + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();
    public void addObserver(Observer o) { observers.add(o); }
    public void notifyObservers(String message) {
        for (Observer o : observers) {
            o.update(message);
        }
    }
}

这段代码很标准,但有个问题——观察者的逻辑是写死在类里的。如果我想让不同的观察者做不同的事,就得写一堆子类。

用Stream和Lambda改造一下:

// 观察者接口变成函数式接口
@FunctionalInterface
interface Observer {
    void update(String message);
}

// 主题类
class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer o) { observers.add(o); }
    
    public void notifyObservers(String message) {
        // 用Stream处理
        observers.stream()
                 .forEach(o -> o.update(message));
    }
    
    // 甚至可以加过滤条件
    public void notifyObserversWithFilter(String message, Predicate<Observer> filter) {
        observers.stream()
                 .filter(filter)
                 .forEach(o -> o.update(message));
    }
}

// 使用
Subject subject = new Subject();
subject.addObserver(msg -> System.out.println("日志记录:" + msg));
subject.addObserver(msg -> System.out.println("发送通知:" + msg));
subject.notifyObservers("系统更新了");

你看,观察者的行为变成了Lambda,想怎么变就怎么变。而且Stream的filter、map这些操作,天然就是观察者模式的扩展——你可以对观察者列表做各种变换。

核心思想: 函数式编程不是要取代设计模式,而是让设计模式的实现更简洁、更灵活。策略模式用Lambda,命令模式用函数对象,观察者模式用Stream——本质上都是用「行为」代替「类」。

三者对比

设计模式 传统实现 函数式实现 适用场景
策略模式 接口 + 多个实现类 函数式接口 + Lambda 算法简单、策略固定
命令模式 接口 + 命令类 函数对象 / 方法引用 命令逻辑简单、无需序列化
观察者模式 接口 + 观察者类 Stream + Lambda 观察者行为多变、需要流式处理

知识体系图

下面这张图展示了设计模式与函数式编程的对应关系:

设计模式与函数式编程对应关系 策略模式 命令模式 观察者模式 Lambda表达式 函数对象/方法引用 Stream流式处理 代码简洁 行为参数化 灵活组合 核心:用「行为」代替「类」,用「函数」代替「对象」

说白了,设计模式是骨架,函数式编程是血肉。骨架决定了结构,血肉让结构更灵活。我个人建议,在项目中优先考虑用函数式的方式实现设计模式,只有当逻辑复杂到Lambda难以表达时,再退回到传统的类实现。

最后说一句,别为了用函数式而用函数式。我见过有人把几十行的策略逻辑塞进一个Lambda里,那代码简直没法看。适度使用,才是正道。

公众号:蓝海资料掘金营,微信deep3321