个人主页:金鳞踏雨
个人简介:大家好,我是金鳞,一个初出茅庐的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);
// 将日志写入文件的实现省略
}
}
在这个例子中,我们只是对现有的日志记录器类进行了适当的修改,以添加时间戳功能。这种修改不会影响到其他部分的代码,因此不违背开闭原则。总之,适当的修改代码并不一定违背开闭原则,关键在于我们如何权衡修改的影响和代码设计。
开闭原则讲的就是代码的扩展性问题,是判断一段代码是否易扩展的“黄金标准”。
如果某段代码在应对未来需求变化的时候,能够做到“对扩展开放、对修改关闭”,那就说明这段代码的扩展性比较好。
在讲具体的方法论之前,我们先来看一些更加偏向顶层的指导思想。为了尽量写出扩展性好的代码,我们要时刻具备扩展意识、抽象意识、封装意识。这些“潜意识”可能比任何开发技巧都重要。
有些时候,我们有必要思考如下问题:
需要注意的是,遵循开闭原则并不意味着永远不能修改代码。在实际开发过程中,完全不修改代码是不现实的。开闭原则的目标是要尽量降低修改代码带来的风险和影响,提高代码的可维护性和可复用性。在实际开发中,我们应该根据项目需求和预期的变化来平衡遵循开闭原则的程度。
文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~
希望能和大佬们一起努力,诸君顶峰相见
再次感谢各位小伙伴儿们的支持!!!