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

个人主页金鳞踏雨

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

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

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

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

一、原理概述

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

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

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

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

案例分析

问题代码

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

class Order {
    private double totalAmount;

    public Order(double totalAmount) {
        this.totalAmount = totalAmount;
    }

    // 计算折扣后的金额
    public double getDiscountedAmount(String discountType) {
        double discountedAmount = totalAmount;

        if (discountType.equals("FESTIVAL")) {
            discountedAmount = totalAmount * 0.9; // 节日折扣,9折
        } else if (discountType.equals("SEASONAL")) {
            discountedAmount = totalAmount * 0.8; // 季节折扣,8折
        }

        return discountedAmount;
    }
}

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

优化

抽象接口

// 抽象折扣策略接口
interface DiscountStrategy {
    double getDiscountedAmount(double totalAmount);
}

具体策略

// 节日折扣策略
class FestivalDiscountStrategy implements DiscountStrategy {
    @Override
    public double getDiscountedAmount(double totalAmount) {
        return totalAmount * 0.9; // 9折
    }
}

// 季节折扣策略
class SeasonalDiscountStrategy implements DiscountStrategy {
    @Override
    public double getDiscountedAmount(double totalAmount) {
        return totalAmount * 0.8; // 8折
    }
}
class Order {
    private double totalAmount;
    private DiscountStrategy discountStrategy;

    public Order(double totalAmount, DiscountStrategy discountStrategy) {
        this.totalAmount = totalAmount;
        this.discountStrategy = discountStrategy;
    }

    public void setDiscountStrategy(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    // 计算折扣后的金额
    public double getDiscountedAmount() {
        return discountStrategy.getDiscountedAmount(totalAmount);
    }
}

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

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

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

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

案例分析

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

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

原始代码

interface Logger {
    void log(String message);
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Console: " + message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("File: " + message);
        // 将日志写入文件的实现省略
    }
}

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

修改后的代码

interface Logger {
    void log(String message);
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println("Console [" + timestamp + "]: " + message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        String logMessage = "File [" + timestamp + "]: " + message;
        System.out.println(logMessage);
        // 将日志写入文件的实现省略
    }
}

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

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

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

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

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

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

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

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

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

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

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

猜你喜欢

转载自blog.csdn.net/weixin_43715214/article/details/134001698