news 2026/6/20 2:28:22

从CRM图表重构,吃透「开闭原则」

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从CRM图表重构,吃透「开闭原则」

题目里的原始需求很简单:Sunny的CRM系统要支持显示不同类型的业务图表,现在已经做了饼状图PieChart和柱状图BarChart,要求前端展示类ChartDisplay来根据类型调用不同图表的显示方法,原始设计类图就是题目给的这样:

我们先把原始设计翻译成大家能看懂的代码,感受一下问题出在哪:

Java

// 饼状图类 class PieChart { public void display() { System.out.println("显示饼状图"); } } // 柱状图类 class BarChart { public void display() { System.out.println("显示柱状图"); } } // 图表显示类(原始版本) class ChartDisplay { public void display(String type) { // 根据传入的类型判断显示哪种图表 if ("pie".equals(type)) { new PieChart().display(); } else if ("bar".equals(type)) { new BarChart().display(); } } }

这个代码写出来,能用吗?当然能用。但问题在哪?我们想想业务迭代:下个月产品经理说,我们要给CRM加个折线图LineChart,还要加个雷达图RadarChart,你要怎么改?

你只能跑到ChartDisplaydisplay方法里,再加两个if else分支——每加一种新图表,就要修改原有显示类的代码​。

这就犯了软件开发的大忌:

  1. 修改原有代码很容易不小心改出bug,原来好好的饼状图显示,你加新分支的时候碰了原来的逻辑,直接把旧功能搞崩了
  2. 代码越堆越长,最后display方法变成几百行的"面条代码",谁看谁头疼
  3. 新增功能必须改核心代码,完全不符合我们说的开闭原则。

开闭原则到底要解决什么问题?

我们先回忆一下开闭原则的定义:

软件实体(类、模块、方法等)应该对扩展开放,对修改关闭——也就是添加新功能的时候,尽量不要修改原有的、已经测试好的代码,而是通过扩展新代码来实现需求。

为什么要这么做?核心就是把变化的风险隔离起来​:已经上线测试过的稳定代码,改得越少,出bug的概率越低。对于我们这个图表的例子来说,变化点是什么?就是"不断新增的图表类型"——所以我们要把图表变化这个部分抽出来,让加新图不用改原来的代码。

重构:用抽象+多态实现开闭原则

要实现开闭,核心思路就是把变化的部分抽象成抽象层,具体实现交给子类扩展​。我们梳理一下重构步骤:

步骤1:抽出抽象图表父层/接口

不管是什么类型的图表,都要实现display()方法,所以我们先定义一个抽象的AbstractChart,规定所有图表的统一行为:

Java

// 抽象图表抽象类:定义所有图表的公共行为 public abstract class AbstractChart { public abstract void display(); }

如果你更喜欢用接口,也可以写成接口形式,这里用抽象类不影响核心逻辑:

Java

public interface Chart { void display(); }

步骤2:具体图表继承抽象层,各自实现

原来的PieChartBarChart都改成抽象层的子类,各自实现自己的显示逻辑就好:

Java

// 饼状图:扩展抽象图表 public class PieChart extends AbstractChart { @Override public void display() { System.out.println("显示饼状图"); } } // 柱状图:扩展抽象图表 public class BarChart extends AbstractChart { @Override public void display() { System.out.println("显示柱状图"); } }

步骤3:重构显示类,去除条件判断

原来的ChartDisplay不需要再根据type写一堆if else了,现在它只需要面向抽象的AbstractChart编程,不管来什么图表,调用统一的display()就可以:

Java

public class ChartDisplay { // 直接接收抽象图表,不需要判断类型 public void display(AbstractChart chart) { chart.display(); } }

重构完的类图结构就变成了这样:

┌─────────────────────────────────┐ │ AbstractChart(抽象层) │ │ + display() : void │ └─────────────┬───────────────────┘ │ ┌──────┴──────┐ ▼ ▼ ┌───────────┐ ┌───────────┐ │ PieChart │ │ BarChart │ │ +display()│ │ +display()│ └───────────┘ └───────────┘ ┌────────────────────┐ │ ChartDisplay │ │ +display(AbstractChart) │ └────────────────────┘

加新图表有多爽?看看开闭原则的优势

现在我们再回头看刚才的需求:要加折线图,怎么做?

