学习设计模式首先要明白所有原则都是为了达到面向对象设计的可扩展可复用可维护性而出现的。对于设计模式的原则,不同的书说法不一样,有的6个有的7个,单一职责原则和接口隔离原则多数只提了一个。
单一职责原则
- 定义
类职责要单一,一个类应该只做一件事情。 - 目的
降低代码复杂度、系统解耦合、提高可读性。单一职责原则告诉我们:一个类不能太“累”!在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作,因此要将这些职责进行分离,将不同的职责封装在不同的类中,即将不同的变化原因封装在不同的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。 - 案例
电线类Wire为居民供电,电压为220v;但是新的需求增加,电线也输送高压电,电压为200kv,原有电线类可以增加方法实现扩充,这就违背了单一职责原则。可以提供基类,创建两个派生类,居民供电线、高压输电线。
单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
开闭原则(OCP)
-
概念
一个软件实体(类、模块和方法)应该对扩展开放,对修改关闭。当功能需要变化的时候,我们应该是通过扩展的方式来实现,而不是通过修改已有的代码来实现。随着软件规模越来越大,软件寿命越来越长,软件维护成本越来越高,设计满足开闭原则的软件系统也变得越来越重要
-
目的
一个已有的代码模块,需要很容易增加新的扩展功能(可扩展性),这个已有模块需要是对外开放的;为了使已有模块可以复用(可复用性),已有模块需要是独立的(单一职责,高内聚,不与其他模块耦合在一起),同时为了方便维护(可维护性),已有模块最好不要对原有代码进行修改,也就是需要是对内封闭的;实现了开闭原则的设计,就达到面向对象设计可扩展可复用可维护性的目的
-
场景分析
一个软件随着时间的推移会不断更新升级,那么在程序进行更新、升级的过程中,我们应该尽量避免在原来代码上进行修改,因为这样会使得原来的程序出现异常或变得不够稳定,并且更改后我们需要对原有的功能进行反复的测试,这样的做法是不提倡的。
-
解决方案
当程序功能需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。
-
程序设计
-
现有如下程序
目前程序支持2种银行的转账,2种银行的转账手续费各不一样,1年开发小白写的代码如下:
//交通银行 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(); bank.transferMoney(); }else if("cbc".equals(type)) { CBCBank bank = new CBCBank(); bank.transferMoney(); } } }
上述代码在线上运行得非常不错,但随着业务的扩大,需要加入新的银行转账功能,此时开发小白说没有问题,三下五除二,加了一个银行,并且更改了
TransferAccount
中的方法,如下://农业银行 class ABCBank { //转账100,扣除手续费3块钱 int transferMoney() { return 100 - 3; } } class TransferAccount { //转账方法 public void transfer(String type) { if("icbc".equals(type)) { ICBCBank bank = new ICBCBank(); bank.transferMoney(); }else if("cbc".equals(type)) { CBCBank bank = new CBCBank(); bank.transferMoney(); //增加了ABC银行的判断 }else if("ABC".equals(type)) { ABCBank bank = new ABCBank(); bank.transferMoney(); } } }
上述代码需要修改transfer已有功能的源码,增加新的逻辑判断,违反了开闭原则,现对该系统进行重构,使其符合开闭原则
-
使用开闭原则后的程序
程序中transfer方法需要针对每一种银行进行硬编程,业务发生变更该代码就要发生改变,故进行调整,使用抽象化的方式对系统进行重构,使得业务扩展时改代码不受影响,具体做法如下
-
增加一个抽象银行类
AbstractBank
,将各种具体的银行类作为其子类;abstract class AbstractBank { abstract int transferMoney(); } //交通银行 class CBCBank extends AbstractBank { int transferMoney() {...} } //建设银行 class ICBCBank extends AbstractBank { int transferMoney() {...} } //农业银行 class ABCBank extends AbstractBank{ int transferMoney() {...} }
-
TransferAccount
类针对抽象银行类进行编程,由用户来决定使用哪种具体银行;class TransferAccount { //转账方法 public void transfer(AbstractBank bank) { System.out.println("转账前的校验"); bank.transferMoney(); System.out.println("转账后的通知"); } }
-
调用程序
TransferAccount transferAccount = new TransferAccount(); //转ICBC银行 transferAccount.transfer(new ICBCBank());
-
-
总结
将程序调整满足开闭原则后,后续代码更新,我们无需再修改
TransferAccount
转账方法,而只需要加入一个新的银行,将新的银行继承AbstractBank
即可
-
-
图解程序
-
开闭原则优点
- 可复用性
- 可维护性
-
注意事项
- 通过接口或者抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法
- 参数类型、引用对象尽量使用接口或者抽象类,而不是具体的实现类
- 抽象层尽量保持稳定,一旦确定即不允许修改,这对开发人员的经验有一定要求