• 【23种设计模式】开闭原则


    个人主页金鳞踏雨

    个人简介:大家好,我是金鳞,一个初出茅庐的Java小白

    目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作

    我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~

    本文来自抖音《IT楠老师》设计模式课程,下面是本人结合原课件的一些学习心得。

    一、原理概述

    开闭原则(Open Closed Principle),简写为 OCP。软件实体(模块、类、方法等)应该"对扩展开放、对修改关闭"。一个很明显的例子就是——策略设计模式

    对扩展开放、对修改关闭。

    当我们需要添加一个新的功能时,应该在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

    开闭原则并不是不让我们修改代码,而是让我们尽量避免"大修大改",设计模式是方法,是套路,而不是"枷锁"!!!

    案例分析

    问题代码

    以下是一个常见的生产环境中的例子,我们将展示一个简化的电商平台的订单折扣策略。下述代码是否可以符合开闭原则的定义呢?

    1. class Order {
    2. private double totalAmount;
    3. public Order(double totalAmount) {
    4. this.totalAmount = totalAmount;
    5. }
    6. // 计算折扣后的金额
    7. public double getDiscountedAmount(String discountType) {
    8. double discountedAmount = totalAmount;
    9. if (discountType.equals("FESTIVAL")) {
    10. discountedAmount = totalAmount * 0.9; // 节日折扣,9折
    11. } else if (discountType.equals("SEASONAL")) {
    12. discountedAmount = totalAmount * 0.8; // 季节折扣,8折
    13. }
    14. return discountedAmount;
    15. }
    16. }

    上述代码中,Order 类包含一个计算折扣金额的方法,它根据不同的折扣类型应用折扣。当我们需要添加新的折扣类型时,就不得不需要修改 getDiscountedAmount() 方法的代码,这显然是不合理的,这就违反了开闭原则

    优化

    抽象接口

    1. // 抽象折扣策略接口
    2. interface DiscountStrategy {
    3. double getDiscountedAmount(double totalAmount);
    4. }

    具体策略

    1. // 节日折扣策略
    2. class FestivalDiscountStrategy implements DiscountStrategy {
    3. @Override
    4. public double getDiscountedAmount(double totalAmount) {
    5. return totalAmount * 0.9; // 9折
    6. }
    7. }
    8. // 季节折扣策略
    9. class SeasonalDiscountStrategy implements DiscountStrategy {
    10. @Override
    11. public double getDiscountedAmount(double totalAmount) {
    12. return totalAmount * 0.8; // 8折
    13. }
    14. }
    1. class Order {
    2. private double totalAmount;
    3. private DiscountStrategy discountStrategy;
    4. public Order(double totalAmount, DiscountStrategy discountStrategy) {
    5. this.totalAmount = totalAmount;
    6. this.discountStrategy = discountStrategy;
    7. }
    8. public void setDiscountStrategy(DiscountStrategy discountStrategy) {
    9. this.discountStrategy = discountStrategy;
    10. }
    11. // 计算折扣后的金额
    12. public double getDiscountedAmount() {
    13. return discountStrategy.getDiscountedAmount(totalAmount);
    14. }
    15. }

    在遵循开闭原则的代码中,我们定义了一个抽象的折扣策略接口 DiscountStrategy,然后为每种折扣类型创建了一个实现该接口的策略类。Order 类使用组合的方式,包含一个 DiscountStrategy 类型的成员变量,以便在运行时设置或更改折扣策略,(可以通过编码,配置、依赖注入等形式)。

    这样,当我们需要添加新的折扣类型时,只需实现 DiscountStrategy 接口即可,而无需修改现有的 Order 代码。这个例子遵循了开闭原则。

    二、修改代码就意味着违背开闭原则吗?

    开闭原则的核心思想是要尽量减少对现有代码的修改,以降低修改带来的风险和影响。在实际开发过程中,完全不修改代码是不现实的!当需求变更或者发现代码中的错误时,修改代码是正常的。然而,开闭原则鼓励我们通过设计更好的代码结构,使得在添加新功能或者扩展系统时,尽量减少对现有代码的修改。

    案例分析

    以下是一个简化的日志记录器的示例,展示了在适当情况下修改代码,也不违背开闭原则。

    在这个例子中,我们的应用程序支持将日志输出到控制台和文件。假设我们需要添加一个新功能,以便在输出日志时同时添加一个时间戳

    原始代码

    1. interface Logger {
    2. void log(String message);
    3. }
    4. class ConsoleLogger implements Logger {
    5. @Override
    6. public void log(String message) {
    7. System.out.println("Console: " + message);
    8. }
    9. }
    10. class FileLogger implements Logger {
    11. @Override
    12. public void log(String message) {
    13. System.out.println("File: " + message);
    14. // 将日志写入文件的实现省略
    15. }
    16. }

    为了添加时间戳功能,我们需要修改现有的 ConsoleLogger 和 FileLogger 类。虽然我们需要修改代码,但由于这是对现有功能的改进,而不是添加新的功能,所以这种修改是可以接受的,不违背开闭原则。

    修改后的代码

    1. interface Logger {
    2. void log(String message);
    3. }
    4. class ConsoleLogger implements Logger {
    5. @Override
    6. public void log(String message) {
    7. String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    8. System.out.println("Console [" + timestamp + "]: " + message);
    9. }
    10. }
    11. class FileLogger implements Logger {
    12. @Override
    13. public void log(String message) {
    14. String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
    15. String logMessage = "File [" + timestamp + "]: " + message;
    16. System.out.println(logMessage);
    17. // 将日志写入文件的实现省略
    18. }
    19. }

    在这个例子中,我们只是对现有的日志记录器类进行了适当的修改,以添加时间戳功能。这种修改不会影响到其他部分的代码,因此不违背开闭原则。总之,适当的修改代码并不一定违背开闭原则,关键在于我们如何权衡修改的影响和代码设计。

    三、如何做到 "对扩展开放、修改关闭"?

    开闭原则讲的就是代码的扩展性问题,是判断一段代码是否易扩展的“黄金标准”。

    如果某段代码在应对未来需求变化的时候,能够做到“对扩展开放、对修改关闭”,那就说明这段代码的扩展性比较好。

    在讲具体的方法论之前,我们先来看一些更加偏向顶层的指导思想。为了尽量写出扩展性好的代码,我们要时刻具备扩展意识、抽象意识、封装意识。这些“潜意识”可能比任何开发技巧都重要。

    有些时候,我们有必要思考如下问题:

    • 我要写的这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩展开放、对修改关闭”。
    • 我们还要识别出代码可变部分和不可变部分,要将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。

    需要注意的是,遵循开闭原则并不意味着永远不能修改代码。在实际开发过程中,完全不修改代码是不现实的。开闭原则的目标是要尽量降低修改代码带来的风险和影响,提高代码的可维护性和可复用性。在实际开发中,我们应该根据项目需求和预期的变化来平衡遵循开闭原则的程度。

    文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~

    希望能和大佬们一起努力,诸君顶峰相见

    再次感谢各位小伙伴儿们的支持!!!

  • 相关阅读:
    Java基于SpringBoot的数码论坛系统
    文件包含漏洞
    看我如何连夜自建网站背刺我的求职对手们
    《对比Excel,轻松学习Python数据分析》读书笔记------数据分析简介
    FTTC-BSA-AuNCs 荧光素异硫氰酸酯标记牛血清白蛋白修饰的金簇
    深入理解 Swift Combine
    NewStarCTF2023week2-Unserialize?
    微软官方开源免费的Blazor UI组件库 - Fluent UI Blazor
    字符串数字出现的新功能
    ZTMap是如何在相关政策引导下让建筑更加智慧化的?
  • 原文地址:https://blog.csdn.net/weixin_43715214/article/details/134001698