  1. 新增一个LineChart类,继承AbstractChart,实现自己的display()——搞定,不用改任何原来的代码
  2. 客户端直接把新的折线图传给ChartDisplay就可以显示。

代码写出来就是这样:

Java

// 新增折线图:只需要扩展新类,不需要改原来的ChartDisplay、其他图表的代码 public class LineChart extends AbstractChart { @Override public void display() { System.out.println("显示折线图"); } }

客户端调用:

Java

public class Client { public static void main(String[] args) { ChartDisplay display = new ChartDisplay(); // 显示饼状图 display.display(new PieChart()); // 显示柱状图 display.display(new BarChart()); // 新增折线图:不需要改任何原有逻辑,直接用 display.display(new LineChart()); } }

完美!原来的ChartDisplay是稳定的,原来的PieChartBarChart也是稳定的,所有稳定代码我们一行都没改,只加了新的扩展类,完全符合开闭原则。

如果以后还要加雷达图、散点图、热力图,都是一模一样的流程,只加新类,不改旧代码,从根源上避免了改旧代码出bug的风险。

一些更灵活的扩展:如果需要适配第三方图表库?

实际开发里我们经常会遇到一种情况:我们用到的第三方图表库,它的类已经写好了,没法改成我们继承AbstractChart的结构怎么办?这时候我们可以用适配器模式配合开闭原则,一样不用改原有代码:

比如我们拿到一个第三方的柱状图类,接口和我们不兼容:

Java

// 第三方的柱状图类,我们改不了它的源码 public class ThirdPartyBarChart { // 它的方法叫show(),不是我们要求的display() public void show() { System.out.println("第三方柱状图渲染"); } }

我们不需要改第三方的代码,也不需要改我们的ChartDisplay代码,只需要加一个适配器类,扩展我们的抽象图表就好:

Java

// 适配器:扩展我们的AbstractChart,适配第三方类 public class ThirdPartyBarChartAdapter extends AbstractChart { private ThirdPartyBarChart thirdChart = new ThirdPartyBarChart(); @Override public void display() { // 转调第三方的show方法 thirdChart.show(); } }

还是那个逻辑:加新类,不改旧代码,完美兼容。

总结:开闭原则的核心不是"永远不修改"

很多人会误解开闭原则:开闭原则就是说永远不能改原有代码吗?其实不是,开闭原则是一种设计思想:​我们要把会变化的部分提前抽出来抽象化,让后续新增需求的时候,尽量通过扩展而不是修改来实现,以此来保证系统的稳定性​。

回到我们这个CRM的例子,重构前后的对比其实很明显:

原始设计重构后(符合开闭)
新增图表需要修改ChartDisplay代码新增图表只需要加新类,不用改原有代码
条件分支越来越多,代码可读性差代码职责清晰,符合单一职责
修改旧代码容易引入bug稳定代码不修改,风险隔离

其实开闭原则是所有设计模式的核心基调,很多设计模式(策略、装饰、适配器、工厂等等)本质上都是为了符合开闭原则。吃透这个小案例,再看其他设计模式,你一下子就能get到设计背后的思路了~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/20 2:23:06

终极免费多语言字体指南:如何快速上手Poppins字体家族

终极免费多语言字体指南:如何快速上手Poppins字体家族 【免费下载链接】Poppins Poppins, a Devanagari Latin family for Google Fonts. 项目地址: https://gitcode.com/gh_mirrors/po/Poppins Poppins字体是一款完全免费的开源几何无衬线字体,…

作者头像 李华
网站建设 2026/6/20 2:22:35

高效办公新体验:在VS Code中无缝预览Word与Excel文件

高效办公新体验:在VS Code中无缝预览Word与Excel文件 【免费下载链接】vscode-office Let VSCode support previewing PDF, Excel, Word and other formats, and add markdown WYSIWYG editor. 项目地址: https://gitcode.com/gh_mirrors/vs/vscode-office 在…

作者头像 李华
网站建设 2026/6/20 2:19:07

山东大学软件学院创新实训——CodeGaurd(七)

6.19日从 V1 骨架到 V14 验收,前端跟随业务迭代逐步演进,而非一开始就设计"完美架构"。1. API 层的演进:从单一调用到复杂业务流程V1-V2 时期,API 层只是简单的 CRUD 调用:export async function fetchProje…

作者头像 李华
网站建设 2026/6/20 2:15:40

英雄联盟回放播放神器:ROFL-Player完整实战指南

英雄联盟回放播放神器:ROFL-Player完整实战指南 【免费下载链接】ROFL-Player (No longer supported) One stop shop utility for viewing League of Legends replays! 项目地址: https://gitcode.com/gh_mirrors/ro/ROFL-Player 还在为英雄联盟旧版本回放无…

作者头像 李华
网站建设 2026/6/20 2:13:59

FitGirl游戏管家终极指南:3步搭建你的专属游戏库

FitGirl游戏管家终极指南:3步搭建你的专属游戏库 【免费下载链接】Fitgirl-Repack-Launcher An Electron launcher designed specifically for FitGirl Repacks, utilizing pure vanilla JavaScript, HTML, and CSS for optimal performance and customization 项…

作者头像 李华