学习内容均来自抖音号 【it楠老师教java】课程。
开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。说人话就是,当我们需要添加一个新的功能时,应该在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。以下是一个常见的生产环境中的例子,我们将展示一个简化的电商平台的订单折扣策略。
你觉得下边的代码有问题吗?
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 {@Overridepublic double getDiscountedAmount ( double totalAmount ) {return totalAmount * 0.9 ; // 9 折}}// 季节折扣策略class SeasonalDiscountStrategy implements DiscountStrategy {@Overridepublic 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 ; }
在遵循开闭原则的代码中,我们定义了一个抽象的折扣策略接口 DiscountStrategy ,然后为每种折扣类型创建了一个实现该接口的策略类。 Order 类使用组合的方式,包含一个 DiscountStrategy 类型的成员变量,以便 在运行时设置或更改折扣策略,(可以通过编码,配置、依赖注入等形式)。这样, 当我们需要添加新的折扣类型时,只需实现 DiscountStrategy 接口即可,而无需 修改现有的 Order 代码。这个例子遵循了开闭原则。
这里相信很多初级程序员看到都会有种恍然大悟的感觉,或者某些中级程序员不注重代码风格的人也会有所顿悟,csdn上写设计模式的人有一大堆,但是没人告诉你怎么用,这个写的确实不错,都是写程序的人了,十几二十块真不算什么,这个老师还是有点水平的。
2、修改代码就意味着违背开闭原则吗
开闭原则的核心思想是要尽量减少对现有代码的修改,以降低修改带来的风险和影 响。在实际开发过程中,完全不修改代码是不现实的。当需求变更或者发现代码中的 错误时,修改代码是正常的。然而,开闭原则鼓励我们通过设计更好的代码结构,使 得在添加新功能或者扩展系统时,尽量减少对现有代码的修改。 以下是一个简化的日志记录器的示例,展示了在适当情况下修改代码,也不违背开闭 原则。在这个例子中,我们的应用程序支持将日志输出到控制台和文件。假设我们需 要添加一个新功能,以便在输出日志时同时添加一个时间戳。
interface Logger {void log ( String message );}class ConsoleLogger implements Logger {@Overridepublic void log ( String message ) {System . out . println ( "Console: " + message ); }}class FileLogger implements Logger {@Overridepublic void log ( String message ) {System . out . println ( "File: " + message );// 将日志写入文件的实现省略}}
为了添加时间戳功能,我们需要修改现有的 ConsoleLogger 和 FileLogger 类。 虽然我们需要修改代码,但由于这是对现有功能的改进,而不是添加新的功能,所以 这种修改是可以接受的,不违背开闭原则。
interface Logger {void log ( String message );}class ConsoleLogger implements Logger {@Overridepublic void log ( String message ) {String timestamp =LocalDateTime . now (). format ( DateTimeFormatter . ISO_LOCAL_DATE_TIME );System . out . println ( "Console [" + timestamp + "]: " + message );}}class FileLogger implements Logger {@Overridepublic void log ( String message ) {String timestamp =LocalDateTime . now (). format ( DateTimeFormatter . ISO_LOCAL_DATE_TIME );String logMessage = "File [" + timestamp + "]: " + message ;System . out . println ( logMessage );// 将日志写入文件的实现省略}}
在这个例子中,我们只是对现有的日志记录器类进行了适当的修改,以添加时间戳功能。这种修改不会影响到其他部分的代码,因此不违背开闭原则。总之,适当的修改 代码并不一定违背开闭原则,关键在于我们如何权衡修改的影响和代码设计。
当我们遵循开闭原则时,其目的是为了让我们的代码更容易维护、更具可复用性,同时降低了引入新缺陷的风险。但是,在某些情况下,遵循开闭原则可能会导致过度设计,增加代码的复杂性。因此,在实际开发中,我们应该根据实际需求和预期的变化来平衡遵循开闭原则的程度。写代码不是为了设计而设计,脱离需求谈设计都是耍流氓,有些场景,比如项目的使用频率不高,修改的可能性很低,或者代码本来就很简单使用了设计模式可能会增加开发难度,提升开发成本,反而得不偿失。