先来看一段代码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | 
 
 
 
 
 
 
 
 public boolean transfer(String payer, String payee, String money) {
 Log.info("transfer start, payer = " + payer + ", payee = " + payee + ", money = " + money);
 
 
 if (!isValidUser(payer) || !isValidUser(payee) || !isValidMoney(money)) {
 return false;
 }
 
 
 TransferResult transferResult = transferService.transfer(payer, payee, money);
 if (!transferResult.isSuccess()) {
 return false;
 }
 
 
 UserInfo userInfo = userInfoService.getUserInfo(payee);
 if (userInfo.getNotifyType() == NotifyTypeEnum.SMS) {
 
 smsClient.sendSms(payee, NOTIFY_CONTENT);
 } else if (userInfo.getNotifyType() == NotifyTypeEnum.MAIL) {
 
 mailClient.sendMail(payee, NOTIFY_CONTENT);
 }
 
 
 biiiService.sendBiii(transferResult);
 
 
 monitorService.sendRecord(transferResult);
 
 
 quotaService.recordQuota(transferResult);
 
 Log.info("transfer success");
 return true;
 }
 
 | 
是不是感觉还行?但其实就扩展性而言,它是有很多问题的。
出参和入参的扩展性
问题:出入参不具备扩展性,且有巨坑
| 12
 3
 
 | public boolean transfer(String payer, String payee, String money) {
 }
 
 | 
解决措施:封装请求
封装入参,校验方法接收一个封装好的请求对象。
| 12
 3
 4
 5
 6
 
 | public boolean transfer(TransferRequest transferRequest) {Log.info("transfer start, transferRequest=" + transferRequest);
 
 
 validatorManager.checkParam(transferRequest);
 }
 
 | 
校验的扩展性
问题:随着参数新增,校验会变得越来越多
| 12
 3
 
 | if (!isValidUser(payer) || !isValidUser(payee) || !isValidMoney(money)) {
 }
 
 | 
解决:责任链模式
把具体的逻辑放在具体的类里去实现,然后串接起来执行
1.定义校验接口
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 
 
 public interface ParamValidator {
 
 
 
 void checkParam(TransferRequest transferRequest);
 }
 
 | 
2.实现校验类
3.实现校验管理器
使用Spring框架,当ValidatorManager这个bean在初始化以后,会去整个spring上下文把所有ParalValidator的实现类的bean都捞出来,放到一个list里去。
他还会对外暴露一个checkParam的方法,当各种各样的服务去调用checkParam方法的时候,它就会去循环调用所有校验的实现类去校验入参
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class ValidatorManager implements InitializingBean {@Autowired
 private ApplicationContext applicationContext;
 
 private List<ParamValidator> validatorList = new ArrayList<>();
 
 @Override
 public void afterPropertiesSet() throws Exception {
 Map<String, ParamValidator> paramValidatorMap = applicationContext.getBeansOfType(ParamValidator.class);
 validatorList = new ArrayList<>(paramValidatorMap.values());
 }
 
 public void checkParam(TransferRequest transferRequest) {
 for (ParamValidator paramValidator : validatorList) {
 paramValidator.checkParam(transferRequest);
 }
 }
 }
 
 | 
这样新增参数或者修改旧参数校验逻辑,无需修改transfer方法。
只需要去实现一个新的校验器,(用刚才从Spring上下文加载的方式)自然而然的就会被纳入到校验管理器的管理范畴
更多优化点
在校验器上面还可以打上@Order注解,这个@Order注解可以自定义。ValidatorManager去捞取所有实现类的时候,可以根据上面的汪解去判断执行逻辑的顺序(其实就有一个编排的概念在里边了)
渠道的扩展性
问题:增加通知方式就要加if-else
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | UserInfo userInfo = userInfoService.getUserInfo(payee);
 if (userInfo.getNotifyType() == NotifyTypeEnum.SMS) {
 
 smsClient.sendSms(payee, NOTIFY_CONTENT);
 } else if (userInfo.getNotifyType() == NotifyTypeEnum.MAIL) {
 
 mailClient.sendMail(payee, NOTIFY_CONTENT);
 }
 
 | 
解决:适配器模式
1.定义通知接口
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 
 
 public interface NotifyService {
 
 
 
 void notifyMessage(String userId, String content);
 }
 
 | 
