内容提要
本文介绍设计模式的定义、四要素,以及按功能(创建型、结构型、行为型)和按方式(类、对象)的分类;重点讲解简单工厂及其改进、单例、适配器、模板方法、策略、组合、装饰器模式,并举例说明其在JUnit、Java IO、Servlet及Spring DI/IOC中的应用。
延伸解读
简单工厂的改进:用抽象换取扩展性
文章指出简单工厂虽让 Client 不依赖具体 Sorter,但增加 Sorter 仍需修改 Factory,违反 OCP。改进一通过写死的类加载获取,使 Factory 无需修改,但 Client 仍要知道实现类,且丧失编译时类型安全。改进二进一步抽象并动态绑定,让 Client 和 Factory 都满足 OCP,代价是类型安全转为运行时检查,且限制实现类只能通过默认构造函数创建。这提示读者:扩展性提升往往伴随类型安全或构造灵活性的取舍。
单例模式:性能与功能需求下的无状态设计
文章从性能(减少频繁创建销毁的资源消耗)和功能(如打印机对象需统一控制)两个角度解释单例的动机。实现上强调私有构造函数和公有获取方法,懒加载需加锁避免多实例,并建议优先用静态类方式。特别提醒单例的成员变量会被多线程重用,可能产生意外结果,因此应尽量设计为无状态对象,只提供服务不保存状态。这为实际使用单例时避免线程安全问题提供了明确指引。
模板方法与策略:继承和组合的扩展取舍
文章将模板方法归为类行为模式,通过继承扩展,基类定义算法骨架,子类实现具体步骤;策略模式归为对象行为模式,通过组合扩展,将条件语句转为多态调用。策略的优点是把使用策略的人与具体实现分离,策略对象可自由组合;但策略不关心何时使用算法,需由客户端决定。模板方法基于继承更易实现,但文章提醒要适当使用。读者可据此根据扩展灵活性和实现复杂度选择。
装饰器与适配器:同为包装器,意图不同
文章指出装饰器和适配器都被称为包装器,但适配器是转换成另一个接口,装饰器是保持接口不变、扩展外围功能。装饰器可动态组合,比继承更灵活,但组合可能产生荒谬结果,增加复杂性。文章还对比装饰器与模板、策略:装饰器保持对象功能不变扩展外围,模板和策略保持算法框架不变扩展内部实现。文中列举 Java IO、Servlet 请求包装和 Collections.synchronizedList 等应用,帮助读者识别实际代码中的装饰器结构。
Q&A
设计模式的定义是什么?它包含哪四个基本要素?
设计模式是一种可重复使用的解决方案,描述了环境中不断出现的问题的通用解决方案。其四个基本要素为:模式名称(少量字组成,有助于表达设计)、待解问题(何时用及运用模式的环境)、解决方案(组成设计的元素、关系、职责及合作,是抽象而非具体实现)、结论(方案带来的利弊,对系统弹性、扩展性和可移植性的影响)。
设计模式按功能和方式分别如何分类?
按功能分为创建模式(对类的实例化过程的抽象)、结构模式(将类或对象结合形成更大结构)、行为模式(对对象间责任和算法的抽象化)。按方式分为类模式(以继承方式实现,静态)和对象模式(以组合方式实现,动态)。
简单工厂模式有什么优点和缺点?如何改进?
优点:使Client不再依赖Sorter的具体实现,对Client实现OCP——增加Sorter不影响Client。缺点:对Factory未实现OCP——增加Sorter需要修改Factory。改进一:通过写死的类加载获取,增加Sorter时无需修改Factory但仍需改Client,且丧失编译时类型安全,Client和Factory均类型不安全,Client仍知道Sorter实现,限制Sorter只能通过默认构造函数创建。改进二:通过抽象和动态绑定(将编译时类型检查转为运行时检查),使Client和Factory均满足OCP,但缺少编译时类型安全,仍限制默认构造函数创建。该机制解决了简单工厂模式最致命的问题。
为什么要使用单例模式?实现时需要注意什么?
使用单例模式保证一个类只产生一个实例,减少实例频繁创建和销毁的资源消耗(性能需求),当多个用户使用同一实例时便于统一控制(功能需求)。实现时必须有私有构造函数,保证实例通过公有方法获取;加锁懒加载初始化需加锁关键字避免多重实例;尽量使用静态类方式;单例中成员变量多线程重用可能产生意外结果,因此尽量设计为无状态对象(只提供服务,不保存状态)。
适配器模式和装饰器模式都叫包装器,它们有什么区别?
适配器用于将现有类的接口转换成所需接口(如JDBC Driver适配具体数据库),是转换成另一个接口;装饰器在不改变对客户端接口的前提下(对客户端透明)扩展现有对象的功能,是保持接口不变。装饰器形成一条链,可动态组合,但可能组合出荒谬结果;适配器不强调链式组合。
模板方法模式和策略模式在扩展功能上有什么不同?
模板方法模式是类的行为模式,通过继承实现扩展:基类负责算法轮廓和骨架,子类负责具体实现。策略模式是对象的行为模式,通过组合实现扩展,将使用策略的人与策略具体实现分离,策略对象可自由组合。模板方法基于继承更易实现但静态;策略基于组合更灵活但客户端需决定何时使用算法。
装饰器模式与继承相比有什么优缺点?
装饰器和继承都可以扩展对象功能,但装饰器是动态的,继承是静态的;装饰器可以任意组合,但这也使装饰器更复杂,有可能组合出荒谬的结果。
设计模式在JUnit、Java IO、Servlet和Spring中有哪些典型应用?
JUnit:模板方法模式(TestCase定义setUp、tearDown等骨架,子类实现测试方法)。Java IO:装饰器模式(流类库通过包装器扩展功能)。Servlet:模板方法模式(HttpServlet定义service方法骨架)和装饰器模式(HttpServletRequestWrapper)。Spring:依赖注入DI和控制反转IOC。