文章目录

  • 什么是开闭原则
  • 简单实例
  • 实战实例
  • 如何理解“对修改关闭”?修改代码就一定违背开闭原则吗
  • 参考资料

什么是开闭原则

开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

可扩展性,就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。是衡量代码质量最重要的标准之一。

开闭原则理解起来并不难,难的是能够灵活的应用到实际开发工作中。

简单实例

以下代码相信也不陌生,这就是典型的使用面向对象语言做着面向过程的事:

package com.study;public class Ocp {public static void main(String[] args) {GraphicEditor graphicEditor = new GraphicEditor();graphicEditor.drawShape(new Rectangle());graphicEditor.drawShape(new Circle());graphicEditor.drawShape(new Triangle());}
}//这是一个用于绘图的类 [使用方]
class GraphicEditor {//接收 Shape 对象,然后根据 type,来绘制不同的图形public void drawShape(Shape s) {if (s.m_type == 1){drawRectangle(s);} else if (s.m_type == 2){drawCircle(s);} else if (s.m_type == 3){drawTriangle(s);}}//绘制矩形public void drawRectangle(Shape r) {System.out.println(" 绘制矩形 ");}//绘制圆形public void drawCircle(Shape r) {System.out.println(" 绘制圆形 ");}//绘制三角形public void drawTriangle(Shape r) {System.out.println(" 绘制三角形 ");}
}//Shape 类,基类
class Shape {int m_type;
}class Rectangle extends Shape {Rectangle() {super.m_type = 1;}
}class Circle extends Shape {Circle() {super.m_type = 2;}
}//新增画三角形
class Triangle extends Shape {Triangle() {super.m_type = 2;}
}

优化后:

package com.study;public class Ocp {public static void main(String[] args) {GraphicEditor graphicEditor = new GraphicEditor();graphicEditor.drawShape(new Rectangle());graphicEditor.drawShape(new Circle());graphicEditor.drawShape(new Triangle());}
}//这是一个用于绘图的类 [使用方]
class GraphicEditor {//接收 Shape 对象,然后根据 type,来绘制不同的图形public void drawShape(Shape s) {s.draw();}
}//Shape 类,基类
abstract class Shape {abstract void draw();
}class Rectangle extends Shape {@Overridevoid draw() {System.out.println(" 绘制矩形 ");}
}class Circle extends Shape {@Overridevoid draw() {System.out.println(" 绘制圆形 ");}
}//新增画三角形
class Triangle extends Shape {@Overridevoid draw() {System.out.println(" 绘制三角形 ");}
}

实战实例

现在有一个登录逻辑:

public class Login {private AccountService accountService;@Autowarepublic Login (AccountService accountService) {this.accountService = accountService;}public boolean loginCheck(String password, String name) {// 检查账号密码if(!accountService.checkPwd(password, name)){return false;}// 检查登录失败次数if(accountService.getLoginErrorCount() >= 5){return false;}return true;}
}

上述代码相信很多小伙伴都会遇到,看上去似乎并没有什么大问题。

但是,现在新增一个逻辑:登录时,我要校验验证码是否正确,代码可能需要做以下改动:

public class Login {private AccountService accountService;@Autowarepublic Login (AccountService accountService) {this.accountService = accountService;}// 改动1:方法添加参数public boolean loginCheck(String password, String name, String verificationCode) {// 检查账号密码if(!accountService.checkPwd(password, name)){return false;}// 检查登录失败次数if(accountService.getLoginErrorCount() >= 5){return false;}// 改动2:检查验证码if(!accountService.getVerificationCode().equals(verificationCode)){return false;}return true;}
}

这样的代码修改实际上存在挺多问题的。一方面,我们对接口进行了修改,这就意味着调用这个接口的代码都要做相应的修改。另一方面,修改了 loginCheck() 函数内容,相应的单元测试及其他逻辑都需要修改。

接下来我们重构一下Login的代码,让其具备扩展性,重构包括两部分:

