目录
对于设计模式的原则,不同的书说法不一样,有的6个有的7个,单一职责原则和接口隔离原则多数只提了一个。
开闭原则为最重要的面向对象设计原则,它是面向对象的可复用设计的第一块基石。其他几条,则可以看作是开闭原则的实现方法。设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。
单一职责原则
类职责要单一,一个类应该只做一件事情。
开闭原则(OCP)
- 概念
一个软件实体(类、模块和方法)应该对扩展开放,对修改关闭。当功能需要变化的时候,我们应该是通过扩展的方式实现,而不是通过修改已有的代码来实现。随着软件规模越来越大,软件寿命越来越长,软件维护成本越来越高,设计满足开闭原则的软件系统也变得越来越重要。
- 场景分析
一个软件随着时间的推移会不断更新升级,那么在程序进行更新、升级的过程中,我们应该尽量避免在原来代码上进行修改,因为这样会使得原来的程序出现异常或变得不够稳定,并且更改后我们需要对原有的功能进行反复的测试,这样的做法是不提倡的。
- 解决方案
当程序功能需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。
- 程序设计
○ 现有如下程序
目前程序支持两种银行的转账,两种银行的转账手续费各不一样,1年开发小白写的代码如下:
public class Test {
public static void main(String[] args) {
TransferAccount transferAccount = new TransferAccount();
transferAccount.transfer("icbc");
}
}
//交通银行
class CBCBank {
//转账100,扣除手续费1块钱
int transferMoney() {
return 100 - 1;
}
}
//工商银行
class ICBCBank {
//转账100,扣除手续费2块钱
int transferMoney() {
return 100 - 2;
}
}
//转账
class TransferAccount {
//转账方法
public void transfer(String type) {
if ("icbc".equals(type)) {
ICBCBank bank = new ICBCBank();
int i = bank.transferMoney();
System.out.println("icbc银行转账了:" + i + "块钱");
} else if ("cbc".equals(type)) {
CBCBank bank = new CBCBank();
int i = bank.transferMoney();
System.out.println("cbc银行转账了:" + i + "块钱");
}
}
}
上述代码在线上运行的非常不错,但随着业务的扩大,需要加入新的银行转账功能,此时开发小白说没有问题,三下五除二,加了一个银行,并且更改了TransferAccount的方法,如下:
public class Test {
public static void main(String[] args) {
TransferAccount transferAccount = new TransferAccount();
transferAccount.transfer("icbc");
}
}
//交通银行
class CBCBank {
//转账100,扣除手续费1块钱
int transferMoney() {
return 100 - 1;
}
}
//工商银行
class ICBCBank {
//转账100,扣除手续费2块钱
int transferMoney() {
return 100 - 2;
}
}
//农业银行
class ABCBank {
//转账100,扣除手续费3块钱
int transferMoney() {
return 100 - 3;
}
}
//转账
class TransferAccount {
//转账方法
public void transfer(String type) {
if ("icbc".equals(type)) {
ICBCBank bank = new ICBCBank();
int i = bank.transferMoney();
System.out.println("icbc银行转账了:" + i + "块钱");
} else if ("cbc".equals(type)) {
CBCBank bank = new CBCBank();
int i = bank.transferMoney();
System.out.println("cbc银行转账了:" + i + "块钱");
} else if ("abc".equals(type)) {
//产品经理说,小白,今天功能要增加了,加了一个ABCBank转账
ABCBank bank = new ABCBank();
bank.transferMoney();
//问题:每次新增功能都会修改TransferAccount中的转账逻辑,如果不小心把方法改坏了,里面之前支持的转账功能都不好使了,
//本来只是想增加一个新的功能,结果影响到了老的功能,造成老的功能就要进行反复的测试。此时就违反了开闭原则,开闭原则是
//扩展不是新增,尤其不可以修改现有的方法。
}
}
}
上述代码需要修改transfer已有功能的源码,增加新的逻辑判断,违反了开闭原则,现对该系统进行重构,使其符合开闭原则。
○ 使用开闭原则后的程序
程序中transfer方法需要对每一种银行进行硬编程,业务发生变更该代码就要发生改变,故进行调整,使用抽象化的方法对系统进行重构,使得业务扩展时改代码不受影响,具体做法如下
增加一个抽象银行类AbstractBank,将各种具体的银行类作为其子类;
public class Test {
public static void main(String[] args) {
TransferAccount transferAccount = new TransferAccount();
transferAccount.transfer(new CBCBank());
}
}
//交通银行
class CBCBank extends AbstractBank {
//转账100,扣除手续费1块钱
int transferMoney() {
return 100 - 1;
}
}
//工商银行
class ICBCBank extends AbstractBank {
//转账100,扣除手续费2块钱
int transferMoney() {
return 100 - 2;
}
}
//农业银行
class ABCBank extends AbstractBank {
//转账100,扣除手续费3块钱
int transferMoney() {
return 100 - 3;
}
}
//抽象类、接口都可以,反正需要做到足够抽象
abstract class AbstractBank {
abstract int transferMoney();
}
//转账账户
class TransferAccount {
//转账方法
public void transfer(AbstractBank bank) {
System.out.println("转账前的校验");
int i = bank.transferMoney();
System.out.println("转账了:" + i + "块钱");
System.out.println("转账后的通知");
}
}
总结:
将程序调整满足开闭原则后,后续代码更新,我们无需再修改TransferAccount转账方法,而只需要加入一个新的银行,将新的银行继承AbstractBank即可。
- 开闭原则优点
○ 可复用性
○ 可维护性
- 注意事项
○ 通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法
○ 参数类型、引用对象尽量使用接口或者抽象类,而不是具体的实现类
○ 抽象层尽量保持稳定,一旦确定即不允许修改,这对开发人员的经验有一定要求
资料来源:B站视频https://www.bilibili.com/video/BV17z4y117qn?spm_id_from=333.999.0.0