2.适配各种通知
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | @Servicepublic class MailNotifyService implements NotifyService {
 @Autowired
 private MailClient mailClient;
 
 public void notifyMessage(String userId, String content) {
 
 mailClient.sendMail(userId, content);
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | @Servicepublic class SmsNotifyService implements NotifyService {
 @Autowired
 private SmsClient smsClient;
 
 public void notifyMessage(String userId, String content) {
 
 smsClient.sendSms(userId, content);
 }
 }
 
 | 
3.实现通知管理器
通知管理器就是在初始化完了以后,会获取到所有被注入的通知服务。
然后,根据不同的这个通知类型去映射成map,对外提供notify方法.
通知的时候,需要传进来具体的通知方式、通知的用户、具体的通知内容。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | @Servicepublic class NotifyServiceManager implements InitializingBean {
 @Autowired
 private SmsNotifyService smsNotifyService;
 
 @Autowired
 private MailNotifyService mailNotifyService;
 
 private final Map<NotifyTypeEnum, NotifyService> notifyServiceMap = Maps.newHashMap();
 
 public void afterPropertiesSet() throws Exception {
 
 notifyServiceMap.put(NotifyTypeEnum.SMS, smsNotifyService);
 notifyServiceMap.put(NotifyTypeEnum.MAIL, mailNotifyService);
 }
 
 public void notify(NotifyTypeEnum notifyTypeEnum, String userId, String content) {
 NotifyService notifyService = notifyServiceMap.get(notifyTypeEnum);
 if (notifyService == null) {
 throw new RuntimeException("Notify service not exist");
 }
 
 notifyService.notifyMessage(userId, content);
 }
 }
 
 
 | 
4.替换原先实现
| 12
 3
 4
 
 | UserInfo userInfo = userInfoService.getUserInfo(transferRequest.getPayeeId);
 NotifyTypeEnum notifyTypeEnum = userInfo.getNotifyType();
 notifyServiceManager.notify(notifyTypeEnum, transferRequest.getPayeeId(), NOTIFY_CONTENT);
 
 | 
这样,新增通知也无需修改transfer方法
增加一个后置动作就要修改这个核心业务(转账方法 )的代码  
| 12
 3
 4
 5
 6
 
 | biiiService.sendBiii(transferResult);
 
 monitorService.sendRecord(transferResult);
 
 quotaService.recordQuota(transferResult);
 
 | 
业务的扩展性
问题:业务变动需要修改核心代码
增加一个后置动作就要修改这个核心业务(转账方法 )的代码  
| 12
 3
 4
 5
 6
 
 | biiiService.sendBiii(transferResult);
 
 monitorService.sendRecord(transferResult);
 
 quotaService.recordQuota(transferResult);
 
 | 
解决:观察者模式
1.定义观察者接口
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 
 
 public interface TransferObserver {
 
 
 
 void update(TransferResult transferResult);
 }
 
 | 
2.实现各种观察者
3.实现主题订阅
主题订阅和之前实现的管理器是相似的,也是在bean初始化以后,从上下文捞出所有的观察者接口的实现类。然后对外提供notifyObserver,也就是触发观察者的方法,在里边去执行每一个观察者
在触发观察者的行为时,观察者的执行可以全部放到异步线程池去执行(如下)
这代表了和责任链模式的几个不同点:
- 所有观察者的执行之间是不会相互影响的(达到了解耦)
- 观祭者执行是可以做到并行的(因为解耦)
- 观察者是在主业务执行完后才触发的。观察者逻辑不会再影响主业务逻辑
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | @Componentpublic class TransferSubject implements InitializingBean {
 @Autowired
 private ApplicationContext applicationContext;
 
 private final List<TransferObserver> transferObserverList = new ArrayList<>();
 
 
 private final ExecutorService executorService = Executors.newFixedThreadPool(10);
 
 @Override
 public void afterPropertiesSet() throws Exception {
 Map<String, TransferObserver> transferObserverMap =
 applicationContext.getBeansOfType(TransferObserver.class);
 
 transferObserverMap.values().forEach(this::addObserver);
 }
 
 
 
 
 public void notifyObservers(TransferResult transferResult) {
 transferObserverList.forEach(transferObserver -> {
 
 executorService.execute(() -> transferObserver.update(transferResult));
 });
 }
 
 
 
 
 public void addObserver(TransferObserver transferObserver) {
 transferObserverList.add(transferObserver);
 }
 }
 
 | 
4.替换原来逻辑
| 12
 3
 4
 5
 
 | transferSubject.notifyObserver(transferResult)
 
 log.info("transfer success")
 return true;
 
 | 
这样,新增观察者也无需修改transfer方法
策略模式
策略模式就是定义算法族并封装,让他们可以相互替换,算法独立
假设你开发了一个游戏,游戏里面有多种角色,包括:剑士、射手、医生、护士等。剑士和射手是战斗职业,可以打怪也可以对打。医生和护士是治疗职业,负责给其他角色治疗。
一种典型的设计方法如下:

Role包含了每个角色都需要的属性,实现了每个角色都需要的功能。其他角色继承Role即可。
你一定注意到这里有个明显的问题,那就是Warrior和Shooter会有攻击(attack)行为,而Doctor和Nurse有治疗(cure)行为,并且这些行为都是相似的。
一种实现方式是,把这些行为都放在Role中,就会变成如下这样:

这有个明显的问题,那就是图中所描述的“一些类拥有了自己不需要的行为”。
为了解决这个问题,你也许会这样来修改类设计:

中间增加了Fighter和MedicalStaff这一层,就可以把相似的行为提取出来了,是不是很完美?
那么,问题来了。如果这个时候来了一个新的职业叫做“法师(Wizard)”,这个职业又可以战斗又可以治疗怎么办呢?

明显,Wizard无论是继承Fighter还是MedicalStaff都不合适。
那是不是要建一个FighterAndMedicalStaff类?这显然是更不合适的。为什么呢?
其一,如果以后职业越来越多,这些职业的技能组合会使得FighterAndMedicalStaff这样的类越来越多。
其二,FighterAndMedicalStaff不继承Fighter和MedicalStaff的话,attack和cure方法就要重复写了。完全没有复用性可言。
所以,上面这种设计在扩展性上就显得力不从心。
那要怎么设计呢?那就是:【使用组合而非继承】。
上面的设计中,我们使用继承的目的是为了复用行为。例如战士(Warrior)和射手(Shooter)继承Fighter来复用战斗(attack)行为。
但是复用战斗行为并不一定要使用继承,也可以使用组合。我们把【战斗】和【治疗】两种行为抽成接口,然后实现各种不同的战斗和治疗行为。每个角色根据需要去组合这些行为即可。类图如下:

通过上面这样的设计,每个角色的行为就可以非常方便地组合。你不难想象,如果某一天要求Doctor也可以战斗,扩展起来非常简单。
装饰者模式
装饰者模式可以动态将能力扩展到对象上
还是那个游戏,里面有几种武器,现在多了绿宝石剑、红宝石剑和绿宝石法杖、红宝石法杖,该怎么设计?
- 剑初始100攻击力法杖初始80攻击力
- 绿宝石+10攻击力、红宝石+20攻击力
武器
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | 
 
 public interface Weapon {
 
 
 
 int damage();
 }
 
 public class Staff implements Weapon {
 @Override
 public int damage() {
 return 80;
 }
 }
 
 public class Sword implements Weapon {
 @Override
 public int damage() {
 return 100;
 }
 }
 
 | 
武器装饰
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 
 | 
 
 public interface WeaponDecorator extends Weapon {
 }
 
 
 
 
 public class AttackBuff implements WeaponDecorator {
 private Weapon weapon;
 
 public AttackBuff(Weapon weapon) {
 this.weapon = weapon;
 }
 
 @Override
 public int damage() {
 
 return this.weapon.damage() * 2;
 }
 }
 
 
 
 
 public class GreenDiamond implements WeaponDecorator {
 private Weapon weapon;
 
 public GreenDiamond(Weapon weapon) {
 this.weapon = weapon;
 }
 
 @Override
 public int damage() {
 
 return this.weapon.damage() + 10;
 }
 }
 
 
 
 
 public class RedDiamond implements WeaponDecorator {
 private Weapon weapon;
 
 public RedDiamond(Weapon weapon) {
 this.weapon = weapon;
 }
 
 @Override
 public int damage() {
 
 return this.weapon.damage() + 20;
 }
 }
 
 | 
调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | public static void main(String[] args) {Weapon sword = new Sword();
 
 
 sword = new GreenDiamond(sword);
 
 
 sword = new RedDiamond(sword);
 
 
 sword = new AttackBuff(sword);
 
 
 System.out.println(sword.damage());
 }
 
 | 
总结
从下面常用的七种常用的设计模式中:
- 责任链模式
- 适配器模式
- 观察者模式
- 策略模式
- 装饰器模式
- 模板方法模式
- 代理模式
我们可以学到:软件设计的原则:
- 只做一件事:责任链模式、适配器模式、观察者模式
- 依赖抽象而非具体:责任链模式、适配器模式、观察者模式、代理模式
- 对扩展开放,对修改关闭:责任链模式、适配器模式、观察者模式、策略模式、装饰器模式、代理模式
- (仅)对变化封装:策略模式、模板方法模式
- 组合优于继承:策略模式
参考资料
[^1]: 【学架构也可以很有趣】【“趣”学架构】- 6.还得是“设计模式”
[^2]: 【成为架构师】12. 成为工程师 - 如何提高代码的扩展性?