  • 第一部分是将 loginCheck() 函数的多个入参封装成 LoginCheckInfo类;
  • 第二部分是引入 handler 的概念,将 if 判断逻辑分散在各个 handler 中。
public class LoginCheckInfo {//省略constructor/getter/setter方法private String name;private String password;private String verificationCode;
}public class Login {private List<LoginCheckHandler> checkHandlers = new ArrayList<>();public void addLoginCheckHandler(CheckHandler checkHandler) {this.checkHandlers.add(checkHandler);}public void check(LoginCheckInfo loginCheckInfo) {for (LoginCheckHandlerhandler : checkHandlers) {boolean result = handler.check(loginCheckInfo);// 处理逻辑}}
}public abstract class CheckHandler {private AccountService accountService;@Autowarepublic CheckHandler (AccountService accountService) {this.accountService = accountService;}public abstract void check(LoginCheckInfo loginCheckInfo);
}public class PwdCheckHandler extends CheckHandler {public PwdCheckHandler(AccountService accountService) {super(accountService);}@Overridepublic boolean check(LoginCheckInfo loginCheckInfo) {return accountService.checkPwd(loginCheckInfo.getPassword(), loginCheckInfo.getName());}
}public class LoginCountCheckHandler extends CheckHandler {public LoginCountCheckHandler(AccountService accountService) {super(accountService);}@Overridepublic boolean check(LoginCheckInfo loginCheckInfo) {return accountService.getLoginErrorCount() >= 5;}
}

要想使用我们重构后的登录逻辑:

@Bean
public Login login(){Login login = new Login();login.addLoginCheckHandler(new PwdCheckHandler());login.addLoginCheckHandler(new LoginCountCheckHandler());
}// 只需要在service层注入login,然后调用login.check();即可

此时,我们如果想要扩展验证码功能:

public class VerificationCodeCheckHandler extends CheckHandler {public VerificationCodeCheckHandler(AccountService accountService) {super(accountService);}@Overridepublic boolean check(LoginCheckInfo loginCheckInfo) {return accountService.getVerificationCode().equals(loginCheckInfo.getVerificationCode());}
}// 然后在定义Login的bean中将这个处理器添加进去即可。
login.addLoginCheckHandler(new VerificationCodeCheckHandler());

重构之后的代码更加灵活和易扩展。如果我们要想添加新的告警逻辑,只需要基于扩展的方式创建新的 handler 类即可,不需要改动原来的 check() 函数的逻辑。而且,我们只需要为新的 handler 类添加单元测试,老的单元测试都不会失败,也不用修改。

如何理解“对修改关闭”?修改代码就一定违背开闭原则吗

对修改关闭的前提是:对扩展开放。

,添加一个新功能,不可能任何模块、类、方法的代码都不“修改”,这个是做不到的。类需要创建、组装、并且做一些初始化操作,才能构建成可运行的的程序,这部分代码的修改是在所难免的。我们要做的是尽量让修改操作更集中、更少、更上层,尽量让最核心、最复杂的那部分逻辑代码满足开闭原则。

也就是说,对拓展开放是为了应对变化(需求),对修改关闭是为了保证已有代码的稳定性;最终结果是为了让系统更有弹性!

添加一个新的功能,如果能够保证老的核心代码不会被修改,那么这就是符合开闭原则的。

熟练使用各种设计模式、并且应用到实际工作中,是我们开发者一生都要去学习的。

但是,熟悉了“开闭原则”,这并不意味着你需要随时随地都要考虑扩展。需求永远是在不断变化的,即便我们对业务、对系统有足够的了解,那也不可能识别出所有的扩展点,即便你能识别出所有的扩展点,为这些地方都预留扩展点,这样做的成本也是不可接受的。我们没必要为一些遥远的、不一定发生的需求去提前买单,做过度设计。

最合理的做法是,对于一些比较确定的、短期内可能就会扩展,或者需求改动对代码结构影响比较大的情况,或者实现成本不高的扩展点,在编写代码的时候之后,我们就可以事先做些扩展性设计。但对于一些不确定未来是否要支持的需求,或者实现起来比较复杂的扩展点,我们可以等到有需求驱动的时候,再通过重构代码的方式来支持扩展的需求。

所以,具体场景还需要具体分析。

参考资料

王争老师的《设计模式之美》
https://blog.csdn.net/m0_54485604/article/details/113502478

设计原则之【开闭原则】相关推荐

  1. 设计模式-软件架构设计七大原则及开闭原则详解

    前言 在日常工作中,我们使用Java语言进行业务开发的时候,或多或少的都会涉及到设计模式,而运用好设计模式对于我而言,又是一个比较大的难题.为了解决.克服这个难题,Remi酱特别开了这个博客来记录自己 ...

  2. Java设计原则之单一职责原则、开闭原则、里氏代换原则

    文章目录 面向对象设计原则概述 单一职责原则 开闭原则 里氏代换原则 面向对象设计原则概述 软件的可维护性(Maintainability)和可复用性(Reusability)是两个非常重要的用于衡量 ...

  3. 七大设计原则之开闭原则

    一.开闭原则介绍 开闭原则(Open Closed Principle)是编程中最基础,也是最重要的设计原则.编程中遵循其他原则以及使用设计模式的目的就是遵循开闭原则. 一个软件实体如类,模块和函数应 ...

  4. 设计原则:开闭原则(OCP)

    1.什么是开闭原则 开闭原则的英文是Open Closed Principle,缩写就是OCP.其定义如下: 软件实体(模块.类.方法等)应该"对扩展开放.对修改关闭". 从定义上 ...

  5. 设计模式-02.经典设计原则-第一节-单一职责原则,开闭原则,里式替换,接口隔离【万字长文系列】

    文章目录 设计模式经典设计原则-第一节 单一职责原则(SRP) 如何理解单一职责原则? 如何判断类的职责是否足够单一? 类的职责是否设计得越单一越好? 开闭原则(OCP) 如何理解"对扩展开 ...

  6. [设计原则] 六大设计原则之“开闭原则”

    [设计原则] 六大设计原则之"开闭原则" 目录 [设计原则] 六大设计原则之"开闭原则" 什么是开闭原则 为什么使用开闭原则 如何使用开闭原则 注意事项 总结 ...

  7. Java面向对象设计原则1——开闭原则

    在我们学习面向对象编程的时候,总会出现一些问题,好比以前刚刚写好的代码,上线测试可以.正常运行,突然有一天说要加一个功能,改完之后,发现以前正常运行的功能不能用了,类似这样的问题有好多好多,为了避免类 ...

  8. 设计模式六大原则之--开闭原则(OCP)

    设计模式六大原则之--开闭原则(OCP) 前言 1 描述 2 理解: 3 问题由来: 4 使用LoD的好处: 5 难点: 6 最佳实践: 7 范例: 前言 The Open - Closed Prin ...

  9. 关于Java面向对象程序设计原则之一——开闭原则的思考与分享

    整理日期:2022-05-27 目录 一.开闭原则 二.为什么使用开闭原则 三.如何在程序设计中体现开闭原则 一.开闭原则 开闭原则(Open-Closed Principle, OCP)是指一个软件 ...

  10. 面向对象设计原则之开闭原则

    两截门--一个被水平分割为两部分的门,这样每一部分都可以独立保持开放或封闭 开放-封闭原则(The Open-Closed Principle) 软件实体(类.模块.函数)应该是可以扩展的,但是不可以 ...

最新文章

  1. spring Bean自动装配
  2. 安卓环境搭建 unable to access Android SDK add-on list解决方案
  3. C#各种加密算法的研究
  4. 追求代码质量: 用 AOP 进行防御性编程
  5. 为什么Go没有三元运算符
  6. 关于××× 相关收集资料
  7. python自定义类的属性_我可以将自定义方法/属性添加到内置Python类型吗?
  8. CI框架PHP漫画小说二合一CMS网站系统源码
  9. 增改删(python 版)
  10. 软件行业正面临一场新的变革——SaaS软件
  11. 用java写一个折半查找_用 Java 写一个折半查找?
  12. win8 不显示计算机,Win8电脑插上U盘不显示盘符怎么办?
  13. python求真分数_Python 列出最简真分数序列*
  14. SSH登录 解决 REMOTE HOST IDENTIFICATION HAS CHANGED问题
  15. java 双冒号用法
  16. 一个字都没写,也能发Nature子刊?
  17. 记录一下自己的本本,DELL 640M铲掉XP,重装VISTA的过程
  18. 华为OD机试 - 火星文计算 2(C++) | 附带编码思路 【2023】
  19. 【转】菜鸟也来打造全自动QQ大家来找茬外挂
  20. 用python爬取网贷之家p2p平台数据

热门文章

  1. Unity3D的Json篇:LitJson.dll插件
  2. PaddleNLP实战:应用NeZha模型做微博情感6分类
  3. 【网页设计】HTML做一个属于我的音乐页面(纯html代码)
  4. 备忘录莫名其妙的没了_华为手机总是多出莫名其妙的照片?一键找到源头,教你彻底删除...
  5. 2010 我的求职经历(3)
  6. docker安装kong和konga并简单使用
  7. PCB高速信号布线技巧
  8. Apache的配置与应用【Apache访问控制】以及apache日志管理【日志分割、awstats日志分析】
  9. VScode Shortcuts for Mac
  10. go基于腾讯云实现发送短信