第6章 抽象工厂类
当需要给人添加性别时,一共会有种族+性别6种组合,所以在工厂开设时,可以按照男女区别开两个工厂,目前的类图,包括
1、人(接口)
2、黑白黄人(抽象人)
3、黑白黄男女(具体人)
1、工厂(接口)
2、抽象工厂(负责反射产生对象)
3、具体两个工厂(传入具体人种性别参数)
1、枚举类 (包含具体人种性别参数,负责反射时服务)
package ABFactory;
//抽象工厂只实现了一个 createHuman 的方法,目的是简化实现类的代码工作量
//方便在实现工厂时只用传入不同参数,不用使用6个构造方法,直接使用反射实现对象创建
//通过已有的对象(当然使用静态类也可以实现)限制传入的参数
public abstract class AbstractHumanFactory implements HumanFactory {
protected Human createHuman(HumanEnum humanEnum){
Human human = null;
//空的不执行 只有非空才有
if (!humanEnum.getValue().equals("")){
try {
//利用反射构造出对象
human = (Human)Class.forName(humanEnum.getValue()).newInstance();
}catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
//因为使用了enum,这个种异常情况不会产生了,除非你的enum有问题;
e.printStackTrace();
}
}
return human;
}
}
package ABFactory;
/**
* Enum 以前我也很少用,最近在一个项目中偶然使用上了,然后才发觉它的好处,Enum 类型作为一个参
* 数传递到一个方法中时,在 Junit 进行单元测试的时候,不用判断输入参数是否为空、长度为 0 的边界异
* 常条件,如果方法传入的参数不是 Enum 类型的话,根本就传递不进来,你说定义一个类,定义一堆的静态
* 变量,这也可以呀,这个不和你抬杠,上面的代码我解释一下,构造函数没啥好说的,然后是 getValue()
* 方法,就是获得枚举类型中一个元素的值,枚举类型中的元素也是有名称和值的,这个和 HashMap 有点类
* 似。
*/
public enum HumanEnum {
//把世界上所有人类型都定义出来 方便反射构造对象
MaleYHuman("ABFactory.MaleYHuman"),
FemaleYHuman("ABFactory.FemaleYHuman"),
FemaleWHuman("ABFactory.FemaleWHuman"),
MaleWHuman("ABFactory.MaleWHuman"),
FemaleBHuman("ABFactory.FemaleBHuman"),
MaleBHuman("ABFactory.MaleBHuman");
private String value = "";
//定义构造函数,目的是Data(value)类型的相匹配 枚举类是类的集合
private HumanEnum(String value){
this.value = value;
}
public String getValue(){
return this.value;
}
}
第 9 章 模板方法模式
在面向接口编程时,上面一个接口或者模板设计好该类型要求,下面就负责实现就好,最后一个方法汇总之前重写的方法,实现该类的实现,此时就会遇到模板方法:
其他的子类都不用修改(如果要修改,就是把四个方法的访问权限由 public 修改 protected),
大家请看这个 run 方法,他定义了调用其他方法的顺序,并且子类是不能修改的,这个叫做模板方法;start、stop、
alarm、engineBoom 这四个方法是子类必须实现的,而且这四个方法的修改对应了不同的类,这个叫做基本
方法,基本方法又分为三种:在抽象类中实现了的基本方法叫做具体方法;在抽象类中没有实现,在子类
中实现了叫做抽象方法,我们这四个基本方法都是抽象方法,由子类来实现的;还有一种叫做钩子方法,
这个等会讲。
public final void run(){
start();
stop();
alarm();
enginerBoom();
}
到目前为止,这两个模型都稳定的运行,突然有一天,老大又找到了我,
“客户提出新要求了,那个喇叭想让它响就响,你看你设计的模型,车子一启动,喇叭就狂响,赶快
修改一下”,确实是设计缺陷,呵呵,不过是我故意的,那我们怎么修改呢?看修改后的类图:
钩子方法模式是由抽象类来实现的,子类可以重写的
public abstract class HummerModel {
private boolean alarmFlag; //是否要响喇叭
//要不要响喇叭,是有客户的来决定的
public void setAlarm(boolean isAlarm){
this.alarmFlag = isAlarm;
}
}
那我们总结一下模板方法模式,模板方法模式就是在模板方法中按照一个的规则和顺序调用基本方法,
具体到我们上面那个例子就是 run 方法按照规定的顺序(先调用 start,然后再调用 engineBoom,再调用
alarm,最后调用 stop)调用本类的其他方法,并且由 isAlarm 方法的返回值确定 run 中的执行顺序变更,
初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”,这个问题很有普遍性,反正我
是被问过好几回,那么父类是否可以调用子类的方法呢?我的回答是能,但强烈的、极度的不建议,怎么
做呢?
— 把子类传递到父类的有参构造中,然后调用;
— 使用反射的方式调用,你使用了反射还有谁不能调用的?!
— 父类调用子类的静态方法。
这三种都是父类直接调用子类的方法,好用不?好用!解决问题了吗?解决了!项目中允许使用不?
不允许!我就一直没有搞懂为什么要父类调用子类的方法,如果一定要调用子类,那为什么要继承它呢?
搞不懂。其实这个问题可以换个角度去理解,在重写了父类部分方法后,子类再调用从父类继承的方法,产生
不同的结果(而这正是模板方法模式),这是不是也可以理解为父类调用了子类的方法呢?你修改了子类,
影响了父类的结果,模板方法模式就是这样效果。
第 10 章 建造者模式
这些车辆模型的都有 run 方法,但是具体到每一个模型的 run 方法可能中间的
执行任务的顺序是不同的,老大说要啥顺序,我就给啥顺序,最终客户买走后只能是既定的模型,还是没
听明白,我们继续,我们先把我们最基本的对象 Product 在类图中表明出来:
和模板方法不一样的是,run需要根据车主对每个车型的进行单独要求,即每个车型的run中执行顺序不一样
现在有
1、CarModel 抽象类 (负责车模板)
2、BenzCarModel 、 BWMCarModel 具体类 (实现)
3、Builder 抽象类 (1、得到模板 2、设置run的顺序)
4、BenzBuilder、BWMBuilder 具体类 (重写)
5、Director 具体类 (将Builder全部集合起来,将run的顺序一一写入Builder)
6、Client 具体类 (只与Director打交道)
CarModel
package BuilderModelPatten;
import java.util.ArrayList;
public abstract class CarModel {
private ArrayList<String> sequence;
protected abstract void stop();
protected abstract void alarm();
protected abstract void start();
final public void run(){
for (int i =0 ; i<sequence.size();i++){
if (sequence.get(i) == "stop"){
stop();
}else if (sequence.get(i) == "alarm"){
alarm();
}else if (sequence.get(i)=="start" ){
start(); //问题 调谁的start 谁重写就用谁
}
}
}
public void setSequence(ArrayList<String> sequence){
this.sequence = sequence;
}
}
BenzCarModel
package BuilderModelPatten;
public class BenzCarModel extends CarModel {
@Override
protected void stop() {
System.out.println("BenzStop");
}
@Override
protected void alarm() {
System.out.println("BenzAlarm");
}
@Override
protected void start() {
System.out.println("BenzStart");
}
}
BWMCarModel
package BuilderModelPatten;
public class BWMCarModel extends CarModel {
@Override
protected void stop() {
System.out.println("BWMStop");
}
@Override
protected void alarm() {
System.out.println("BWMAlarm");
}
@Override
protected void start() {
System.out.println("BWMStart");
}
}
CarBuilder
package BuilderModelPatten;
import java.util.ArrayList;
public abstract class CarBuilder {
public abstract CarModel getCarModel(); //返回车型
public abstract void setSequence(ArrayList<String> sequence); //只负责听话实现run的顺序
}
BenzBuilder
package BuilderModelPatten;
import java.util.ArrayList;
public class BenzBuilder extends CarBuilder {
private BenzCarModel benzCarModel =new BenzCarModel(); //需要的是对象(不是属性) 不初始话,后面会有空指针异常
@Override
public BenzCarModel getCarModel() {
return this.benzCarModel;
}
@Override
public void setSequence(ArrayList<String> sequence) {
this.benzCarModel.setSequence(sequence); //子类调用父类的setSequence方法
}
}
BWMBuilder
package BuilderModelPatten;
import java.util.ArrayList;
public class BWMBuilder extends CarBuilder {
private BWMCarModel bwmCarModel = new BWMCarModel();
@Override
public BWMCarModel getCarModel() {
return bwmCarModel;
}
@Override
public void setSequence(ArrayList<String> sequence) {
this.bwmCarModel.setSequence(sequence);
}
}
Director
package BuilderModelPatten;
import java.util.ArrayList;
public class Director {
private ArrayList<String> sequence = new ArrayList<>(); //要调用的对象都要初始化
public BenzCarModel getBenzAMode(){
this.sequence.clear(); //初始化
this.sequence.add("start");
this.sequence.add("stop");
BenzBuilder benzBuilder = new BenzBuilder();
benzBuilder.setSequence(sequence);
return benzBuilder.getCarModel();
}
public BenzCarModel getBenzBMode(){
this.sequence.clear();
this.sequence.add("alarm");
this.sequence.add("start");
this.sequence.add("stop");
BenzBuilder benzBuilder = new BenzBuilder();
benzBuilder.setSequence(sequence);
return benzBuilder.getCarModel();
}
public BWMCarModel getBWMCMode(){
this.sequence.clear();
this.sequence.add("start");
this.sequence.add("stop");
BWMBuilder bwmBuilder =new BWMBuilder();
bwmBuilder.setSequence(sequence);
return bwmBuilder.getCarModel();
}
public BWMCarModel getBWMDMode(){
this.sequence.clear();
this.sequence.add("alarm");
this.sequence.add("start");
this.sequence.add("stop");
BWMBuilder bwmBuilder =new BWMBuilder();
bwmBuilder.setSequence(sequence);
return bwmBuilder.getCarModel();
}
}
Client
package BuilderModelPatten;
import java.util.ArrayList;
public class Client {
public static void main(String[] args) {
// ArrayList<String> sequence = new ArrayList<>();
// sequence.add("start");
// BWMBuilder bwmBuilder = new BWMBuilder();
// BWMCarModel bwmCarModel =bwmBuilder.getCarModel();
// bwmBuilder.setSequence(sequence);
// bwmCarModel.run();
Director director = new Director();
director.getBenzAMode().run();
director.getBenzBMode().run();
director.getBWMCMode().run();
director.getBWMDMode().run();
}
}
可见用户只面向Director,将自己需求的车型和run中执行顺序告诉它,Director就可以给Builder下达指令造车,不用Client在看车时对每一个车进行指令
优化了对具体汽车(如1000万辆汽车)run顺序设置,变成对具体车型(如4种车型)的run顺序设置。大大优化程序!
清晰,简单吧,我们写程序重构的最终目的就是这个,简单,清晰,代码是让人看的,不是写完就完事了,
我一直在教育我带的团队,Java 程序不是像我们前辈写那个二进制代码、汇编一样,写完基本上就自己能看懂,
别人看就跟看天书一样,现在的高级语言,要像写中文汉字一样,你写的,别人能看懂。
整个程序编写完毕,而且简洁明了,这就是建造者模式,中间有几个角色需要说明一下:
Client 就是牛叉公司,这个到具体的应用中就是其他的模块或者页面;
CarModel 以及两个实现类 BenzModel 和 BMWModel 叫做产品类(Product Class),这个产品类实现了模板方法模式,也就是有模板方法和基本方法,这个参考上一节的模板方法模式;
CarBuilder 以及两个实现类 BenzBuilder 和 BMWBuilder 叫做建造者(Builder Class),在上面的那个例子中就是我和我的团队,负责建造 Benz 和 BMW 车模,按照指定的顺序;
Director 类叫做导演类(Director Class),负责安排已有模块的顺序,然后告诉 Builder 开始建造,在上面的例子中就是我们的老大,Client 找到老大,说我要这个,这个,那个类型的车辆模型,然后老大就把命令传递给我,我和我的团队就开始拼命的建造,于是一个项目建设完毕了。
大家看到这里估计就开始犯嘀咕了,这个建造者模式和工厂模式非常相似呀,Yes,是的,是非常相似,但
是记住一点你就可以游刃有余的使用了:建造者模式最主要功能是基本方法的调用顺序安排,也就是这些基本方
法已经实现了;而工厂方法则重点是创建,你要什么对象我创造一个对象出来,组装顺序则不是他关心的。
建造者模式使用的场景,一是产品类非常的复杂,或者产品类中的调用顺序不同产生了不同的效能,这个时
候使用建造者模式是非常合适,我曾在一个银行交易类项目中遇到了这个问题,一个产品的定价计算模型有 N 多
种,每个模型有固定的计算步骤,计算非常复杂,项目中就使用了建造者模式;二是“ 在对象创建过程中会使
用到系统中的一些其它对象,这些对象在产品对象的创建过程中不易得到”,这个是我没有遇到过的,创建过程
中不易得到?那为什么在设计阶段不修正这个问题,创建的时候都不易得到耶!
第11章 装饰者模式
当考试考了差分,为了让爸妈在卷子上签字时,自己少挨打,这时候需要将卷子装饰一下,添加额外信息解释一下
就这成绩还要我签字?!老爸就开始找笤帚,我的屁股已经做好了准备,肌肉要绷紧,要不那个太疼了!哈
哈,幸运的是,这个不是当时的真实情况,我没有直接把成绩单交给老爸,而是在交给他之前做了点技术工作,
我要把成绩单封装一下,封装分类两步走:
第一步:跟老爸说各个科目的最高分,语文最高是 75,数学是 78,自然是 80,然老爸觉的我成绩与最高分
数相差不多,这个是实情,但是不知道是什么原因,反正期末考试都考的不怎么样,但是基本上都集中在 70 分
以上,我这 60 多分基本上还是垫底的角色;
第二步:在老爸看成绩单后,告诉他我是排名第 38 名,全班,这个也是实情,为啥呢?有将近十个同学退
学了!这个情况我是不说的。不知道是不是当时第一次发成绩单,学校没有考虑清楚,没有写上总共有多少同学,
排名第几名等等,反正是被我钻了个空子。
常规做法
通过继承去成绩单的封装,将report方法重写,实现报告的美化!
美化,在report时不直接打印原始成绩,而是在这之前解释说明一下
public class SugarFouthGradeSchoolReport extends FouthGradeSchoolReport {
//首先要定义你要美化的方法,先给老爸说学校最高成绩
private void reportHighScore(){
System.out.println("这次考试语文最高是75,数学是78,自然是80");
}
//在老爸看完毕成绩单后,我再汇报学校的排名情况
private void reportSort(){
System.out.println("我是排名第38名...");
}
//由于汇报的内容已经发生变更,那所以要重写父类
@Override
public void report(){
this.reportHighScore(); //先说最高成绩
super.report(); //然后老爸看成绩单
this.reportSort(); //然后告诉老爸学习学校排名
}
}
打印结果:
但是有个问题所在,一旦我想实现丰富report的结果,比如再说明一下每科的排名,成绩分布,和之前成绩对比之类的效果时,又需要通过新的子类通过继承去重写report方法,这样会导致子类数量和继承层数爆炸,且扩展性极其差,尤其再原抽象类report改变抽象方法时,维护起来十分困难,这时候使用装饰着模式,能构解耦合这些组合方法,让扩展性变强的同时,并行多个子类使继承层数降低!!
并且在面向对象的设计中,如果超过 2 层继承,你就应该想想是不是出设计问题了,是不是应该重新找一条道了,这是经验值,不是什么绝对的,继承层次越多你以后的维护成本越多,问题这么多,那怎么办?好办,装饰模式出场来解决这些问题,我们先来看类图:
增加一个抽象类和两个实现类,其中 Decorator 的作用是封装 SchoolReport 类,看源代码:
成绩单抽象类
package DecarePatten;
public abstract class SchoolReport {
public abstract void report();
public abstract void sign(String name);
}
原始成绩单
package DecarePatten;
public class PriorSR extends SchoolReport{
public void report(){
System.out.println("65");
}
@Override
public void sign(String name) {
System.out.println("家长签字"+name);
}
}
修饰类
package DecarePatten;
public abstract class Decorator extends SchoolReport {
//封装报告
private SchoolReport sr; //多态使用抽象报告,传入报告是该抽象的具体子类就行
public Decorator(SchoolReport sr) {
this.sr = sr;
}
@Override
public void report(){
this.sr.report(); //能实现传入成绩单的report信息
}
@Override
public void sign(String name) {
this.sr.sign(name);
}
}
装饰1,写入最高分
package DecarePatten;
//抽象类需要重写构造方法 目标就是为了实现该类
public class HighSchoolDecorator extends Decorator {
public HighSchoolDecorator(SchoolReport sr) {
super(sr);
}
private void reportHighScore(){
System.out.println("最高分75分");
}
public void report(){
super.report();
reportHighScore();
}
}
装饰2,写入排名
package DecarePatten;
public class SortDecorator extends Decorator {
public SortDecorator(SchoolReport sr) {
super(sr);
}
private void reportSort(){
System.out.println("本次排名38");
}
public void report(){
super.report();
reportSort();
}
}
父亲检阅试卷
package DecarePatten;
public class Father {
public static void main(String[] args) {
//相当于将原始的sr成绩单,一层一层的封装起来 在重写构造方法时,封装对象的成员方法也会在super中得到保存
SchoolReport sr;
sr = new PriorSR();
sr = new HighSchoolDecorator(sr);
sr = new SortDecorator(sr);
sr.report();
sr.sign("san");
}
}
最神奇的一幕出现了,sr在封装以后传入下一层时,通过super会将sr的方法和属性也会封装到下一层对象种
第12章 观察者模式
韩非子大家都应该记得吧,法家的代表人物,主张建立法制社会,实施重罚制度,真是非常有远见呀,
看看现在社会在呼吁什么,建立法制化的社会,在 2000 多年前就已经提出了。大家可能还不知道,法家还
有一个非常重要的代表人物,李斯,对,就是李斯,秦国的丞相,最终被残忍的车裂的那位,李斯和韩非
子都是荀子的学生,李斯是师兄,韩非子是师弟,若干年后,李斯成为最强诸侯秦国的上尉,致力于统一
全国,于是安插了间谍到各个国家的重要人物的身边,以获取必要的信息,韩非子作为韩国的重量级人物,
身边自然没少间谍了,韩非子早饭吃的什么,中午放了几个 P,晚上在做什么娱乐,李斯都了如指掌,那可
是相隔千里!怎么做到的呢?间谍呀! 好,我们先通过程序把这个过程展现一下,看看李斯是怎么监控韩
非子,先看类图:
初代
来看看,实现监视线程
package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 监控程序
*/
class Watch extends Thread{
private HanFeiZi hanFeiZi;
private LiSi liSi;
private String type;
//通过构造函数传递参数,我要监控的是谁,谁来监控,要监控什么
public Watch(HanFeiZi _hanFeiZi,LiSi _liSi,String _type){
this.hanFeiZi =_hanFeiZi;
this.liSi = _liSi;
this.type = _type;
}
@Override
public void run(){
while(true){
if(this.type.equals("breakfast")){ //监控是否在吃早餐
//如果发现韩非子在吃饭,就通知李斯
if(this.hanFeiZi.isHaveBreakfast()){
this.liSi.update("韩非子在吃饭");
//重置状态,继续监控
this.hanFeiZi.setHaveBreakfast(false);
}
}else{//监控是否在娱乐
if(this.hanFeiZi.isHaveFun()){
this.liSi.update("韩非子在娱乐");
this.hanFeiZi.setHaveFun(false); } } } }
}
结果出来,韩非子一吃早饭李斯就知道,韩非子一娱乐李斯也知道,非常正确!结果正确但并不表示你有成绩,我告诉你:你的成绩是 0,甚至是负的,你有没有看到你的 CPU 飙升,Eclipse 不响应状态?看到了?看到了你还不想为什么?!看看上面的程序,别的就不多说了,使用了一个 **while(true)**这样一个死循环来做监听,你要是用到项目中,你要多少硬件投入进来?你还让不让别人的程序也 run 起来?!一台服务器就跑你这一个程序就完事了,错,绝对的错!
错误也看到了,我们必须要修改,这个没有办法应用到项目中去呀,而且这个程序根本就不是面向对象的程序,这完全是面向过程的(我写出这样的程序也不容易呀,安慰一下自己),不改不行,怎么修改呢?
我们来想,**既然韩非子一吃饭李斯就知道了,那我们为什么不把李斯这个类聚集到韩非子这里类上呢?**说改就改,立马动手,我们来看修改后的类图:
(所以让两个类有紧密联系 可以通过 继承、封装实现)
二代
类图非常简单,就是在 HanFeiZi 类中引用了 IliSi 这个接口,看我们程序代码怎么修改,IhanFeiZi
接口完全没有修改,我们来看 HanFeiZi 这个实现类:
package com.cbf4life.advance;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 韩非子,李斯的师弟,韩国的重要人物
*/
public class HanFeiZi implements IHanFeiZi{
//把李斯声明出来
private ILiSi liSi =new LiSi();
//韩非子要吃饭了
public void haveBreakfast(){
System.out.println("韩非子:开始吃饭了...");
//通知李斯
this.liSi.update("韩非子在吃饭");
}
//韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多
public void haveFun(){
System.out.println("韩非子:开始娱乐了...");
this.liSi.update("韩非子在娱乐");
}
}
韩非子这么有名望(法家代表)、有实力(韩国的公子,他老爹参与过争夺韩国王位)的人,就只有秦国一个国家关心他吗?想想也不可能呀,肯定有一大帮的各国的类似李斯这样的人在看着他,监视着一举一动,但是看看我们的程序,你在 HanFeiZi 这个类中定义:
private ILiSi liSi =new LiSi();
一下子就敲死了,只有李斯才能观察到韩非子,这是不对的,也就是说韩非子的活动只通知了李斯一
个人,这不可能;再者,李斯只观察韩非子的吃饭,娱乐吗?政治倾向不关心吗?思维倾向不关心吗?杀
人放火不关心吗?也就说韩非子的一系列活动都要通知李斯,那可怎么办?要按照上面的例子,我们不是
要修改疯掉了吗?这和开闭原则严重违背呀,我们的程序有问题,怎么修改,来看类图:
三代(升级了多个观察)
package com.cbf4life.advance2;
import java.util.ArrayList;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 韩非子,李斯的师弟,韩国的重要人物
*/
public class HanFeiZi implements Observable{
//定义个变长数组,存放所有的观察者
private ArrayList<Observer> observerList = new ArrayList<Observer>();
//增加观察者
public void addObserver(Observer observer){
this.observerList.add(observer);
}
//删除观察者
public void deleteObserver(Observer observer){
this.observerList.remove(observer);
}
//通知所有的观察者
public void notifyObservers(String context){
for(Observer observer:observerList){
observer.update(context);
}
}
//韩非子要吃饭了
public void haveBreakfast(){
System.out.println("韩非子:开始吃饭了...");
//通知所有的观察者
this.notifyObservers("韩非子在吃饭");
}
//韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多
public void haveFun(){
System.out.println("韩非子:开始娱乐了...");
this.notifyObservers("韩非子在娱乐");
}
}
好了,结果也正确了,也符合开闭原则了,也同时实现类间解耦,想再加观察者?好呀,继续实现Observer 接口就成了,这时候必须修改 Client 程序,因为你业务都发生了变化。
细心的你可能已经发现,HanFeiZi 这个实现类中应该抽象出一个父类,父类完全实现接口,HanFeiZi
这个类只实现两个方法 haveBreakfast 和 haveFun 就可以了,是的,是的,确实是应该这样,那先稍等等,
查找一下 Observable 是不是已经有这个类了? JDK 中提供了 :
java.util.Observable 实现类和 java.util.Observer 接口,也就是说我们上面写的那个例子中的
Observable 接口可以改换成 java.util.Observale 实现类了,看如下类图:
先看 HanFeiZi 的实现类:
package com.cbf4life.perfect;
import java.util.ArrayList;
import java.util.Observable;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 韩非子,李斯的师弟,韩国的重要人物
*/
public class HanFeiZi extends Observable{
//韩非子要吃饭了
public void haveBreakfast(){
System.out.println("韩非子:开始吃饭了...");
//通知所有的观察者
super.setChanged();
super.notifyObservers("韩非子在吃饭");
}
//韩非子开始娱乐了,古代人没啥娱乐,你能想到的就那么多
public void haveFun(){
System.out.println("韩非子:开始娱乐了...");
super.setChanged(); //相当于mark一下有内容改变 change = true,可以报告了
this.notifyObservers("韩非子在娱乐"); //if(change)才报告
}
}
改变的不多,引入了一个 java.util.Observable 对象,删除了增加、删除观察者的方法,简单了很多,(内部已经实现了)
那我们再来看观察者的实现类:
package com.cbf4life.perfect;
import java.util.Observable;
import java.util.Observer;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 李斯这个人,是个观察者,只要韩非子一有动静,这边就知道
*/
public class LiSi implements Observer{
//首先李斯是个观察者,一旦韩非子有活动,他就知道,他就要向老板汇报
public void update(Observable observable,Object obj){
System.out.println("李斯:观察到李斯活动,开始向老板汇报了...");
this.reportToQiShiHuang(obj.toString());
System.out.println("李斯:汇报完毕,秦老板赏给他两个萝卜吃吃...\n");
}
//汇报给秦始皇
private void reportToQiShiHuang(String reportContext){
System.out.println("李斯:报告,秦老板!韩非子有活动了--->"+reportContext);
}
}
就改变了黄色的部分,应该 java.util.Observer 接口要求 update 传递过来两个变量,Observable 这个
变量我们没用到,就不处理了。
以上讲解的就是观察者模式,这个模式的通用类图如下:
观察者模式在实际项目的应用中非常常见,比如你到 ATM 机器上取钱,多次输错密码,卡就会被 ATM
吞掉,吞卡动作发生的时候,会触发哪些事件呢?第一摄像头连续快拍,第二,通知监控系统,吞卡发生;
第三,初始化 ATM 机屏幕,返回最初状态,你不能因为就吞了一张卡,整个 ATM 都不能用了吧,一般前两
个动作都是通过观察者模式来完成的。
观察者模式有一个变种叫做发布/订阅模型(Publish/Subscribe),如果你做过 EJB(Enterprise JavaBean)的开发,这个你绝对不会陌生。EJB2 是个折腾死人不偿命的玩意儿,写个 Bean 要实现,还要继承,再加上那一堆的配置文件,小项目还凑活,你要知道用 EJB 开发的基本上都不是小项目,到最后是每
个项目成员都在骂 EJB 这个忽悠人的东西;但是 EJB3 是个非常优秀的框架,还是算比较轻量级,写个 Bean只要加个 Annotaion 就成了,配置文件减少了,而且也引入了依赖注入的概念,虽然只是 EJB2 的翻版,但是毕竟还是前进了一步,不知道以后 EJB 的路会怎么样。在 EJB 中有三个类型的 Bean: Session Bean、Entity Bean 和 MessageDriven Bean,我们这里来说一下 MessageDriven Bean(一般简称为 MDB),消息驱动 Bean,消息的发布者(Provider)发布一个消息,也就是一个消息驱动 Bean,通过 EJB 容器(一般是 Message Queue消息队列)通知订阅者做出回应,从原理上看很简单,就是观察者模式的升级版。
那观察者模式在什么情况下使用呢?观察者可以实现消息的广播,一个消息可以触发多个事件,这是观察者模式非常重要的功能。使用观察者模式也有两个重点问题要解决:
广播链的问题。如果你做过数据库的触发器,你就应该知道有一个触发器链的问题,比如表 A 上写了
一个触发器,内容是一个字段更新后更新表 B 的一条数据,而表 B 上也有个触发器,要更新表 C,表 C 也有
触发器…,完蛋了,这个数据库基本上就毁掉了!我们的观察者模式也是一样的问题,一个观察者可以有双
重身份,即使观察者,也是被观察者,这没什么问题呀,但是链一旦建立,这个逻辑就比较复杂,可维护
性非常差,根据经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消
息最多转发一次(传递两次),这还是比较好控制的;
异步处理问题。这个 EJB 是一个非常好的例子,被观察者发生动作了,观察者要做出回应,如果观察
者比较多,而且处理时间比较长怎么办?那就用异步呗,异步处理就要考虑线程安全和队列的问题,这个
大家有时间看看 Message Queue,就会有更深的了解。
我们在来回顾一下我们写的程序,观察者增加了,我就必须修改业务逻辑 Client 程序,这个是必须得吗?回顾一下我们以前讲到工厂方法模式的时候用到了 ClassUtils 这个类,其中有一个方法就是根据接口查找到所有的实现类,问题解决了吧!我可以查找到所有的观察者,然后全部加进来,以后要是新增加观察者也没有问题呀,程序那真是一点都不用改了!