函数式编程艺术 / 13 函数式设计模式
13 函数式设计模式
“设计模式在函数式编程中不是消失了,而是变得如此简单,以至于你可能不会注意到它们。”
13.1 传统模式 vs 函数式模式
许多经典 GoF 设计模式在函数式编程中变得简单甚至不必要。
13.1.1 模式对照表
| GoF 模式 | 函数式等价 | 说明 |
|---|---|---|
| 策略 | 函数作为参数 | 高阶函数 |
| 命令 | 函数/闭包 | 数据 + 函数 |
| 观察者 | 流/响应式 | Observable |
| 模板方法 | 高阶函数 | 传入变化部分 |
| 迭代器 | 迭代器/Stream | 惰性序列 |
| 工厂 | 工厂函数 | 返回值的函数 |
| 装饰器 | 函数组合 | compose |
| 适配器 | 函数适配 | map, contramap |
| 单例 | 模块/常量 | 顶层绑定 |
| 访问者 | 模式匹配 | 代数数据类型 |
| 状态 | State Monad | 不可变状态 |
| 备忘录 | Memoize | 纯函数缓存 |
13.2 策略模式(Strategy)
13.2.1 传统 OOP 策略
// Java 传统策略模式
interface SortStrategy<T> {
List<T> sort(List<T> items);
}
class QuickSort<T> implements SortStrategy<T> { ... }
class MergeSort<T> implements SortStrategy<T> { ... }
class HeapSort<T> implements SortStrategy<T> { ... }
13.2.2 函数式策略
// 函数式:直接传入排序函数
const sortWith = (compareFn) => (arr) => [...arr].sort(compareFn);
const sortByAge = sortWith((a, b) => a.age - b.age);
const sortByName = sortWith((a, b) => a.name.localeCompare(b.name));
const sortByScore = sortWith((a, b) => b.score - a.score);
// 使用
const users = [{ name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }];
sortByAge(users); // [{ name: 'Bob', ... }, { name: 'Alice', ... }]
sortByName(users); // [{ name: 'Alice', ... }, { name: 'Bob', ... }]
-- Haskell:策略就是函数参数
sortBy' :: (a -> a -> Ordering) -> [a] -> [a]
sortBy' = Data.List.sortBy
sortByAge = sortBy' (comparing age)
sortByName = sortBy' (comparing name)
13.3 装饰器模式(Decorator)
13.3.1 函数式装饰器
// 通过函数组合实现装饰
const withLogging = (fn) => (...args) => {
console.log(`Calling ${fn.name} with`, args);
const result = fn(...args);
console.log(`${fn.name} returned`, result);
return result;
};
const withTiming = (fn) => (...args) => {
const start = performance.now();
const result = fn(...args);
console.log(`${fn.name} took ${performance.now() - start}ms`);
return result;
};
const withRetry = (maxRetries) => (fn) => async (...args) => {
for (let i = 0; i <= maxRetries; i++) {
try {
return await fn(...args);
} catch (err) {
if (i === maxRetries) throw err;
}
}
};
// 组合装饰器
const enhanced = withLogging(withTiming(fetchData));
// 或使用 compose
const enhance = compose(withLogging, withTiming);
const enhancedFetch = enhance(fetchData);
# Python 装饰器(语法糖)
import functools
import time
def with_logging(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
print(f"Calling {fn.__name__} with {args}")
result = fn(*args, **kwargs)
print(f"{fn.__name__} returned {result}")
return result
return wrapper
def with_timing(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(f"{fn.__name__} took {time.time() - start:.3f}s")
return result
return wrapper
@with_logging
@with_timing
def process_data(data):
return sum(data)
13.4 命令模式(Command)
13.4.1 函数即命令
// 命令就是函数 + 数据
const createCommand = (type, payload) => (state) => {
switch (type) {
case 'ADD_TODO':
return { ...state, todos: [...state.todos, payload] };
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(t =>
t.id === payload ? { ...t, done: !t.done } : t
),
};
case 'SET_FILTER':
return { ...state, filter: payload };
default:
return state;
}
};
// 使用
let state = { todos: [], filter: 'all' };
state = createCommand('ADD_TODO', { id: 1, text: 'Learn FP', done: false })(state);
state = createCommand('TOGGLE_TODO', 1)(state);
-- Haskell:命令用代数数据类型表示
data Command
= AddTodo Todo
| ToggleTodo Int
| SetFilter Filter
applyCommand :: AppState -> Command -> AppState
applyCommand state (AddTodo todo) = state { todos = todo : todos state }
applyCommand state (ToggleTodo id) = state { todos = map (toggleIf id) (todos state) }
applyCommand state (SetFilter f) = state { filter = f }
-- 命令回放
applyCommands :: AppState -> [Command] -> AppState
applyCommands = foldl applyCommand
13.5 观察者模式(Observer)
13.5.1 响应式替代观察者
// 传统观察者 → 响应式流
const { Subject } = require('rxjs');
// Subject 既是 Observable 也是 Observer
const eventBus$ = new Subject();
// 订阅(观察者)
eventBus$.subscribe(event => {
if (event.type === 'USER_LOGIN') handleLogin(event.data);
});
eventBus$.subscribe(event => {
if (event.type === 'USER_LOGOUT') handleLogout(event.data);
});
// 发布
eventBus$.next({ type: 'USER_LOGIN', data: { userId: 1 } });
;; Clojure 的 watch 机制
(def state (atom {}))
;; 添加观察者
(add-watch state :logger
(fn [key atom old-state new-state]
(println "State changed:" old-state "->" new-state)))
;; 修改状态会触发观察者
(swap! state assoc :count 1)
;; 输出: State changed: {} -> {:count 1}
13.6 Builder 模式
13.6.1 函数式 Builder
// 使用函数链实现 Builder
const createBuilder = (initial = {}) => {
const builder = {
_data: { ...initial },
name(v) { return createBuilder({ ...this._data, name: v }); },
age(v) { return createBuilder({ ...this._data, age: v }); },
email(v) { return createBuilder({ ...this._data, email: v }); },
build() { return this._data; },
};
return builder;
};
// 使用
const user = createBuilder()
.name('Alice')
.age(30)
.email('alice@example.com')
.build();
-- Haskell:使用 Record 语法
data User = User
{ userName :: String
, userAge :: Int
, userEmail :: String
} deriving (Show)
-- 默认值模式
defaultUser :: User
defaultUser = User "" 0 ""
-- 使用 Record update
alice = defaultUser
{ userName = "Alice"
, userAge = 30
, userEmail = "alice@example.com"
}
// Rust:使用 Builder pattern + 函数式方法
#[derive(Default)]
struct UserBuilder {
name: Option<String>,
age: Option<u32>,
email: Option<String>,
}
impl UserBuilder {
fn new() -> Self { Self::default() }
fn name(mut self, name: &str) -> Self { self.name = Some(name.into()); self }
fn age(mut self, age: u32) -> Self { self.age = Some(age); self }
fn email(mut self, email: &str) -> Self { self.email = Some(email.into()); self }
fn build(self) -> Result<User, String> {
Ok(User {
name: self.name.ok_or("name required")?,
age: self.age.ok_or("age required")?,
email: self.email.ok_or("email required")?,
})
}
}
let user = UserBuilder::new()
.name("Alice")
.age(30)
.email("alice@example.com")
.build();
13.7 访问者模式(Visitor)
13.7.1 模式匹配替代访问者
-- 代数数据类型 + 模式匹配完全替代访问者模式
data Expr
= Num Double
| Add Expr Expr
| Mul Expr Expr
| Neg Expr
-- 每个"访问者"就是一个独立的函数
eval :: Expr -> Double
eval (Num x) = x
eval (Add a b) = eval a + eval b
eval (Mul a b) = eval a * eval b
eval (Neg a) = negate (eval a)
prettyPrint :: Expr -> String
prettyPrint (Num x) = show x
prettyPrint (Add a b) = "(" ++ prettyPrint a ++ " + " ++ prettyPrint b ++ ")"
prettyPrint (Mul a b) = "(" ++ prettyPrint a ++ " * " ++ prettyPrint b ++ ")"
prettyPrint (Neg a) = "(-" ++ prettyPrint a ++ ")"
-- 添加新的"访问者"不需要修改数据类型
height :: Expr -> Int
height (Num _) = 1
height (Add a b) = 1 + max (height a) (height b)
height (Mul a b) = 1 + max (height a) (height b)
height (Neg a) = 1 + height a
13.8 依赖注入
13.8.1 函数式依赖注入
// Reader Monad / 函数参数实现依赖注入
// 方式 1:参数注入(最简单)
const createUserService = (db, mailer) => ({
register: async (userData) => {
const user = await db.save(userData);
await mailer.send(user.email, 'Welcome!');
return user;
},
findById: (id) => db.findById(id),
});
// 方式 2:Reader Monad
class Reader {
constructor(run) { this.run = run; }
static of(x) { return new Reader(() => x); }
map(fn) { return new Reader(env => fn(this.run(env))); }
flatMap(fn) { return new Reader(env => fn(this.run(env)).run(env)); }
}
const ask = new Reader(env => env);
const register = (userData) =>
ask.flatMap(env =>
Reader.of(async () => {
const user = await env.db.save(userData);
await env.mailer.send(user.email, 'Welcome!');
return user;
})
);
-- Haskell:Reader Monad
type App a = Reader Config a
getConfig :: App Config
getConfig = ask
register :: User -> App (IO User)
register userData = do
config <- getConfig
return $ do
user <- saveUser (db config) userData
sendEmail (mailer config) (userEmail user) "Welcome!"
return user
13.9 重试与熔断
// 重试策略(函数组合)
const withRetry = (strategy) => (fn) => async (...args) => {
let lastError;
for (let attempt = 0; attempt < strategy.maxRetries; attempt++) {
try {
return await fn(...args);
} catch (err) {
lastError = err;
await strategy.delay(attempt);
}
}
throw lastError;
};
// 策略组合
const exponentialBackoff = {
maxRetries: 3,
delay: (attempt) => new Promise(r => setTimeout(r, 2 ** attempt * 1000)),
};
const linearBackoff = {
maxRetries: 5,
delay: (attempt) => new Promise(r => setTimeout(r, (attempt + 1) * 1000)),
};
// 熔断器
const withCircuitBreaker = (options) => (fn) => {
let failures = 0;
let lastFailure = 0;
const { threshold, resetTimeout } = options;
return async (...args) => {
if (failures >= threshold && Date.now() - lastFailure < resetTimeout) {
throw new Error('Circuit breaker is open');
}
try {
const result = await fn(...args);
failures = 0;
return result;
} catch (err) {
failures++;
lastFailure = Date.now();
throw err;
}
};
};
// 组合
const resilientFetch = compose(
withRetry(exponentialBackoff),
withCircuitBreaker({ threshold: 5, resetTimeout: 30000 })
)(fetch);
13.10 业务场景
13.10.1 插件系统
// 函数式插件系统
const createPluginSystem = () => {
let plugins = [];
return {
register: (plugin) => {
plugins = [...plugins, plugin]; // 不可变
return createPluginSystem(plugins); // 返回新系统
},
execute: (hook, context) =>
plugins
.filter(p => p.hooks && p.hooks[hook])
.reduce((ctx, plugin) => plugin.hooks[hook](ctx), context),
};
};
// 插件定义
const loggingPlugin = {
name: 'logging',
hooks: {
beforeRequest: (ctx) => {
console.log(`[${ctx.method}] ${ctx.url}`);
return ctx;
},
afterResponse: (ctx) => {
console.log(`Response: ${ctx.status}`);
return ctx;
},
},
};
const authPlugin = {
name: 'auth',
hooks: {
beforeRequest: (ctx) => ({
...ctx,
headers: { ...ctx.headers, Authorization: `Bearer ${getToken()}` },
}),
},
};
// 使用
const system = createPluginSystem()
.register(loggingPlugin)
.register(authPlugin);
const result = system.execute('beforeRequest', {
method: 'GET', url: '/api/users', headers: {}
});
13.11 小结
| 要点 | 说明 |
|---|---|
| 策略模式 | 高阶函数,传入函数作为参数 |
| 装饰器模式 | 函数组合,包装函数增加行为 |
| 命令模式 | 代数数据类型 + 函数 |
| 观察者模式 | 响应式流(Observable) |
| 访问者模式 | 模式匹配,穷尽性检查 |
| 依赖注入 | Reader Monad / 参数传递 |
扩展阅读
- Functional Design Patterns — Scott Wlaschin(视频)
- Functional Programming Patterns — Michael Bevilacqua-Linn
- Design Patterns in Haskell — Haskell Wiki
下一章:14 函数式并发