在为某个模块设计内部结构时,分配给该模块的接口及其承载的需求就是其外部表现。使用面向对象方法构造模块内部时,模块内部的类结构及其协作机制就是其内部结构。使用结构化方法构造模块内部时,模块内部的功能分解结构就是其内部结构。
1.内部结构
如果忽视了内部结构的复杂性和坚固性需求,就很容易产生“ N 个职责→ N 个类, M 个接口→ M 个类的方法”的错误设计思路。
如果认识到内部结构还要考虑坚固性和美感,那么就需要综合考虑高内聚、低耦合、OO设计原则等思想重构类结构。
例如,一个网上销售系统要实现订单功能,设计订单类如图1-21所示,承担的职责包括:维护订单的顾客信息、维护订单的商品列表、计算订单总价。
图1-21的设计只考虑了功能职责。如果有如下灵活性需求:订单的总价计算会使用多种计算规则(规则从略),那么图1-22的设计会质量更好一些,它使用了Strategy设计模式,能够提升灵活性和可修改性。
图1-21 订单类设计示例一
图1-22 订单类设计示例二
如果再追加一个安全需求:订单的数据较为敏感,对不同权限的访问者提供不同等级的数据暴露。图1-22的设计方案很难满足该安全需求。一个更好的方案如图1-23所示,它采用了Proxy设计模式,能够很好地满足该安全需求,包括适应该安全需求将来的变更。
图1-23 订单类设计示例三
上面的示例只是一种场景。实践中,为了质量考虑而需要复杂化设计的场景有:
● 为了实现灵活性和可修改性,将类的单个行为或方法封装为独立的类。
● 为了数据保护(安全性),将类的敏感数据封装为独立的类。
● 为了实现可靠性,将可靠性关键的行为和数据封装为独立的类。
● 为了实现高性能,将性能相关的行为和数据封装为独立的类。
● 为了适应对外接口的可能变更,将接口相关的行为封装为独立的类。
● 复杂算法和复杂数据结构往往意味着未来的变更,需要被封装起来。
● 为了服务于多种不同调用者,建立多样的接口。
● 将一个复杂的多职责类拆分为多个单职责类。
2.外部表现
如果忽视了外部表现的抽象和简洁性需要,就会直接将模块内的各个类暴露给外部。如果认识到外部表现需要简洁和抽象,就会慎重定义模块的导入/导出接口,将不应该和不需要暴露的内部结构隐藏起来。
例如,有模块的类结构如图1-24所示,它所实现的功能是家庭影院应用。
图1-24 家庭影院应用的类结构示例一
按照图1-24的设计,如果有模块Client导入(import)了该模块,那么Client可以访问图中的所有方法,Client清楚该模块的所有结构细节。
如果认真考虑模块对外承载的功能需求,可以发现它只有三个职责:看电影、听CD、听收音机。于是可以将模块结构改造为图1-25所示。Client模块只需要导入HomeTheaterFacade类,就能够使用该模块的服务,同时不需要知道该模块的内部结构。
图1-25 家庭影院应用的类结构示例二
相比较之下,图1-25的设计方案质量更好,它可以在完全不考虑Client的情况下任意修改除了HomeTheaterFacade类之外的其他类。而在图1-24的设计方案中,任意一个类被修改都可能连锁影响到Client。
又如,销售应用的界面模块和业务逻辑模块有两种设计方案如图1-26所示。在方案a中,模块之间没有隐藏内部结构,它们之间的耦合就更分散、更高一些。在方案b中,模块之间定义了接口,隐藏了内部结构,它们之间的耦合就更集中、更低一些。
图1-26 销售应用的界面模块和业务逻辑模块设计
最后提醒一下:图1-25的设计方案使用了Facade设计模式,图1-26b的设计方案使用了Controller模式。