前言
在日常的前端开发中,我们经常会遇到这样的场景:根据不同的条件执行不同的逻辑。最常见的做法是使用大量的 if-else
或 switch
语句,但这样的代码往往难以维护和扩展。今天我们来深入探讨一个优雅的解决方案------策略模式(Strategy Pattern)。
🎯 什么是策略模式
策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
核心概念
- Strategy(策略):定义所有具体策略的公共接口
- ConcreteStrategy(具体策略):实现具体的算法
- Context(上下文):持有策略引用,并将工作委托给策略对象
传统问题示例
javascript
// ❌ 传统的条件判断方式
function calculatePrice(price, discountType) {
if (discountType === 'student') {
return price * 0.8;
} else if (discountType === 'vip') {
return price * 0.7;
} else if (discountType === 'member') {
return price * 0.9;
} else if (discountType === 'newuser') {
return price * 0.85;
} else {
return price;
}
}
问题:
- 代码冗长,难以维护
- 违反开闭原则(对扩展开放,对修改封闭)
- 新增折扣类型需要修改现有代码
- 难以进行单元测试
🛠️ 策略模式的基础实现
TypeScript 版本
typescript
// 策略接口
interface DiscountStrategy {
calculate(price: number): number;
getDescription(): string;
}
// 具体策略实现
class StudentDiscountStrategy implements DiscountStrategy {
calculate(price: number): number {
return price * 0.8;
}
getDescription(): string {
return '学生折扣 8折';
}
}
class VipDiscountStrategy implements DiscountStrategy {
calculate(price: number): number {
return price * 0.7;
}
getDescription(): string {
return 'VIP折扣 7折';
}
}
class MemberDiscountStrategy implements DiscountStrategy {
calculate(price: number): number {
return price * 0.9;
}
getDescription(): string {
return '会员折扣 9折';
}
}
class NewUserDiscountStrategy implements DiscountStrategy {
calculate(price: number): number {
return price * 0.85;
}
getDescription(): string {
return '新用户折扣 8.5折';
}
}
class NoDiscountStrategy implements DiscountStrategy {
calculate(price: number): number {
return price;
}
getDescription(): string {
return '无折扣';
}
}
// 上下文类
class PriceCalculator {
private strategy: DiscountStrategy;
constructor(strategy: DiscountStrategy) {
this.strategy = strategy;
}
setStrategy(strategy: DiscountStrategy): void {
this.strategy = strategy;
}
calculatePrice(originalPrice: number): number {
return this.strategy.calculate(originalPrice);
}
getDiscountDescription(): string {
return this.strategy.getDescription();
}
}
// 使用示例
const calculator = new PriceCalculator(new NoDiscountStrategy());
// 计算学生折扣
calculator.setStrategy(new StudentDiscountStrategy());
const studentPrice = calculator.calculatePrice(100); // 80
console.log(`${calculator.getDiscountDescription()}: ¥${studentPrice}`);
// 计算VIP折扣
calculator.setStrategy(new VipDiscountStrategy());
const vipPrice = calculator.calculatePrice(100); // 70
console.log(`${calculator.getDiscountDescription()}: ¥${vipPrice}`);
工厂模式结合策略模式
typescript
// 策略工厂
class DiscountStrategyFactory {
private static strategies = new Map<string, DiscountStrategy>([
['student', new StudentDiscountStrategy()],
['vip', new VipDiscountStrategy()],
['member', new MemberDiscountStrategy()],
['newuser', new NewUserDiscountStrategy()],
['none', new NoDiscountStrategy()]
]);
static getStrategy(type: string): DiscountStrategy {
const strategy = this.strategies.get(type);
if (!strategy) {
throw new Error(`Unknown discount type: ${type}`);
}
return strategy;
}
static getAllStrategies(): Map<string, DiscountStrategy> {
return new Map(this.strategies);
}
static registerStrategy(type: string, strategy: DiscountStrategy): void {
this.strategies.set(type, strategy);
}
}
// 简化使用
function calculateDiscountedPrice(price: number, discountType: string): number {
const strategy = DiscountStrategyFactory.getStrategy(discountType);
const calculator = new PriceCalculator(strategy);
return calculator.calculatePrice(price);
}
🚀 React Hooks 中的策略模式
表单验证策略
typescript
// 验证策略接口
interface ValidationStrategy {
validate(value: string): { isValid: boolean; message: string };
}
// 具体验证策略
class EmailValidationStrategy implements ValidationStrategy {
validate(value: string) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return {
isValid: emailRegex.test(value),
message: emailRegex.test(value) ? '' : '请输入有效的邮箱地址'
};
}
}
class PhoneValidationStrategy implements ValidationStrategy {
validate(value: string) {
const phoneRegex = /^1[3-9]\d{9}$/;
return {
isValid: phoneRegex.test(value),
message: phoneRegex.test(value) ? '' : '请输入有效的手机号码'
};
}
}
class PasswordValidationStrategy implements ValidationStrategy {
validate(value: string) {
const isValid = value.length >= 8 && /(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value);
return {
isValid,
message: isValid ? '' : '密码至少8位,包含大小写字母和数字'
};
}
}
class RequiredValidationStrategy implements ValidationStrategy {
validate(value: string) {
const isValid = value.trim().length > 0;
return {
isValid,
message: isValid ? '' : '此字段为必填项'
};
}
}
// 验证策略工厂
class ValidationStrategyFactory {
private static strategies = new Map<string, ValidationStrategy>([
['email', new EmailValidationStrategy()],
['phone', new PhoneValidationStrategy()],
['password', new PasswordValidationStrategy()],
['required', new RequiredValidationStrategy()]
]);
static getStrategy(type: string): ValidationStrategy {
const strategy = this.strategies.get(type);
if (!strategy) {
throw new Error(`Unknown validation type: ${type}`);
}
return strategy;
}
}
// React Hook 实现
import { useState, useCallback } from 'react';
interface UseValidationOptions {
validationType: string;
required?: boolean;
}
export const useValidation = ({ validationType, required = false }: UseValidationOptions) => {
const [error, setError] = useState<string>('');
const [isValid, setIsValid] = useState<boolean>(true);
const validate = useCallback((value: string) => {
try {
// 必填验证
if (required) {
const requiredStrategy = ValidationStrategyFactory.getStrategy('required');
const requiredResult = requiredStrategy.validate(value);
if (!requiredResult.isValid) {
setError(requiredResult.message);
setIsValid(false);
return false;
}
}
// 如果值为空且不是必填,则认为有效
if (!value.trim() && !required) {
setError('');
setIsValid(true);
return true;
}
// 具体类型验证
const strategy = ValidationStrategyFactory.getStrategy(validationType);
const result = strategy.validate(value);
setError(result.message);
setIsValid(result.isValid);
return result.isValid;
} catch (error) {
setError('验证配置错误');
setIsValid(false);
return false;
}
}, [validationType, required]);
const clearError = useCallback(() => {
setError('');
setIsValid(true);
}, []);
return {
error,
isValid,
validate,
clearError
};
};
// 使用示例
const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const emailValidation = useValidation({
validationType: 'email',
required: true
});
const passwordValidation = useValidation({
validationType: 'password',
required: true
});
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
emailValidation.validate(value);
};
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setPassword(value);
passwordValidation.validate(value);
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const isEmailValid = emailValidation.validate(email);
const isPasswordValid = passwordValidation.validate(password);
if (isEmailValid && isPasswordValid) {
// 提交表单
console.log('表单提交成功');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
value={email}
onChange={handleEmailChange}
placeholder="邮箱"
/>
{emailValidation.error && (
<span className="error">{emailValidation.error}</span>
)}
</div>
<div>
<input
type="password"
value={password}
onChange={handlePasswordChange}
placeholder="密码"
/>
{passwordValidation.error && (
<span className="error">{passwordValidation.error}</span>
)}
</div>
<button
type="submit"
disabled={!emailValidation.isValid || !passwordValidation.isValid}
>
登录
</button>
</form>
);
};
🎨 主题切换策略模式
typescript
// 主题策略接口
interface ThemeStrategy {
name: string;
colors: {
primary: string;
secondary: string;
background: string;
text: string;
border: string;
};
fonts: {
primary: string;
secondary: string;
};
spacing: {
small: string;
medium: string;
large: string;
};
applyTheme(): void;
getCSS(): string;
}
// 具体主题策略
class LightThemeStrategy implements ThemeStrategy {
name = 'light';
colors = {
primary: '#007AFF',
secondary: '#5856D6',
background: '#FFFFFF',
text: '#000000',
border: '#E5E5E7'
};
fonts = {
primary: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
secondary: 'Georgia, serif'
};
spacing = {
small: '8px',
medium: '16px',
large: '24px'
};
applyTheme(): void {
const root = document.documentElement;
Object.entries(this.colors).forEach(([key, value]) => {
root.style.setProperty(`--color-${key}`, value);
});
Object.entries(this.fonts).forEach(([key, value]) => {
root.style.setProperty(`--font-${key}`, value);
});
Object.entries(this.spacing).forEach(([key, value]) => {
root.style.setProperty(`--spacing-${key}`, value);
});
}
getCSS(): string {
return `
:root {
--color-primary: ${this.colors.primary};
--color-secondary: ${this.colors.secondary};
--color-background: ${this.colors.background};
--color-text: ${this.colors.text};
--color-border: ${this.colors.border};
--font-primary: ${this.fonts.primary};
--font-secondary: ${this.fonts.secondary};
--spacing-small: ${this.spacing.small};
--spacing-medium: ${this.spacing.medium};
--spacing-large: ${this.spacing.large};
}
`;
}
}
class DarkThemeStrategy implements ThemeStrategy {
name = 'dark';
colors = {
primary: '#0A84FF',
secondary: '#5E5CE6',
background: '#000000',
text: '#FFFFFF',
border: '#38383A'
};
fonts = {
primary: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
secondary: 'Georgia, serif'
};
spacing = {
small: '8px',
medium: '16px',
large: '24px'
};
applyTheme(): void {
const root = document.documentElement;
Object.entries(this.colors).forEach(([key, value]) => {
root.style.setProperty(`--color-${key}`, value);
});
Object.entries(this.fonts).forEach(([key, value]) => {
root.style.setProperty(`--font-${key}`, value);
});
Object.entries(this.spacing).forEach(([key, value]) => {
root.style.setProperty(`--spacing-${key}`, value);
});
}
getCSS(): string {
return `
:root {
--color-primary: ${this.colors.primary};
--color-secondary: ${this.colors.secondary};
--color-background: ${this.colors.background};
--color-text: ${this.colors.text};
--color-border: ${this.colors.border};
--font-primary: ${this.fonts.primary};
--font-secondary: ${this.fonts.secondary};
--spacing-small: ${this.spacing.small};
--spacing-medium: ${this.spacing.medium};
--spacing-large: ${this.spacing.large};
}
`;
}
}
class HighContrastThemeStrategy implements ThemeStrategy {
name = 'high-contrast';
colors = {
primary: '#FFFF00',
secondary: '#00FFFF',
background: '#000000',
text: '#FFFFFF',
border: '#FFFFFF'
};
fonts = {
primary: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto',
secondary: 'Georgia, serif'
};
spacing = {
small: '12px',
medium: '20px',
large: '32px'
};
applyTheme(): void {
const root = document.documentElement;
Object.entries(this.colors).forEach(([key, value]) => {
root.style.setProperty(`--color-${key}`, value);
});
Object.entries(this.fonts).forEach(([key, value]) => {
root.style.setProperty(`--font-${key}`, value);
});
Object.entries(this.spacing).forEach(([key, value]) => {
root.style.setProperty(`--spacing-${key}`, value);
});
}
getCSS(): string {
return `
:root {
--color-primary: ${this.colors.primary};
--color-secondary: ${this.colors.secondary};
--color-background: ${this.colors.background};
--color-text: ${this.colors.text};
--color-border: ${this.colors.border};
--font-primary: ${this.fonts.primary};
--font-secondary: ${this.fonts.secondary};
--spacing-small: ${this.spacing.small};
--spacing-medium: ${this.spacing.medium};
--spacing-large: ${this.spacing.large};
}
`;
}
}
// 主题管理器
class ThemeManager {
private strategy: ThemeStrategy;
private static strategies = new Map<string, ThemeStrategy>([
['light', new LightThemeStrategy()],
['dark', new DarkThemeStrategy()],
['high-contrast', new HighContrastThemeStrategy()]
]);
constructor(defaultTheme: string = 'light') {
this.strategy = ThemeManager.strategies.get(defaultTheme) || new LightThemeStrategy();
}
setTheme(themeName: string): void {
const strategy = ThemeManager.strategies.get(themeName);
if (strategy) {
this.strategy = strategy;
this.strategy.applyTheme();
}
}
getCurrentTheme(): ThemeStrategy {
return this.strategy;
}
static registerTheme(name: string, strategy: ThemeStrategy): void {
this.strategies.set(name, strategy);
}
static getAvailableThemes(): string[] {
return Array.from(this.strategies.keys());
}
}
// React Hook 实现
import { useState, useEffect, createContext, useContext } from 'react';
const ThemeContext = createContext<{
currentTheme: ThemeStrategy;
setTheme: (themeName: string) => void;
availableThemes: string[];
} | null>(null);
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [themeManager] = useState(() => new ThemeManager());
const [currentTheme, setCurrentTheme] = useState<ThemeStrategy>(
themeManager.getCurrentTheme()
);
const setTheme = (themeName: string) => {
themeManager.setTheme(themeName);
setCurrentTheme(themeManager.getCurrentTheme());
};
const availableThemes = ThemeManager.getAvailableThemes();
// 初始化主题
useEffect(() => {
themeManager.getCurrentTheme().applyTheme();
}, [themeManager]);
return (
<ThemeContext.Provider value={{ currentTheme, setTheme, availableThemes }}>
{children}
</ThemeContext.Provider>
);
};
// 使用示例
const ThemeSelector = () => {
const { currentTheme, setTheme, availableThemes } = useTheme();
return (
<select
value={currentTheme.name}
onChange={(e) => setTheme(e.target.value)}
>
{availableThemes.map(theme => (
<option key={theme} value={theme}>
{theme.charAt(0).toUpperCase() + theme.slice(1)}
</option>
))}
</select>
);
};
📊 数据处理策略模式
typescript
// 数据处理策略接口
interface DataProcessingStrategy<T, R> {
process(data: T[]): R;
getName(): string;
}
// 统计策略
class SumStrategy implements DataProcessingStrategy<number, number> {
process(data: number[]): number {
return data.reduce((sum, num) => sum + num, 0);
}
getName(): string {
return 'sum';
}
}
class AverageStrategy implements DataProcessingStrategy<number, number> {
process(data: number[]): number {
if (data.length === 0) return 0;
return data.reduce((sum, num) => sum + num, 0) / data.length;
}
getName(): string {
return 'average';
}
}
class MaxStrategy implements DataProcessingStrategy<number, number> {
process(data: number[]): number {
return Math.max(...data);
}
getName(): string {
return 'max';
}
}
class MinStrategy implements DataProcessingStrategy<number, number> {
process(data: number[]): number {
return Math.min(...data);
}
getName(): string {
return 'min';
}
}
// 分组策略
class GroupByStrategy<T> implements DataProcessingStrategy<T, Map<string, T[]>> {
constructor(private keyExtractor: (item: T) => string) {}
process(data: T[]): Map<string, T[]> {
return data.reduce((groups, item) => {
const key = this.keyExtractor(item);
if (!groups.has(key)) {
groups.set(key, []);
}
groups.get(key)!.push(item);
return groups;
}, new Map<string, T[]>());
}
getName(): string {
return 'groupBy';
}
}
// 排序策略
class SortStrategy<T> implements DataProcessingStrategy<T, T[]> {
constructor(
private compareFn: (a: T, b: T) => number,
private direction: 'asc' | 'desc' = 'asc'
) {}
process(data: T[]): T[] {
const sorted = [...data].sort(this.compareFn);
return this.direction === 'desc' ? sorted.reverse() : sorted;
}
getName(): string {
return `sort-${this.direction}`;
}
}
// 数据处理器
class DataProcessor<T> {
private strategy: DataProcessingStrategy<T, any>;
constructor(strategy: DataProcessingStrategy<T, any>) {
this.strategy = strategy;
}
setStrategy(strategy: DataProcessingStrategy<T, any>): void {
this.strategy = strategy;
}
process(data: T[]) {
return this.strategy.process(data);
}
getStrategyName(): string {
return this.strategy.getName();
}
}
// React Hook 实现
interface UseDataProcessorOptions<T> {
data: T[];
initialStrategy: DataProcessingStrategy<T, any>;
}
export const useDataProcessor = <T>({ data, initialStrategy }: UseDataProcessorOptions<T>) => {
const [processor] = useState(() => new DataProcessor(initialStrategy));
const [result, setResult] = useState<any>(null);
const [currentStrategy, setCurrentStrategy] = useState(initialStrategy.getName());
const processData = useCallback((strategy?: DataProcessingStrategy<T, any>) => {
if (strategy) {
processor.setStrategy(strategy);
setCurrentStrategy(strategy.getName());
}
const processedResult = processor.process(data);
setResult(processedResult);
return processedResult;
}, [data, processor]);
useEffect(() => {
processData();
}, [processData]);
return {
result,
currentStrategy,
processData
};
};
// 使用示例
const DataDashboard = () => {
const salesData = [100, 200, 150, 300, 250, 400];
const {
result: sumResult,
processData: processSum
} = useDataProcessor({
data: salesData,
initialStrategy: new SumStrategy()
});
const {
result: avgResult,
processData: processAvg
} = useDataProcessor({
data: salesData,
initialStrategy: new AverageStrategy()
});
const {
result: maxResult,
processData: processMax
} = useDataProcessor({
data: salesData,
initialStrategy: new MaxStrategy()
});
return (
<div className="dashboard">
<h2>销售数据分析</h2>
<div className="stats">
<div className="stat-card">
<h3>总销售额</h3>
<p>${sumResult}</p>
</div>
<div className="stat-card">
<h3>平均销售额</h3>
<p>${avgResult?.toFixed(2)}</p>
</div>
<div className="stat-card">
<h3>最高销售额</h3>
<p>${maxResult}</p>
</div>
</div>
<div className="controls">
<button onClick={() => processSum(new SumStrategy())}>
重新计算总和
</button>
<button onClick={() => processAvg(new AverageStrategy())}>
重新计算平均值
</button>
<button onClick={() => processMax(new MaxStrategy())}>
重新计算最大值
</button>
</div>
</div>
);
};
🔄 请求重试策略模式
typescript
// 重试策略接口
interface RetryStrategy {
shouldRetry(attempt: number, error: Error): boolean;
getDelay(attempt: number): number;
getName(): string;
}
// 具体重试策略
class LinearRetryStrategy implements RetryStrategy {
constructor(
private maxAttempts: number = 3,
private baseDelay: number = 1000
) {}
shouldRetry(attempt: number, error: Error): boolean {
return attempt < this.maxAttempts;
}
getDelay(attempt: number): number {
return this.baseDelay * attempt;
}
getName(): string {
return 'linear';
}
}
class ExponentialRetryStrategy implements RetryStrategy {
constructor(
private maxAttempts: number = 3,
private baseDelay: number = 1000,
private multiplier: number = 2
) {}
shouldRetry(attempt: number, error: Error): boolean {
return attempt < this.maxAttempts;
}
getDelay(attempt: number): number {
return this.baseDelay * Math.pow(this.multiplier, attempt - 1);
}
getName(): string {
return 'exponential';
}
}
class FixedRetryStrategy implements RetryStrategy {
constructor(
private maxAttempts: number = 3,
private delay: number = 1000
) {}
shouldRetry(attempt: number, error: Error): boolean {
return attempt < this.maxAttempts;
}
getDelay(attempt: number): number {
return this.delay;
}
getName(): string {
return 'fixed';
}
}
class SmartRetryStrategy implements RetryStrategy {
constructor(private maxAttempts: number = 3) {}
shouldRetry(attempt: number, error: Error): boolean {
if (attempt >= this.maxAttempts) return false;
// 根据错误类型决定是否重试
if (error.message.includes('401') || error.message.includes('403')) {
return false; // 认证错误不重试
}
if (error.message.includes('429')) {
return true; // 限流错误需要重试
}
if (error.message.includes('5')) {
return true; // 服务器错误重试
}
return false;
}
getDelay(attempt: number): number {
// 智能延迟:基于尝试次数和随机抖动
const baseDelay = 1000;
const jitter = Math.random() * 500;
return baseDelay * Math.pow(2, attempt - 1) + jitter;
}
getName(): string {
return 'smart';
}
}
// 重试执行器
class RetryExecutor {
constructor(private strategy: RetryStrategy) {}
setStrategy(strategy: RetryStrategy): void {
this.strategy = strategy;
}
async execute<T>(operation: () => Promise<T>): Promise<T> {
let attempt = 1;
let lastError: Error;
while (true) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (!this.strategy.shouldRetry(attempt, lastError)) {
throw lastError;
}
const delay = this.strategy.getDelay(attempt);
console.log(
`Attempt ${attempt} failed, retrying in ${delay}ms using ${this.strategy.getName()} strategy`
);
await new Promise(resolve => setTimeout(resolve, delay));
attempt++;
}
}
}
}
// React Hook 实现
interface UseRetryOptions {
strategy?: RetryStrategy;
onRetry?: (attempt: number, error: Error) => void;
onSuccess?: (result: any, attempts: number) => void;
onFailure?: (error: Error, attempts: number) => void;
}
export const useRetry = (options: UseRetryOptions = {}) => {
const {
strategy = new ExponentialRetryStrategy(),
onRetry,
onSuccess,
onFailure
} = options;
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [attempts, setAttempts] = useState(0);
const executor = useRef(new RetryExecutor(strategy));
const execute = useCallback(async <T>(operation: () => Promise<T>): Promise<T | null> => {
setIsLoading(true);
setError(null);
setAttempts(0);
try {
// 包装操作以追踪重试次数
let currentAttempt = 0;
const wrappedOperation = async (): Promise<T> => {
currentAttempt++;
setAttempts(currentAttempt);
try {
return await operation();
} catch (error) {
onRetry?.(currentAttempt, error as Error);
throw error;
}
};
const result = await executor.current.execute(wrappedOperation);
onSuccess?.(result, currentAttempt);
return result;
} catch (error) {
const err = error as Error;
setError(err);
onFailure?.(err, attempts);
return null;
} finally {
setIsLoading(false);
}
}, [onRetry, onSuccess, onFailure, attempts]);
const setStrategy = useCallback((newStrategy: RetryStrategy) => {
executor.current.setStrategy(newStrategy);
}, []);
return {
execute,
setStrategy,
isLoading,
error,
attempts
};
};
// 使用示例
const ApiComponent = () => {
const [data, setData] = useState(null);
const [retryStrategy, setRetryStrategyType] = useState('exponential');
const retry = useRetry({
strategy: new ExponentialRetryStrategy(3, 1000),
onRetry: (attempt, error) => {
console.log(`Retry attempt ${attempt}:`, error.message);
},
onSuccess: (result, attempts) => {
console.log(`Success after ${attempts} attempts`);
setData(result);
},
onFailure: (error, attempts) => {
console.error(`Failed after ${attempts} attempts:`, error.message);
}
});
const fetchData = async () => {
await retry.execute(async () => {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
});
};
const changeStrategy = (strategyType: string) => {
setRetryStrategyType(strategyType);
switch (strategyType) {
case 'linear':
retry.setStrategy(new LinearRetryStrategy(3, 1000));
break;
case 'exponential':
retry.setStrategy(new ExponentialRetryStrategy(3, 1000));
break;
case 'fixed':
retry.setStrategy(new FixedRetryStrategy(3, 2000));
break;
case 'smart':
retry.setStrategy(new SmartRetryStrategy(5));
break;
}
};
return (
<div>
<h3>API 请求重试示例</h3>
<div>
<label>重试策略: </label>
<select
value={retryStrategy}
onChange={(e) => changeStrategy(e.target.value)}
>
<option value="linear">线性延迟</option>
<option value="exponential">指数退避</option>
<option value="fixed">固定延迟</option>
<option value="smart">智能重试</option>
</select>
</div>
<button onClick={fetchData} disabled={retry.isLoading}>
{retry.isLoading ? `请求中... (尝试 ${retry.attempts})` : '获取数据'}
</button>
{retry.error && (
<div style={{ color: 'red' }}>
错误: {retry.error.message}
</div>
)}
{data && (
<div style={{ color: 'green' }}>
数据获取成功: {JSON.stringify(data)}
</div>
)}
</div>
);
};
🎯 策略模式的优势
1. 开闭原则
- 对扩展开放:可以轻松添加新策略
- 对修改封闭:无需修改现有代码
2. 单一职责原则
- 每个策略类只负责一种算法
- 上下文类只负责委托工作
3. 可测试性
typescript
// 策略可以独立测试
describe('DiscountStrategy', () => {
it('should calculate student discount correctly', () => {
const strategy = new StudentDiscountStrategy();
expect(strategy.calculate(100)).toBe(80);
});
it('should calculate VIP discount correctly', () => {
const strategy = new VipDiscountStrategy();
expect(strategy.calculate(100)).toBe(70);
});
});
4. 运行时切换
typescript
// 可以在运行时动态切换策略
const calculator = new PriceCalculator(new NoDiscountStrategy());
// 用户登录后切换为相应的折扣策略
if (user.type === 'student') {
calculator.setStrategy(new StudentDiscountStrategy());
} else if (user.type === 'vip') {
calculator.setStrategy(new VipDiscountStrategy());
}
⚠️ 注意事项和最佳实践
1. 避免过度设计
typescript
// ❌ 简单场景不需要策略模式
function isEven(num: number): boolean {
return num % 2 === 0;
}
// ✅ 复杂策略才使用策略模式
interface ValidationStrategy {
validate(data: FormData): ValidationResult;
}
2. 合理使用工厂模式
typescript
// 结合工厂模式管理策略
class StrategyFactory {
private static cache = new Map<string, Strategy>();
static getStrategy(type: string): Strategy {
if (!this.cache.has(type)) {
this.cache.set(type, this.createStrategy(type));
}
return this.cache.get(type)!;
}
private static createStrategy(type: string): Strategy {
// 创建策略的逻辑
}
}
3. 考虑性能影响
typescript
// 使用单例模式避免重复创建策略对象
class SingletonStrategy implements Strategy {
private static instance: SingletonStrategy;
static getInstance(): SingletonStrategy {
if (!this.instance) {
this.instance = new SingletonStrategy();
}
return this.instance;
}
private constructor() {}
}
📋 总结
策略模式是一个强大而优雅的设计模式,特别适合以下场景:
- 算法选择:需要在多种算法中选择
- 行为变化:对象的行为需要动态改变
- 条件语句替换:减少复杂的if-else或switch语句
- 扩展性要求:需要频繁添加新的处理方式
通过策略模式,我们可以:
- 提高代码的可维护性和扩展性
- 提高代码的可测试性
- 实现更清晰的代码结构
在实际项目中,策略模式常常与工厂模式、单例模式等其他设计模式结合使用,形成更加强大和灵活的解决方案。
提醒一手:好的设计模式不是为了炫技,而是为了解决实际问题,让代码更加清晰、可维护和可扩展。