基于开闭原则重构 CRM 图表系统基于单一职责原则重构登录模块
基于开闭原则重构 CRM 图表系统
背景
原 CRM 图表系统中,图表类型(柱状图、饼图、折线图等)的渲染逻辑耦合在一个大方法或类中,每增加一种图表类型就需要修改核心代码,违反开闭原则(对扩展开放,对修改关闭)。
重构前(反例)
typescript
// 反例:每增加图表类型都要修改 ChartRenderer class ChartRenderer { render(type: string, data: any) { if (type === 'bar') { // 渲染柱状图逻辑 console.log('渲染柱状图', data); } else if (type === 'pie') { // 渲染饼图逻辑 console.log('渲染饼图', data); } else if (type === 'line') { // 渲染折线图逻辑 console.log('渲染折线图', data); } // 新增图表 → 需要增加 else if,修改原有类 } }重构后(符合开闭原则)
定义抽象图表接口
Chart每种图表实现该接口
使用工厂或依赖注入管理图表实例
新增图表只需新建实现类,无需修改已有代码
typescript
// 图表抽象接口 interface Chart { render(data: any): void; } // 柱状图实现 class BarChart implements Chart { render(data: any): void { console.log('渲染柱状图', data); } } // 饼图实现 class PieChart implements Chart { render(data: any): void { console.log('渲染饼图', data); } } // 折线图实现 class LineChart implements Chart { render(data: any): void { console.log('渲染折线图', data); } } // 新增雷达图(扩展开放) class RadarChart implements Chart { render(data: any): void { console.log('渲染雷达图', data); } } // 图表渲染器(对修改关闭) class ChartRenderer { private chart: Chart; constructor(chart: Chart) { this.chart = chart; } render(data: any): void { this.chart.render(data); } } // 可选:图表工厂,用于根据类型创建(但工厂模式本身也符合开闭原则,可通过配置映射避免修改) class ChartFactory { private static registry: Map<string, new () => Chart> = new Map(); static register(type: string, ctor: new () => Chart) { this.registry.set(type, ctor); } static create(type: string): Chart { const Ctor = this.registry.get(type); if (!Ctor) throw new Error(`未知图表类型: ${type}`); return new Ctor(); } } // 注册已知图表 ChartFactory.register('bar', BarChart); ChartFactory.register('pie', PieChart); ChartFactory.register('line', LineChart); // 新增图表只需调用 register('radar', RadarChart),不改动原有逻辑 // 使用示例 const barChart = ChartFactory.create('bar'); const renderer = new ChartRenderer(barChart); renderer.render({ sales: [100, 200] });要点:
图表接口
Chart作为抽象层,依赖倒置。新增图表不需要修改
ChartRenderer和ChartFactory(工厂通过注册机制扩展)。符合开闭原则:系统对扩展开放(可注册新图表),对修改关闭(核心流程不变)。
基于单一职责原则重构登录模块
背景
原登录模块一个类/函数承担了过多职责:输入验证、用户认证、会话管理、日志记录、错误处理等,导致代码臃肿、难以测试和维护。
重构前(反例)
java
// 反例:一个类承担了多个职责 public class LoginService { public boolean login(String username, String password) { // 1. 输入校验 if (username == null || username.trim().isEmpty()) { throw new IllegalArgumentException("用户名不能为空"); } if (password == null || password.length() < 6) { throw new IllegalArgumentException("密码长度不足"); } // 2. 数据库认证 User user = database.findUserByUsername(username); if (user == null || !user.getPassword().equals(encrypt(password))) { return false; } // 3. 创建会话 String sessionId = UUID.randomUUID().toString(); sessionStore.put(sessionId, user); // 4. 记录日志 logger.info("用户 " + username + " 登录成功,session: " + sessionId); // 5. 发送欢迎邮件(意外职责) emailService.sendWelcome(user.getEmail()); return true; } }重构后(符合单一职责原则)
将不同职责分离到独立的类/模块中:
LoginValidator:负责输入校验AuthenticationService:负责用户认证SessionManager:负责会话创建和管理LoginAuditLogger:负责日志记录可选:
LoginSuccessHandler处理登录成功后的附加动作(如发送邮件)
java
// 1. 职责:输入校验 class LoginValidator { public void validate(String username, String password) { if (username == null || username.trim().isEmpty()) { throw new IllegalArgumentException("用户名不能为空"); } if (password == null || password.length() < 6) { throw new IllegalArgumentException("密码长度不足"); } } } // 2. 职责:用户认证 interface AuthenticationService { User authenticate(String username, String password) throws AuthException; } class DatabaseAuthenticationService implements AuthenticationService { private UserRepository userRepository; private PasswordEncoder passwordEncoder; @Override public User authenticate(String username, String password) { User user = userRepository.findByUsername(username); if (user == null || !passwordEncoder.matches(password, user.getPasswordHash())) { throw new AuthException("用户名或密码错误"); } return user; } } // 3. 职责:会话管理 class SessionManager { private Map<String, User> sessionStore = new ConcurrentHashMap<>(); public String createSession(User user) { String sessionId = UUID.randomUUID().toString(); sessionStore.put(sessionId, user); return sessionId; } // 可扩展其他会话操作:销毁、验证等 } // 4. 职责:登录日志记录 class LoginAuditLogger { public void logSuccess(String username, String sessionId) { LoggerFactory.getLogger(getClass()).info("用户 {} 登录成功,session: {}", username, sessionId); } public void logFailure(String username, String reason) { LoggerFactory.getLogger(getClass()).warn("用户 {} 登录失败: {}", username, reason); } } // 5. 可选:登录成功后的附加动作处理器(遵循开闭原则) interface LoginSuccessHandler { void onSuccess(User user, String sessionId); } class WelcomeEmailHandler implements LoginSuccessHandler { private EmailService emailService; @Override public void onSuccess(User user, String sessionId) { emailService.sendWelcome(user.getEmail()); } } // 最终组装:登录门面或协调者(只负责协调,不实现具体逻辑) class LoginFacade { private LoginValidator validator; private AuthenticationService authService; private SessionManager sessionManager; private LoginAuditLogger auditLogger; private List<LoginSuccessHandler> successHandlers; // 可以注入多个 public LoginResult login(String username, String password) { try { validator.validate(username, password); User user = authService.authenticate(username, password); String sessionId = sessionManager.createSession(user); auditLogger.logSuccess(username, sessionId); for (LoginSuccessHandler handler : successHandlers) { handler.onSuccess(user, sessionId); } return LoginResult.success(sessionId); } catch (IllegalArgumentException | AuthException e) { auditLogger.logFailure(username, e.getMessage()); return LoginResult.failure(e.getMessage()); } } }要点:
每个类只负责一项明确的职责,变更原因只有一个。
LoginFacade仅做流程编排,不包含具体业务逻辑。认证方式(数据库、LDAP、OAuth)可通过
AuthenticationService接口灵活替换。登录成功后的附加行为通过
LoginSuccessHandler扩展,不修改核心流程,也符合开闭原则。
总结
| 原则 | 重构对象 | 核心手法 | 收益 |
|---|---|---|---|
| 开闭原则 | CRM 图表系统 | 定义抽象接口Chart,具体图表实现接口,使用工厂+注册表扩展 | 新增图表无需修改原有类,降低风险 |
| 单一职责原则 | 登录模块 | 将输入校验、认证、会话、日志、附加动作分离到独立类 | 提高可读性、可测试性,降低耦合 |
