设计模式与函数式编程:策略模式与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);
你看,代码量直接砍半。而且逻辑更清晰——策略就是一段行为,不是一堆类。
命令模式与函数对象
命令模式,说白了就是把「操作」封装成一个对象。这样你可以把操作排队、记录、撤销。
传统写法是这样的:
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代替命令类,逻辑上完全说得通。
观察者模式与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 | 观察者行为多变、需要流式处理 |
知识体系图
下面这张图展示了设计模式与函数式编程的对应关系:
说白了,设计模式是骨架,函数式编程是血肉。骨架决定了结构,血肉让结构更灵活。我个人建议,在项目中优先考虑用函数式的方式实现设计模式,只有当逻辑复杂到Lambda难以表达时,再退回到传统的类实现。
最后说一句,别为了用函数式而用函数式。我见过有人把几十行的策略逻辑塞进一个Lambda里,那代码简直没法看。适度使用,才是正道。