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

发布时间:2026/6/20 2:29:47
从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你要怎么改你只能跑到ChartDisplay的display方法里再加两个if else分支——每加一种新图表就要修改原有显示类的代码​。这就犯了软件开发的大忌修改原有代码很容易不小心改出bug原来好好的饼状图显示你加新分支的时候碰了原来的逻辑直接把旧功能搞崩了代码越堆越长最后display方法变成几百行的面条代码谁看谁头疼新增功能必须改核心代码完全不符合我们说的开闭原则。开闭原则到底要解决什么问题我们先回忆一下开闭原则的定义软件实体类、模块、方法等应该对扩展开放对修改关闭——也就是添加新功能的时候尽量不要修改原有的、已经测试好的代码而是通过扩展新代码来实现需求。为什么要这么做核心就是把变化的风险隔离起来​已经上线测试过的稳定代码改得越少出bug的概率越低。对于我们这个图表的例子来说变化点是什么就是不断新增的图表类型——所以我们要把图表变化这个部分抽出来让加新图不用改原来的代码。重构用抽象多态实现开闭原则要实现开闭核心思路就是把变化的部分抽象成抽象层具体实现交给子类扩展​。我们梳理一下重构步骤步骤1抽出抽象图表父层/接口不管是什么类型的图表都要实现display()方法所以我们先定义一个抽象的AbstractChart规定所有图表的统一行为Java// 抽象图表抽象类定义所有图表的公共行为 public abstract class AbstractChart { public abstract void display(); }如果你更喜欢用接口也可以写成接口形式这里用抽象类不影响核心逻辑Javapublic interface Chart { void display(); }步骤2具体图表继承抽象层各自实现原来的PieChart和BarChart都改成抽象层的子类各自实现自己的显示逻辑就好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()就可以Javapublic class ChartDisplay { // 直接接收抽象图表不需要判断类型 public void display(AbstractChart chart) { chart.display(); } }重构完的类图结构就变成了这样┌─────────────────────────────────┐ │ AbstractChart(抽象层) │ │ display() : void │ └─────────────┬───────────────────┘ │ ┌──────┴──────┐ ▼ ▼ ┌───────────┐ ┌───────────┐ │ PieChart │ │ BarChart │ │ display()│ │ display()│ └───────────┘ └───────────┘ ┌────────────────────┐ │ ChartDisplay │ │ display(AbstractChart) │ └────────────────────┘加新图表有多爽看看开闭原则的优势现在我们再回头看刚才的需求要加折线图怎么做新增一个LineChart类继承AbstractChart实现自己的display()——搞定不用改任何原来的代码客户端直接把新的折线图传给ChartDisplay就可以显示。代码写出来就是这样Java// 新增折线图只需要扩展新类不需要改原来的ChartDisplay、其他图表的代码 public class LineChart extends AbstractChart { Override public void display() { System.out.println(显示折线图); } }客户端调用Javapublic 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是稳定的原来的PieChart、BarChart也是稳定的所有稳定代码我们一行都没改只加了新的扩展类完全符合开闭原则。如果以后还要加雷达图、散点图、热力图都是一模一样的流程只加新类不改旧代码从根源上避免了改旧代码出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到设计背后的思路了