JAVA面向对象编程
第 4章 模式:Spring前行的路标
在上一章中研究了 Spring 的一些核心理念,这些理念有的是编程原则和信条,有的是自成
一派的方法论。本章将讲述 Spring与模式的知识。如果读者已经熟悉 Spring 和模式的基本
关系,可以直接略过本章,进入后续篇章。相反,如果没有听说过模式,可以通过本章彻底
了解模式核心。
4.1 模式(Pattern)入门
4.1.1 什么是模式
琼瑶小说就是一种模式,是一种“催泪模式”,让一个女读者流眼泪简单,难的是让千千万
万个女读者都流眼泪,可见模式之威力所在。...
第 4章 模式:Spring前行的路标
在上一章中研究了 Spring 的一些核心理念,这些理念有的是编程原则和信条,有的是自成
一派的方法论。本章将讲述 Spring与模式的知识。如果读者已经熟悉 Spring 和模式的基本
关系,可以直接略过本章,进入后续篇章。相反,如果没有听说过模式,可以通过本章彻底
了解模式核心。
4.1 模式(Pattern)入门
4.1.1 什么是模式
琼瑶小说就是一种模式,是一种“催泪模式”,让一个女读者流眼泪简单,难的是让千千万
万个女读者都流眼泪,可见模式之威力所在。于是乎,依照琼瑶小说的催泪模式,制作了影
视作品,毋庸置疑,依照催泪模式制作的影视作品必有催泪作用,如图 4.1所示。
图 4.1 模式比喻
可以看出,琼瑶写小说,发现了很多催泪元素,通过写作经验的逐步积累,从而总结出了一
套“催泪模式”,而这样的模式在场景 1中被发明,随后可以将其应用到类似的场景中,而
不需要在其他场景中从头来过,这就是模式的魅力所在。
4.1.2 不用模式产生的问题
本书中探讨的模式是针对面向对象软件的。
出可复用度高、具有足够通用性的软件绝非
易事,缺乏经验的研发者遇到每个开发环节,会给出诸如“先 A后 B 再 C”的解决方案,
依照逻辑来看诚然没错。但是如果使用这样的思维进行编码,那么抛开灵活性差、耦合度高
不说,它还存在着巨大隐患。
1.面向过程的编码
代码 4.1中关于最笨的钟点工代码,被称为非面向对象思维(一般指过程化思维)和拷贝式
复用(相同的代码到处粘贴拷贝以形成复用),如下:
代码 4.1 NonOOThinkingAndReuse.java
package chapter4.pattern;
public class NonOOThinkingAndReuse {
/**
* 一套做杂务的脚本方法
*/
private static void makeOddJob() {
System.out.println("洗衣服");
System.out.println("擦窗");
System.out.println("烹调");
System.out.println("洗碗");
}
public static void main(String[] args) throws Throwable {
System.out.println("洗衣服,烹调,洗碗,擦窗这些杂务需要钟点工们打理");
//最笨的钟点工做杂务
System.out.println("最笨的钟点工");
System.out.println("来到雇主家,首次开始做杂务...");
System.out.println("洗衣服");
System.out.println("擦窗");
System.out.println("烹调");
System.out.println("洗碗");
System.out.println("雇主付钱");
System.out.println("来到雇主家,第二次开始做杂务...");
//程序员甲通过复制上述的一大段代码,到下面,结果漏了洗碗
System.out.println("洗衣服");
System.out.println("擦窗");
System.out.println("烹调");
System.out.println("雇主吃饭使用了脏碗,一怒之下解雇了马虎的钟点工");
System.out.println("啊,被解雇了,我只是忘记了洗碗,真不值!");
System.out.println();
//稍微聪明一点的钟点工
System.out.println("稍微聪明一点的钟点工");
/*
* 程序员乙通过将脚本式的 System out代码重构到一个 makeOddJob方法中,
* 迫使钟点工记住所有的工作内容和次序,大大降低了出错的概率,也提高了可重用性和灵活
性,只要修改
* 一个方法就可以修改所有做杂务的方式
*/
System.out.println("来到雇主家,首次开始做杂务...");
makeOddJob();
System.out.println("来到雇主家,第二次开始做杂务...");
makeOddJob();
System.out.println("雇主付钱");
System.out.println("轻松完成任务,赚到钱了");
}
}
可以看到通过对笨钟点工编码方式的优化,聪明钟点工复用了 makeOddJob()方法,这是一
个不小的进步。然而存在的一个大缺点是 makeOddJob()方法作为一个类的私有内嵌方法,
被永远留在了这个 NonOOThinkingAndReuse类中。如果代码中所描述的笨钟点工某天想使
用聪明钟点工的方式做事,或者说,程序员丙想使用程序员乙已经发明过的 makeOddJob()
方法,那是不可能的。因为这些脚本已经留在了某个类中,成为了历史而很难被追溯,所以
程序员丙又必须重新思考编码。
2.面向对象的编码
现在就用面向对象的思维来组织编码,代码 4.2~4.6所示。
代码 4.2 OOThinkingAndReuse.java
public class OOThinkingAndReuse {
public static void main(String[] args) {
//同一个雇主
Master master = new Master();
//同样的杂务
OddJob oddJob = new OddJob();
//最笨的钟点工
StupidHourWorker worker1 = new StupidHourWorker(oddJob);
//聪明一点的钟点工
CleverHourWorker worker2 = new CleverHourWorker(oddJob);
//雇主根据钟点工的工作表现,有不同的反应
master.process(worker1.doJob());
master.process(worker2.doJob());
}
}
代码 4.3 OddJob.java
/**
* 杂务
*/
public class OddJob {
private boolean isAllDone = false;
public OddJob() {
description();
}
public boolean isAllDone() {
return isAllDone;
}
/**
* 一套做杂务的完整方法
*/
public void doItAll() {
System.out.println("洗衣服");
System.out.println("擦窗");
System.out.println("烹调");
System.out.println("洗碗");
isAllDone = true;
}
/**
* 缺少洗碗的一套做杂务的方法
*/
public void doItExceptWashDish() {
System.out.println("洗衣服");
System.out.println("擦窗");
System.out.println("烹调");
isAllDone = false;
}
public void description() {
System.out.println("洗衣服,烹调,洗碗,擦窗这些杂务需要钟点工们打理");
}
}
代码 4.4 Master.java
import chapter4.pattern.better.Worker;
/**
* 雇主类
*/
public class Master {
public void pay() {
System.out.println("雇主付钱");
}
public void fire() {
System.out.println("雇主吃饭使用了脏碗,解雇了钟点工");
}
public void process(boolean isJobAllDone) {
if (isJobAllDone) {
pay();
} else {
fire();
}
}
public void process(Worker worker) {
if (worker.doJob()) {
pay();
} else {
fire();
}
}
}
代码 4.5 StupidHourWorker.java
/**
* 笨钟点工类
*/
public class StupidHourWorker {
private OddJob oddJob;
private static String BAD_FEELING = "啊,被解雇了,我只是忘记了洗碗,真不值!";
public StupidHourWorker(OddJob oddJob) {
this.oddJob = oddJob;
}
public boolean doJob() {
System.out.println("最笨的钟点工");
System.out.println("来到雇主家,开始做杂务...");
oddJob.doItExceptWashDish();
return oddJob.isAllDone();
}
public void feel() {
System.out.println(BAD_FEELING);
}
}
代码 4.6 CleverHourWorker.java
/**
* 聪明一点的钟点工类
*/
public class CleverHourWorker {
private OddJob oddJob;
private static String GOOD_FEELING = "轻松完成任务,赚到钱了";
public CleverHourWorker(OddJob oddJob) {
this.oddJob = oddJob;
}
public boolean doJob() {
System.out.println("聪明一点的钟点工");
System.out.println("来到雇主家,开始做杂务...");
oddJob.doItAll();
return oddJob.isAllDone();
}
public void feel() {
System.out.println(GOOD_FEELING);
}
}
代码 4.3~4.6都是记载有某种属性和行为的特定类,当需要使用的时候,Java语言通过 new
关键字分配内存并产生这些对象,如代码 4.2所示。这样使得任何一个对象实例都可以进行
独立复用。当采用了面向对象的编程方式以后,人和事物被模拟成了类的交互形式,从而使
得对事物的描述更直观易懂了,只需要短短几行代码,就可以轻松表达代码 4.1的逻辑。
如果把对象的职责分配得更贴切一些,形成一定的模式,并加以命名后(比如代码 4.2),一
旦遇上相同问题就可以进行套用,可以使笨钟点工立即变成聪明钟点工。
4.1.3 通过实例理解模式本质
本质上来说,可以复用的东西都可称之为模式。模式的一些基本要素包括:模式名称、问题
(何时使用模式)、解决方案和模式效果。
模式的粒度将决定模式的应用范围和强弱能力,可以发现,代码 4.3~4.6中,这些类的粒度
都非常细,粒度细就是所描述事物太具体,限定了这些事物的应用范围,例如只能让
StupidHourWorker 做马虎的杂务活,却不能把 StupidHourWorker 当 CleverHourWorker 来使
用,所以笔者以为这个两个模式粒度太细所以应用范围太窄,功能太弱。
思考一下,如果将 StupidHourWorker和 CleverHourWorker都作为Worker抽象类(注:此处
的抽象类不是特指 Abstract class,而是指具有抽象能力的类,因此也可以是 Interface)的实
现。依靠这样的解决方案,适用场景将会更广,模式效果就会好许多,如图 4.2所示。
图 4.2 类继承和模式抽象
列出抽象化后的代码,如代码 4.7~4.10所示。
代码 4.7 BetterReuse.java
import chapter4.pattern.better.ClerverHourWorker;
import chapter4.pattern.better.StupidHourWorker;
import chapter4.pattern.better.Worker;
public class BetterReuse {
public static void main(String[] args) {
//有一个笨钟点工
Worker worker = new StupidHourWorker();
//笨钟点工忽然变聪明了,或者想采用聪明钟点工的工作方式
worker = new ClerverHourWorker();
//还是同一个雇主
Master master = new Master();
//雇主并不需要知道钟点工聪明还是笨,自己会处理
master.process(worker);
}
}
代码 4.8 Worker.java
package chapter4.pattern.better;
public interface Worker {
boolean doJob();
void feel();
}
代码 4.9 ClerverHourWorker.java
package chapter4.pattern.better;
import chapter4.pattern.Worker;
public class ClerverHourWorker implements Worker {
public boolean doJob() {
//此处省略
return true;
}
public void feel() {
//此处省略
}
}
代码 4.10 StupidHourWorker.java
import chapter4.pattern.Worker;
public class StupidHourWorker implements Worker {
public boolean doJob() {
//此处省略
return false;
}
public void feel() {
//此处省略
}
}
如图 4.2和代码 4.7~4.10所示,从模式复用的角度出发,Worker越抽象,粒度就越粗,复用
机会也就越大。可以想象一下,代码 4.7 中,Worker 的句柄(Handler)分别被作为局部变
量和 process()方法的参数进行了复用。在其他场景需要的时候,Worker 类还可以被继承或
者组合复用。在此处,因为找到了复合依据,就可以称这个模式为 Worker模式。这些场景
依赖于 Worker模式工作,而完全不知道子类的实现方式,也意味着在历史的某个点上,比
如代码 4.7的 main函数中,通过 worker = new ClerverHourWorker()这行代码,在静态编译期
决定了Worker模式的工作方式将会由子类 ClerverHourWorker来提供。
4.1.4 做好准备,继续前行
从前面可以看出,直接利用 new 来生成一个具体类的实例不是一个好的作法,后面将要介
绍的 Factory模式和依赖倒置可以修正这个方法。
说明:本书介绍的模式主要收纳于设计模式(GOF:Design Pattern),J2EE模式和架构(框架)模式这几
个模式名录(收集了各种模式)中,它们的粒度针对它们各自的应用场景都足够粗,所以这些都是好的模
式名录,自发明以来沿用至今,堪称经典。
本章的目的只是挑选出一些比较直观的模式进行讲解,使读者将模式和 Spring 尽快地结合
在一起,这对学好 Spring 是非常有好处的。可以肯定的是,Spring 框架的很多灵感和构建
知识都来自于这些模式。
4.2 工厂模式(Design Pattern:Factory Method)的精
髓
工厂模式被收录于设计模式(Gof的著作),设计模式依照其目的可以分为如下类型:
创建型模式:与对象的创建有关。
结构型模式:处理类或对象的组合。
行为型模式:描述类或对象怎样交互和怎样分配职责。
工厂模式属于创建型模式,目的是定义出一个用于创建对象的接口,让子类来决定需要实例
化哪个类,使得低层细节代码中不会出现类似代码 4.7 中 Worker worker = new
ClerverHourWorker()这种情况。如果像 4.7一样,那就违反了依赖倒置原则(DIP)。该原则
讲述了高层模块不应该依赖于低层模块,细节应该依赖于抽象,接下来的讲解将围绕 DIP
原则逐步过渡到工厂模式。
4.2.1 引入问题
如图 4.3所示,在软件系统中,这样的层次好像没有什么问题。但是,策略层直接使用了实
现层,实现层又直接使用了工具辅助层,如图 4.4所示。
图 4.3 依赖传递 图 4.4 依赖传递类图
给出实现,如代码 4.11~4.14所示。
代码 4.11 StrongDependency.java
package chapter4.pattern.factory.strongdep;
public class StrongDependency {
public static void main(String[] args) {
Policy policy = new Policy();
policy.doPolicy();
}
}
代码 4.12 Policy.java
public class Policy {
private Implemention impl;
public Policy() {
this.impl = new Implemention();
}
public void doPolicy() {
impl.doImpl();
}
}
代码 4.13 Implemention.java
public class Implemention {
private Util util;
public Implemention() {
util = new Util();
}
public void doImpl() {
util.utilMethod();
}
}
代码 4.14 Util.java
public class Util {
public void utilMethod() {}
}
可以看出,每个位于高层抽象的类,都直接依赖于低层细节类的实现,这是非常糟糕的。这
意味着将细节替换成其他实现是不可能的。而且由于这种依赖性的层间传递,任何对低层细
节的改变都将直接波及高层抽象,使整个系统僵硬、无法测试。
4.2.2 解决方法
为了解决上述问题,引入实现层抽象接口和工具辅助层抽象接口,如图 4.5所示。这样整个
系统发生了很大变化,如图 4.6所示。
图 4.5 依赖倒置和反向控制
图 4.6 依赖倒置和反向控制类图
给出实现,如代码 4.15~4.20所示。
代码 4.15 DipIocUsage.java
package chapter4.pattern.factory .dipioc;
public class DipIocUsage {
public static void main(String[] args) {
UtilService utilService = new ConcreteUtil();
ImplementionService implService = new ConcreteImplemention(utilService);
Policy policy = new Policy(implService);
policy.doPolicy();
}
}
代码 4.16 Policy.java
public class Policy {
private ImplementionService implService;
public Policy(ImplementionService implService) {
this.implService = implService;
}
public void doPolicy() {
implService.doImpl();
}
}
代码 4.17 ImplementionService.java
public interface ImplementionService {
void doImpl();
}
代码 4.18 ConcreteImplemention.java
public class ConcreteImplemention implements ImplementionService {
private UtilService utilService;
public ConcreteImplemention(UtilService utilService) {
this.utilService = utilService;
}
public void doImpl() {
utilService.utilMethod();
}
}
代码 4.19 UtilService.java
public interface UtilService {
void utilMethod();
}
代码 4.20 ConcreteUtil.java
public class ConcreteUtil implements UtilService {
public void utilMethod() {}
}
可以发现,当引入抽象服务接口后,高层模块将通过依赖于抽象接口来使用低层服务,从而
不再依赖于低层模块,此时低层细节反而依赖于高层抽象接口了,这使得控制权体系完全反
转了。
在代码 4.15 中,因为低层模块都是针对接口编程的(这非常重要),所以客户代码(main
函数)可以通过抽象接口,直接控制低层模块的具体实现,而不是由低层模块自己来处理依
赖关系。这样可以在高层轻松替换低层实现,并且整个系统更易于测试。虽然客户代码仍然
破坏着 DIP 原则,但客户代码往往是作为入口或者系统初始的时候执行一次,可以用下面
即将介绍的工厂模式来修正,问题不大。但是通过将依赖抽离到较高的层次,可以发现,依
赖倒置使得控制反转和依赖注射皆成为了可能。
4.2.3 工厂模式和依赖倒置的关系
接下来
工厂模式和依赖倒置的关系。其实,有了上述的讨论,工厂模式已经呼之欲出了。
下面直接给出工厂模式的类图,如图 4.7所示。
图 4.7 经典工厂模式类图
对工厂模式参与者简介如下:
Product:定义工厂方法所创建的对象的接口。
ConcreteProdut:实现 Product接口。
Creator:声明工厂方法,该方法返回一个 Product类型的对象;Creator也可以是抽
象类(Abstract class)或者具体类,以返回一个工厂方法。
ConcreteCreator:重定义工厂方法,以返回一个 ConcreteProduct对象。
给出工厂模式实现,如代码 4.21~4.25所示。
代码 4.21 Client.java
package chapter4.pattern.factory;
public class Client {
public static void main(String[] args) {
Creator factory = new ConcreteCreator();
Product product = factory.factoryMethod();
}
}
代码 4.22 Creator.java
public interface Creator {
public Product factoryMethod();
}
代码 4.23 ConcreteCreator.java
public class ConcreteCreator implements Creator {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
代码 4.24 Product.java
public interface Product {
}
代码 4.25 ConcreteProduct.java
public class ConcreteProduct implements Product {
}
可以看到,经典工厂模式类图的Creator体系和图 4.6相似,区别是:前者多出了产品(Product)
这个平行的继承体系。这个变化在很大程度上解决了客户代码对具体类的依赖问题。在代码
4.21中,客户代码主要使用 Creator和 Product这两个接口工作,而不会再依赖于 Product的
细节。
惟一遗憾的是,Creator 仍然需要依赖于它的子类来决定工厂的行为。注意,这是多态性工
厂无法避免的,因为依赖关系总得有一个地方进行处理。根据 DIP 原则或 IOC 原则,决定
依赖关系的场所越高层、越抽象越好。
在本例的上下文环境中,Client main函数已足够高层,所以把对 ConcreteCreator的依赖架设
在 main 函数中,而在 Creator 接口的具体子类 ConcreteCreator 中,工厂方法能够产生具体
产品,是比较高级的行为,工厂体系本身也已足够抽象,所以把对 ConcreteProduct 的依赖
架设于此。
提示:但这不是最好的方法,后面会给出更好的解决方案。
最后,在图 4.6中,通过引入两个抽象接口 ImplementionService和 UtilService,使得所有的
依赖关系都被移动到代码 4.15的 main函数里了。这样造成了一个结果,就是客户代码仍在
多处对具体类产生依赖,下面用工厂模式来修正一下,如代码 4.26~4.32所示。
代码 4.26 DipIocFactoryUsage.java
package chapter4.pattern.factory.dipioc;
public class DipIocFactoryUsage {
public static void main(String[] args) {
//工厂初始化
UtilServiceFactory utilServiceFactory = new
ConcreteUtilServiceFactory();
ImplServiceFactory implServiceFactory = new
ConcreteImplServiceFactory();
PolicyFactory policyFactory = new ConcretePolicyFactory();
//工厂开始制造产品
UtilService utilService = utilServiceFactory.make();
//<-工厂特例:参数化工厂
ImplementionService implService = implServiceFactory.make(utilService);
Policy policy = policyFactory.make(implService);
//->
//产品开始工作
policy.doPolicy();
}
}
代码 4.27 UtilServiceFactory.java
public interface UtilServiceFactory {
public UtilService make();
}
代码 4.28 ConcreteUtilServiceFactory.java
public class ConcreteUtilServiceFactory implements UtilServiceFactory {
public UtilService make() {
return new ConcreteUtil();
}
}
代码 4.29 ImplServiceFactory.java
public interface ImplServiceFactory {
public ImplementionService make(UtilService utilService);
}
代码 4.30 ConcreteImplServiceFactory.java
public class ConcreteImplServiceFactory implements ImplServiceFactory {
public ImplementionService make(UtilService utilService) {
return new ConcreteImplemention(utilService);
}
}
代码 4.31 PolicyFactory.java
public interface PolicyFactory {
public Policy make(ImplementionService implService);
}
代码 4.32 ConcretePolicyFactory.java
public class ConcretePolicyFactory implements PolicyFactory {
public Policy make(ImplementionService implService) {
return new Policy(implService);
}
}
由于先前(代码 4.16~4.20)已经备有产品体系(Policy、ImplementionService、UtilService
及其子类),所以本例惟一要引入的,是增加一个平行的工厂体系,如代码 4.27~4.32。修改
完成后,看一下客户代码 4.26,通过使用工厂模式,使客户基本上只依赖于抽象产品和抽象
工厂,从而使工厂实现以及工厂方法成为了核心依赖点。这意味着,高层模块只要通过配置
或衍生工厂,就可以达到控管低层模块的目的。在代码 4.26 中提到的“工厂特例参数化工
厂”,属于经典工厂模式的一个变种。
4.3 单例模式(Design Pattern:Singleton)
单例模式属于创建型模式,目的是保证一个类仅有一个实例,并提供一个访问它的全局访问
点。考虑这样一种对象,这个对象应该在程序启动时被创建,并且在结束时被删除,如应用
程序的基础高层对象。通过这个对象可以得到系统中其他的对象,这些基础对象可能是前面
提到的工厂对象(Factories),用来创建其他对象;也可能是管理器对象(managers),负责
控管其他对象;或者是全局注册表(Registry)。类似这种类型的对象不该被创造出多份。
4.3.1 单例模式的实现
下面给出单例模式的实现。单例模式参与者如图 4.8 所示。其中 Singleton 定义了一个
getInstance操作,允许客户访问它的惟一实例。可能负责创建它的惟一实例。
图 4.8 经典单例模式类图
如图 4.8和代码 4.33,可以看到,经典单例模式的实现非常简单。而正是由于概念和实现上
的简单,没有顾及到逻辑概念上、测试性、全局依赖、类装载器、序列化、线程安全以及不
同 JVM之间等等方面的问题,造成了很多的误用。
代码 4.33 Singleton.java
package chapter4.pattern.singleton;
public class Singleton {
private static Singleton instance;
/**
* 不允许通过构造字来实例化
*/
private Singleton() {
}
/**
* 使用 synchronized关键字,保障 Singleton的线程安全
*/
public synchronized static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
注意:Singleton模式应当谨慎使用。代码 4.33中给出了一种合理的使用方法,通过将Singleton
的构造子声明为私有,杜绝了外部或者子类的实例化倾向(当然这也是允许的)。
通过为 getInstance()方法签署 synchronized锁,保障了 Singleton是线程安全的。此处所指的
线程不安全是指在多线程环境下,可能产生不同 Singleton实例。另外,getInstance()方法实
际上是一种静态工厂方法,这是工厂模式的一个变种。考虑上节中工厂模式的例子,尝试结
合 Factory和 Singleton,如代码 4.34所示。
代码 4.34 UtilServiceSingletonFactory.java
package chapter4.pattern.singleton;
import chapter4.pattern.factory.dipioc.ConcreteUtil;
import chapter4.pattern.factory.dipioc.UtilService;
public class UtilServiceSingletonFactory {
private static UtilServiceSingletonFactory instance;
/**
* 允许子类实例化
*/
protected UtilServiceSingletonFactory() {
}
public synchronized static UtilServiceSingletonFactory getInstance() {
if (instance == null) {
instance = new UtilServiceSingletonFactory();
}
return instance;
}
public UtilService make() {
return new ConcreteUtil();
}
}
客户代码可以是这样:
UtilService utilService =
UtilServiceSingletonFactory.getInstance().make();
可以看到,通过单例工厂使得工厂对象实例数不超过 1个,同时给出了 Factory Method的实
现(make 方法),通过变换构造子修饰(private->protected),保留了 Factory 的派生性。如
果不考虑派生性,完全有理由使用静态工厂而不是单例工厂,静态工厂使工厂类不需要被实
例化,客户代码可以是这样:
UtilService utilService = UtilServiceSingletonFactory.make();
4.3.2 单例注册表
接下来将要介绍 Singleton模式的另一种常用实现,即单例注册表(Singleton Registry),如
代码 4.35~4.39所示。
代码 4.35 FactorySingletonRegistryUsage.java
package chapter4.pattern.singleton;
public class FactorySingletonRegistryUsage {
public static void main(String[] args) {
//实例化工厂注册表
FactorySingletonRegistry registry =
FactorySingletonRegistry.getInstance();
//<- 通过反射机制,使得注册表可以依据给定的工厂全限定名返回具体工厂实例
//第一次索取
BeanFactory xmlBeanFactory1 =
registry.getBeanFactory("chapter4.pattern.singleton.XmlBeanFactory");
BeanFactory listableBeanFactory1 =
registry.getBeanFactory("chapter4.pattern.singleton.ListableBeanFactory")
;
//第二次索取
BeanFactory xmlBeanFactory2 =
registry.getBeanFactory("chapter4.pattern.singleton.XmlBeanFactory");
BeanFactory listableBeanFactory2 =
registry.getBeanFactory("chapter4.pattern.singleton.ListableBeanFactory")
;
//->
//比较先后两次索取的工厂实例,希望得到同一实例的工厂引用,结果正确
System.out.println(xmlBeanFactory1.hashCode() ==
xmlBeanFactory2.hashCode());
System.out.println(listableBeanFactory1.hashCode() ==
listableBeanFactory2.hashCode());
}
}
代码 4.36 FactorySingletonRegistry.java
import java.util.HashMap;
import java.util.Map;
public class FactorySingletonRegistry {
private static FactorySingletonRegistry instance;
private static Map factoryMap = new HashMap();①
private FactorySingletonRegistry() {
}
public synchronized static FactorySingletonRegistry getInstance() {
if (instance == null) {
instance = new FactorySingletonRegistry();
}
return instance;
}
public synchronized BeanFactory getBeanFactory(String factoryClassName)
{②
BeanFactory factory = (BeanFactory)factoryMap.get(factoryClassName);
if (factory != null) return factory;
try {
factory = (BeanFactory)Class.forName(factoryClassName).newInstance();
③
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find class " + factoryClassName);
} catch (InstantiationException e) {
System.out.println("Couldn't instantiate an object of type " +
factoryClassName);
} catch (IllegalAccessException e) {
System.out.println("Couldn't access class " + factoryClassName);
}
factoryMap.put(factoryClassName, factory);
return factory;
}
}
代码 4.37 BeanFactory.java
public interface BeanFactory {
}
代码 4.38 ListableBeanFactory.java
public class ListableBeanFactory implements BeanFactory {
public ListableBeanFactory() {
System.out.println("ListableBeanFactory Created");
}
}
代码 4.39 XmlBeanFactory.java
public class XmlBeanFactory implements BeanFactory {
public XmlBeanFactory() {
System.out.println("XmlBeanFactory Created");
}
}
对单例注册表的简单运作方式,做如下说明:
(1)请看代码 4.36,首先这个类是一个单例类,在①处使用了Map对象,这是一个类,持
有聚集的讯息。所谓聚集通常就是在Map中以名值对(field-value)的形式存储一系列各类
对象的实例引用,通过 put(field,value)存储,通过 get(field)取得 value。在代码 4.36 的
getBeanFactory方法中,通过对 factoryMap的存取,可以使该单例注册表持有任意数量的工
厂实例,并且通过 if (factory != null)的判断,保证了返回的是对同一个工厂实例的引用。
(2)注意代码 4.36中的②处,getBeanFactory(String factoryClassName)是一个参数化的工厂
方法,和在代码 4.26中的类似。
说明:如果工厂方法依赖一个参数标识来决定产品的具体生产行为,那么可以说这就是一个参数化工厂方
法。参数化工厂的好处是,创建同一产品族系(拥有同一接口的具体产品)时,不再需要衍生具体的工厂
子类来对应。主要缺点是工厂职责过于集中,另外一个缺点仍然是所有传统工厂模式的通病,就是由于硬
编码的关系,需要使用 if/else语句来决定生产方式,无法摆脱 Product = new ConcreteProduct()这种代码带
来的强依赖性,同时限制了工厂创建产品的种类数目。这也意味着每当引入新的产品时,就需要重新变更
关联代码。
(3)在代码 4.36 中引入了反射(Reflection)技术,如③所示。在代码 4.35 中,可以以类
的全限定名作为参数传入工厂方法,工厂方法会通过反射技术来实例化那个类,而不需要预
先在工厂方法中记载有限的产品集合了。
至此,给出了单例模式的基本用法,单例模式是一种常用的模式,但也很容易被误用。
最后还提到了反射和工厂方法模式的一些结合,这是非常有用的技术。
4.4
模式和策略模式(Design Pattern:Template
Method And Strategy)
本节将同时介绍模板(Template)模式(模
本文档为【JAVA面向对象编程】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑,
图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。