设计模式05-06

163 阅读1小时+

6,行为型模式

6.5 状态模式

6.5.1 概述

【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。

类图如下:

代码如下:

1
public interface ILift {
2
    //电梯的4个状态
3
    //开门状态
4
    public final static int OPENING_STATE = 1;
5
    //关门状态
6
    public final static int CLOSING_STATE = 2;
7
    //运行状态
8
    public final static int RUNNING_STATE = 3;
9
    //停止状态
10
    public final static int STOPPING_STATE = 4;
1112
    //设置电梯的状态
13
    public void setState(int state);
1415
    //电梯的动作
16
    public void open();
17
    public void close();
18
    public void run();
19
    public void stop();
20
}
2122
public class Lift implements ILift {
23
    private int state;
2425
    @Override
26
    public void setState(int state) {
27
        this.state = state;
28
    }
2930
    //执行关门动作
31
    @Override
32
    public void close() {
33
        switch (this.state) {
34
            case OPENING_STATE:
35
                System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门,可以对应电梯状态表来看
36
                this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了
37
                break;
38
            case CLOSING_STATE:
39
                //do nothing //已经是关门状态,不能关门
40
                break;
41
            case RUNNING_STATE:
42
                //do nothing //运行时电梯门是关着的,不能关门
43
                break;
44
            case STOPPING_STATE:
45
                //do nothing //停止时电梯也是关着的,不能关门
46
                break;
47
        }
48
    }
4950
    //执行开门动作
51
    @Override
52
    public void open() {
53
        switch (this.state) {
54
            case OPENING_STATE://门已经开了,不能再开门了
55
                //do nothing
56
                break;
57
            case CLOSING_STATE://关门状态,门打开:
58
                System.out.println("电梯门打开了。。。");
59
                this.setState(OPENING_STATE);
60
                break;
61
            case RUNNING_STATE:
62
                //do nothing 运行时电梯不能开门
63
                break;
64
            case STOPPING_STATE:
65
                System.out.println("电梯门开了。。。");//电梯停了,可以开门了
66
                this.setState(OPENING_STATE);
67
                break;
68
        }
69
    }
7071
    //执行运行动作
72
    @Override
73
    public void run() {
74
        switch (this.state) {
75
            case OPENING_STATE://电梯不能开着门就走
76
                //do nothing
77
                break;
78
            case CLOSING_STATE://门关了,可以运行了
79
                System.out.println("电梯开始运行了。。。");
80
                this.setState(RUNNING_STATE);//现在是运行状态
81
                break;
82
            case RUNNING_STATE:
83
                //do nothing 已经是运行状态了
84
                break;
85
            case STOPPING_STATE:
86
                System.out.println("电梯开始运行了。。。");
87
                this.setState(RUNNING_STATE);
88
                break;
89
        }
90
    }
9192
    //执行停止动作
93
    @Override
94
    public void stop() {
95
        switch (this.state) {
96
            case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下)
97
                //do nothing
98
                break;
99
            case CLOSING_STATE://关门时才可以停止
100
                System.out.println("电梯停止了。。。");
101
                this.setState(STOPPING_STATE);
102
                break;
103
            case RUNNING_STATE://运行时当然可以停止了
104
                System.out.println("电梯停止了。。。");
105
                this.setState(STOPPING_STATE);
106
                break;
107
            case STOPPING_STATE:
108
                //do nothing
109
                break;
110
        }
111
    }
112
}
113114
public class Client {
115
    public static void main(String[] args) {
116
        Lift lift = new Lift();
117
        lift.setState(ILift.STOPPING_STATE);//电梯是停止的
118
        lift.open();//开门
119
        lift.close();//关门
120
        lift.run();//运行
121
        lift.stop();//停止
122
    }
123
}

问题分析:

  • 使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。
  • 扩展性很差。如果新加了断电的状态,我们需要修改上面判断逻辑

 

定义:

对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

 

6.5.2 结构

状态模式包含以下主要角色。

  • 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为。

 

6.5.3 案例实现

对上述电梯的案例使用状态模式进行改进。类图如下:

代码如下:

1
//抽象状态类
2
public abstract class LiftState {
3
    //定义一个环境角色,也就是封装状态的变化引起的功能变化
4
    protected Context context;
56
    public void setContext(Context context) {
7
        this.context = context;
8
    }
910
    //电梯开门动作
11
    public abstract void open();
1213
    //电梯关门动作
14
    public abstract void close();
1516
    //电梯运行动作
17
    public abstract void run();
1819
    //电梯停止动作
20
    public abstract void stop();
21
}
2223
//开启状态
24
public class OpenningState extends LiftState {
2526
    //开启当然可以关闭了,我就想测试一下电梯门开关功能
27
    @Override
28
    public void open() {
29
        System.out.println("电梯门开启...");
30
    }
3132
    @Override
33
    public void close() {
34
        //状态修改
35
        super.context.setLiftState(Context.closeingState);
36
        //动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
37
        super.context.getLiftState().close();
38
    }
3940
    //电梯门不能开着就跑,这里什么也不做
41
    @Override
42
    public void run() {
43
        //do nothing
44
    }
4546
    //开门状态已经是停止的了
47
    @Override
48
    public void stop() {
49
        //do nothing
50
    }
51
}
5253
//运行状态
54
public class RunningState extends LiftState {
5556
    //运行的时候开电梯门?你疯了!电梯不会给你开的
57
    @Override
58
    public void open() {
59
        //do nothing
60
    }
6162
    //电梯门关闭?这是肯定了
63
    @Override
64
    public void close() {//虽然可以关门,但这个动作不归我执行
65
        //do nothing
66
    }
6768
    //这是在运行状态下要实现的方法
69
    @Override
70
    public void run() {
71
        System.out.println("电梯正在运行...");
72
    }
7374
    //这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
75
    @Override
76
    public void stop() {
77
        super.context.setLiftState(Context.stoppingState);
78
        super.context.stop();
79
    }
80
}
8182
//停止状态
83
public class StoppingState extends LiftState {
8485
    //停止状态,开门,那是要的!
86
    @Override
87
    public void open() {
88
        //状态修改
89
        super.context.setLiftState(Context.openningState);
90
        //动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
91
        super.context.getLiftState().open();
92
    }
9394
    @Override
95
    public void close() {//虽然可以关门,但这个动作不归我执行
96
        //状态修改
97
        super.context.setLiftState(Context.closeingState);
98
        //动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
99
        super.context.getLiftState().close();
100
    }
101102
    //停止状态再跑起来,正常的很
103
    @Override
104
    public void run() {
105
        //状态修改
106
        super.context.setLiftState(Context.runningState);
107
        //动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
108
        super.context.getLiftState().run();
109
    }
110111
    //停止状态是怎么发生的呢?当然是停止方法执行了
112
    @Override
113
    public void stop() {
114
        System.out.println("电梯停止了...");
115
    }
116
}
117118
//关闭状态
119
public class ClosingState extends LiftState {
120121
    @Override
122
    //电梯门关闭,这是关闭状态要实现的动作
123
    public void close() {
124
        System.out.println("电梯门关闭...");
125
    }
126127
    //电梯门关了再打开,逗你玩呢,那这个允许呀
128
    @Override
129
    public void open() {
130
        super.context.setLiftState(Context.openningState);
131
        super.context.open();
132
    }
133134135
    //电梯门关了就跑,这是再正常不过了
136
    @Override
137
    public void run() {
138
        super.context.setLiftState(Context.runningState);
139
        super.context.run();
140
    }
141142
    //电梯门关着,我就不按楼层
143
    @Override
144
    public void stop() {
145
        super.context.setLiftState(Context.stoppingState);
146
        super.context.stop();
147
    }
148
}
149150
//环境角色
151
public class Context {
152
    //定义出所有的电梯状态
153
    public final static OpenningState openningState = new OpenningState();//开门状态,这时候电梯只能关闭
154
    public final static ClosingState closeingState = new ClosingState();//关闭状态,这时候电梯可以运行、停止和开门
155
    public final static RunningState runningState = new RunningState();//运行状态,这时候电梯只能停止
156
    public final static StoppingState stoppingState = new StoppingState();//停止状态,这时候电梯可以开门、运行
157158159
    //定义一个当前电梯状态
160
    private LiftState liftState;
161162
    public LiftState getLiftState() {
163
        return this.liftState;
164
    }
165166
    public void setLiftState(LiftState liftState) {
167
        //当前环境改变
168
        this.liftState = liftState;
169
        //把当前的环境通知到各个实现类中
170
        this.liftState.setContext(this);
171
    }
172173
    public void open() {
174
        this.liftState.open();
175
    }
176177
    public void close() {
178
        this.liftState.close();
179
    }
180181
    public void run() {
182
        this.liftState.run();
183
    }
184185
    public void stop() {
186
        this.liftState.stop();
187
    }
188
}
189190
//测试类
191
public class Client {
192
    public static void main(String[] args) {
193
        Context context = new Context();
194
        context.setLiftState(new ClosingState());
195196
        context.open();
197
        context.close();
198
        context.run();
199
        context.stop();
200
    }
201
}

 

6.5.4 优缺点

1,优点:

  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

2,缺点:

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对"开闭原则"的支持并不太好。

 

6.5.5 使用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

 

6.6 观察者模式

6.6.1 概述

定义:

又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。

 

6.6.2 结构

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

 

6.6.3 案例实现

【例】微信公众号

在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

类图如下:

代码如下:

定义抽象观察者类,里面定义一个更新的方法

1
public interface Observer {
2
    void update(String message);
3
}

定义具体观察者类,微信用户是观察者,里面实现了更新的方法

1
public class WeixinUser implements Observer {
2
    // 微信用户名
3
    private String name;
45
    public WeixinUser(String name) {
6
        this.name = name;
7
    }
8
    @Override
9
    public void update(String message) {
10
        System.out.println(name + "-" + message);
11
    }
12
}

定义抽象主题类,提供了attach、detach、notify三个方法

1
public interface Subject {
2
    //增加订阅者
3
    public void attach(Observer observer);
45
    //删除订阅者
6
    public void detach(Observer observer);
7
    
8
    //通知订阅者更新消息
9
    public void notify(String message);
10
}
11

微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法

1
public class SubscriptionSubject implements Subject {
2
    //储存订阅公众号的微信用户
3
    private List<Observer> weixinUserlist = new ArrayList<Observer>();
45
    @Override
6
    public void attach(Observer observer) {
7
        weixinUserlist.add(observer);
8
    }
910
    @Override
11
    public void detach(Observer observer) {
12
        weixinUserlist.remove(observer);
13
    }
1415
    @Override
16
    public void notify(String message) {
17
        for (Observer observer : weixinUserlist) {
18
            observer.update(message);
19
        }
20
    }
21
}

客户端程序

1
public class Client {
2
    public static void main(String[] args) {
3
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
4
        //创建微信用户
5
        WeixinUser user1=new WeixinUser("孙悟空");
6
        WeixinUser user2=new WeixinUser("猪悟能");
7
        WeixinUser user3=new WeixinUser("沙悟净");
8
        //订阅公众号
9
        mSubscriptionSubject.attach(user1);
10
        mSubscriptionSubject.attach(user2);
11
        mSubscriptionSubject.attach(user3);
12
        //公众号更新发出消息给订阅的微信用户
13
        mSubscriptionSubject.notify("传智黑马的专栏更新了");
14
    }
15
}
16
​

 

6.6.4 优缺点

1,优点:

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】

2,缺点:

  • 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
  • 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃

 

6.6.5 使用场景

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。

 

6.6.6 JDK中提供的实现

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

1,Observable类

Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

  • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
  • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
  • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

2,Observer 接口

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。

【例】警察抓小偷

警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:

小偷是一个被观察者,所以需要继承Observable类

1
public class Thief extends Observable {
23
    private String name;
45
    public Thief(String name) {
6
        this.name = name;
7
    }
8
    
9
    public void setName(String name) {
10
        this.name = name;
11
    }
1213
    public String getName() {
14
        return name;
15
    }
1617
    public void steal() {
18
        System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
19
        super.setChanged(); //changed  = true
20
        super.notifyObservers();
21
    }
22
}
23

警察是一个观察者,所以需要让其实现Observer接口

1
public class Policemen implements Observer {
23
    private String name;
45
    public Policemen(String name) {
6
        this.name = name;
7
    }
8
    public void setName(String name) {
9
        this.name = name;
10
    }
1112
    public String getName() {
13
        return name;
14
    }
1516
    @Override
17
    public void update(Observable o, Object arg) {
18
        System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
19
    }
20
}

客户端代码

1
public class Client {
2
    public static void main(String[] args) {
3
        //创建小偷对象
4
        Thief t = new Thief("隔壁老王");
5
        //创建警察对象
6
        Policemen p = new Policemen("小李");
7
        //让警察盯着小偷
8
        t.addObserver(p);
9
        //小偷偷东西
10
        t.steal();
11
    }
12
}

 

6.7 中介者模式

6.7.1 概述

一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。

如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。

定义:

又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

 

6.7.2 结构

中介者模式包含以下主要角色:

  • 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  • 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  • 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  • 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

 

6.7.3 案例实现

【例】租房

现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。

类图如下:

代码如下:

1
//抽象中介者
2
public abstract class Mediator {
3
    //申明一个联络方法
4
    public abstract void constact(String message,Person person);
5
}
67
//抽象同事类
8
public abstract class Person {
9
    protected String name;
10
    protected Mediator mediator;
1112
    public Person(String name,Mediator mediator){
13
        this.name = name;
14
        this.mediator = mediator;
15
    }
16
}
1718
//具体同事类 房屋拥有者
19
public class HouseOwner extends Person {
2021
    public HouseOwner(String name, Mediator mediator) {
22
        super(name, mediator);
23
    }
2425
    //与中介者联系
26
    public void constact(String message){
27
        mediator.constact(message, this);
28
    }
2930
    //获取信息
31
    public void getMessage(String message){
32
        System.out.println("房主" + name +"获取到的信息:" + message);
33
    }
34
}
3536
//具体同事类 承租人
37
public class Tenant extends Person {
38
    public Tenant(String name, Mediator mediator) {
39
        super(name, mediator);
40
    }
4142
    //与中介者联系
43
    public void constact(String message){
44
        mediator.constact(message, this);
45
    }
4647
    //获取信息
48
    public void getMessage(String message){
49
        System.out.println("租房者" + name +"获取到的信息:" + message);
50
    }
51
}
5253
//中介机构
54
public class MediatorStructure extends Mediator {
55
    //首先中介结构必须知道所有房主和租房者的信息
56
    private HouseOwner houseOwner;
57
    private Tenant tenant;
5859
    public HouseOwner getHouseOwner() {
60
        return houseOwner;
61
    }
6263
    public void setHouseOwner(HouseOwner houseOwner) {
64
        this.houseOwner = houseOwner;
65
    }
6667
    public Tenant getTenant() {
68
        return tenant;
69
    }
7071
    public void setTenant(Tenant tenant) {
72
        this.tenant = tenant;
73
    }
7475
    public void constact(String message, Person person) {
76
        if (person == houseOwner) {          //如果是房主,则租房者获得信息
77
            tenant.getMessage(message);
78
        } else {       //反正则是房主获得信息
79
            houseOwner.getMessage(message);
80
        }
81
    }
82
}
8384
//测试类
85
public class Client {
86
    public static void main(String[] args) {
87
        //一个房主、一个租房者、一个中介机构
88
        MediatorStructure mediator = new MediatorStructure();
8990
        //房主和租房者只需要知道中介机构即可
91
        HouseOwner houseOwner = new HouseOwner("张三", mediator);
92
        Tenant tenant = new Tenant("李四", mediator);
9394
        //中介结构要知道房主和租房者
95
        mediator.setHouseOwner(houseOwner);
96
        mediator.setTenant(tenant);
9798
        tenant.constact("需要租三室的房子");
99
        houseOwner.constact("我这有三室的房子,你需要租吗?");
100
    }
101
}

 

6.7.4 优缺点

1,优点:

  • 松散耦合

    中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了。

  • 集中控制交互

    多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。

  • 一对多关联转变为一对一的关联

    没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。

2,缺点:

当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

 

6.7.5 使用场景

  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

 

 

6.8 迭代器模式

6.8.1 概述

定义:

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

 

6.8.2 结构

迭代器模式主要包含以下角色:

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

 

6.8.3 案例实现

【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:

 

代码如下:

定义迭代器接口,声明hasNext、next方法

1
public interface StudentIterator {
2
    boolean hasNext();
3
    Student next();
4
}

定义具体的迭代器类,重写所有的抽象方法

1
public class StudentIteratorImpl implements StudentIterator {
2
    private List<Student> list;
3
    private int position = 0;
45
    public StudentIteratorImpl(List<Student> list) {
6
        this.list = list;
7
    }
89
    @Override
10
    public boolean hasNext() {
11
        return position < list.size();
12
    }
1314
    @Override
15
    public Student next() {
16
        Student currentStudent = list.get(position);
17
        position ++;
18
        return currentStudent;
19
    }
20
}

定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法

1
public interface StudentAggregate {
2
    void addStudent(Student student);
34
    void removeStudent(Student student);
56
    StudentIterator getStudentIterator();
7
}

定义具体的容器类,重写所有的方法

1
public class StudentAggregateImpl implements StudentAggregate {
23
    private List<Student> list = new ArrayList<Student>();  // 学生列表
45
    @Override
6
    public void addStudent(Student student) {
7
        this.list.add(student);
8
    }
910
    @Override
11
    public void removeStudent(Student student) {
12
        this.list.remove(student);
13
    }
1415
    @Override
16
    public StudentIterator getStudentIterator() {
17
        return new StudentIteratorImpl(list);
18
    }
19
}

 

6.8.4 优缺点

1,优点:

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

2,缺点:

增加了类的个数,这在一定程度上增加了系统的复杂性。

 

6.8.5 使用场景

  • 当需要为聚合对象提供多种遍历方式时。
  • 当需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

 

6.8.6 JDK源码解析

迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。

1
List<String> list = new ArrayList<>();
2
Iterator<String> iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
3
while (iterator.hasNext()) {
4
    System.out.println(iterator.next());
5
}

看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明

  • List:抽象聚合类
  • ArrayList:具体的聚合类
  • Iterator:抽象迭代器
  • list.iterator():返回的是实现了 Iterator 接口的具体迭代器对象

具体的来看看 ArrayList的代码实现

1
public class ArrayList<E> extends AbstractList<E>
2
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
3
    
4
    public Iterator<E> iterator() {
5
        return new Itr();
6
    }
7
    
8
    private class Itr implements Iterator<E> {
9
        int cursor;       // 下一个要返回元素的索引
10
        int lastRet = -1; // 上一个返回元素的索引
11
        int expectedModCount = modCount;
1213
        Itr() {}
14
        
15
        //判断是否还有元素
16
        public boolean hasNext() {
17
            return cursor != size;
18
        }
1920
        //获取下一个元素
21
        public E next() {
22
            checkForComodification();
23
            int i = cursor;
24
            if (i >= size)
25
                throw new NoSuchElementException();
26
            Object[] elementData = ArrayList.this.elementData;
27
            if (i >= elementData.length)
28
                throw new ConcurrentModificationException();
29
            cursor = i + 1;
30
            return (E) elementData[lastRet = i];
31
        }
32
        ...
33
}

这部分代码还是比较简单,大致就是在 iterator 方法中返回了一个实例化的 Iterator 对象。Itr是一个内部类,它实现了 Iterator 接口并重写了其中的抽象方法。

注意:

当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。

 

 

6.9 访问者模式

6.9.1 概述

定义:

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

 

6.9.2 结构

访问者模式包含以下主要角色:

  • 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

 

6.9.3 案例实现

【例】给宠物喂食

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

类图如下:

代码如下:

创建抽象访问者接口

1
public interface Person {
2
    void feed(Cat cat);
34
    void feed(Dog dog);
5
}

创建不同的具体访问者角色(主人和其他人),都需要实现 Person接口

1
public class Owner implements Person {
23
    @Override
4
    public void feed(Cat cat) {
5
        System.out.println("主人喂食猫");
6
    }
78
    @Override
9
    public void feed(Dog dog) {
10
        System.out.println("主人喂食狗");
11
    }
12
}
1314
public class Someone implements Person {
15
    @Override
16
    public void feed(Cat cat) {
17
        System.out.println("其他人喂食猫");
18
    }
1920
    @Override
21
    public void feed(Dog dog) {
22
        System.out.println("其他人喂食狗");
23
    }
24
}

定义抽象节点 -- 宠物

1
public interface Animal {
2
    void accept(Person person);
3
}

定义实现Animal接口的 具体节点(元素)

1
public class Dog implements Animal {
23
    @Override
4
    public void accept(Person person) {
5
        person.feed(this);
6
        System.out.println("好好吃,汪汪汪!!!");
7
    }
8
}
910
public class Cat implements Animal {
1112
    @Override
13
    public void accept(Person person) {
14
        person.feed(this);
15
        System.out.println("好好吃,喵喵喵!!!");
16
    }
17
}

定义对象结构,此案例中就是主人的家

1
public class Home {
2
    private List<Animal> nodeList = new ArrayList<Animal>();
34
    public void action(Person person) {
5
        for (Animal node : nodeList) {
6
            node.accept(person);
7
        }
8
    }
910
    //添加操作
11
    public void add(Animal animal) {
12
        nodeList.add(animal);
13
    }
14
}
15

测试类

1
public class Client {
2
    public static void main(String[] args) {
3
        Home home = new Home();
4
        home.add(new Dog());
5
        home.add(new Cat());
67
        Owner owner = new Owner();
8
        home.action(owner);
910
        Someone someone = new Someone();
11
        home.action(someone);
12
    }
13
}

 

6.9.4 优缺点

1,优点:

  • 扩展性好

    在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好

    通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为

    通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

2,缺点:

  • 对象结构变化很困难

    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则

    访问者模式依赖了具体类,而没有依赖抽象类。

 

6.9.5 使用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。

  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

     

6.9.6 扩展

访问者模式用到了一种双分派的技术。

1,分派:

变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map ,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。

静态分派(Static Dispatch)  发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

动态分派(Dynamic Dispatch)  发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。

2,动态分派:

通过方法的重写支持动态分派。

1
public class Animal {
2
    public void execute() {
3
        System.out.println("Animal");
4
    }
5
}
67
public class Dog extends Animal {
8
    @Override
9
    public void execute() {
10
        System.out.println("dog");
11
    }
12
}
1314
public class Cat extends Animal {
15
     @Override
16
    public void execute() {
17
        System.out.println("cat");
18
    }
19
}
2021
public class Client {
22
    public static void main(String[] args) {
23
        Animal a = new Dog();
24
        a.execute();
25
        
26
        Animal a1 = new Cat();
27
        a1.execute();
28
    }
29
}

上面代码的结果大家应该直接可以说出来,这不就是多态吗!运行执行的是子类中的方法。

Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。

3,静态分派:

通过方法重载支持静态分派。

1
public class Animal {
2
}
34
public class Dog extends Animal {
5
}
67
public class Cat extends Animal {
8
}
910
public class Execute {
11
    public void execute(Animal a) {
12
        System.out.println("Animal");
13
    }
1415
    public void execute(Dog d) {
16
        System.out.println("dog");
17
    }
1819
    public void execute(Cat c) {
20
        System.out.println("cat");
21
    }
22
}
2324
public class Client {
25
    public static void main(String[] args) {
26
        Animal a = new Animal();
27
        Animal a1 = new Dog();
28
        Animal a2 = new Cat();
2930
        Execute exe = new Execute();
31
        exe.execute(a);
32
        exe.execute(a1);
33
        exe.execute(a2);
34
    }
35
}

运行结果:

这个结果可能出乎一些人的意料了,为什么呢?

重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。

4,双分派:

所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。

1
public class Animal {
2
    public void accept(Execute exe) {
3
        exe.execute(this);
4
    }
5
}
67
public class Dog extends Animal {
8
    public void accept(Execute exe) {
9
        exe.execute(this);
10
    }
11
}
1213
public class Cat extends Animal {
14
    public void accept(Execute exe) {
15
        exe.execute(this);
16
    }
17
}
1819
public class Execute {
20
    public void execute(Animal a) {
21
        System.out.println("animal");
22
    }
2324
    public void execute(Dog d) {
25
        System.out.println("dog");
26
    }
2728
    public void execute(Cat c) {
29
        System.out.println("cat");
30
    }
31
}
3233
public class Client {
34
    public static void main(String[] args) {
35
        Animal a = new Animal();
36
        Animal d = new Dog();
37
        Animal c = new Cat();
3839
        Execute exe = new Execute();
40
        a.accept(exe);
41
        d.accept(exe);
42
        c.accept(exe);
43
    }
44
}

在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。

说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。

运行结果如下:

双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。

 

6.10 备忘录模式

6.10.1 概述

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

定义:

又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。

 

6.10.2 结构

备忘录模式的主要角色如下:

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

备忘录有两个等效的接口:

  • 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
  • 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

 

6.10.3 案例实现

【例】游戏挑战BOSS

游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。

要实现上述案例,有两种方式:

  • “白箱”备忘录模式
  • “黑箱”备忘录模式

 

6.10.3.1 “白箱”备忘录模式

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:

代码如下:

1
//游戏角色类
2
public class GameRole {
3
    private int vit; //生命力
4
    private int atk; //攻击力
5
    private int def; //防御力
67
    //初始化状态
8
    public void initState() {
9
        this.vit = 100;
10
        this.atk = 100;
11
        this.def = 100;
12
    }
1314
    //战斗
15
    public void fight() {
16
        this.vit = 0;
17
        this.atk = 0;
18
        this.def = 0;
19
    }
2021
    //保存角色状态
22
    public RoleStateMemento saveState() {
23
        return new RoleStateMemento(vit, atk, def);
24
    }
2526
    //回复角色状态
27
    public void recoverState(RoleStateMemento roleStateMemento) {
28
        this.vit = roleStateMemento.getVit();
29
        this.atk = roleStateMemento.getAtk();
30
        this.def = roleStateMemento.getDef();
31
    }
3233
    public void stateDisplay() {
34
        System.out.println("角色生命力:" + vit);
35
        System.out.println("角色攻击力:" + atk);
36
        System.out.println("角色防御力:" + def);
37
    }
3839
    public int getVit() {
40
        return vit;
41
    }
4243
    public void setVit(int vit) {
44
        this.vit = vit;
45
    }
4647
    public int getAtk() {
48
        return atk;
49
    }
5051
    public void setAtk(int atk) {
52
        this.atk = atk;
53
    }
5455
    public int getDef() {
56
        return def;
57
    }
5859
    public void setDef(int def) {
60
        this.def = def;
61
    }
62
}
6364
//游戏状态存储类(备忘录类)
65
public class RoleStateMemento {
66
    private int vit;
67
    private int atk;
68
    private int def;
6970
    public RoleStateMemento(int vit, int atk, int def) {
71
        this.vit = vit;
72
        this.atk = atk;
73
        this.def = def;
74
    }
7576
    public int getVit() {
77
        return vit;
78
    }
7980
    public void setVit(int vit) {
81
        this.vit = vit;
82
    }
8384
    public int getAtk() {
85
        return atk;
86
    }
8788
    public void setAtk(int atk) {
89
        this.atk = atk;
90
    }
9192
    public int getDef() {
93
        return def;
94
    }
9596
    public void setDef(int def) {
97
        this.def = def;
98
    }
99
}
100101
//角色状态管理者类
102
public class RoleStateCaretaker {
103
    private RoleStateMemento roleStateMemento;
104105
    public RoleStateMemento getRoleStateMemento() {
106
        return roleStateMemento;
107
    }
108109
    public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
110
        this.roleStateMemento = roleStateMemento;
111
    }
112
}
113114
//测试类
115
public class Client {
116
    public static void main(String[] args) {
117
        System.out.println("------------大战Boss前------------");
118
        //大战Boss前
119
        GameRole gameRole = new GameRole();
120
        gameRole.initState();
121
        gameRole.stateDisplay();
122123
        //保存进度
124
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
125
        roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
126127
        System.out.println("------------大战Boss后------------");
128
        //大战Boss时,损耗严重
129
        gameRole.fight();
130
        gameRole.stateDisplay();
131
        System.out.println("------------恢复之前状态------------");
132
        //恢复之前状态
133
        gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
134
        gameRole.stateDisplay();
135136
    }
137
}

分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。

 

6.10.3.2 “黑箱”备忘录模式

备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。

将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在 GameRole 里面;在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。类图如下:

代码如下:

窄接口Memento,这是一个标识接口,因此没有定义出任何的方法

1
public interface Memento {
2
}

定义发起人类 GameRole,并在内部定义备忘录内部类 RoleStateMemento(该内部类设置为私有的)

1
/游戏角色类
2
public class GameRole {
3
    private int vit; //生命力
4
    private int atk; //攻击力
5
    private int def; //防御力
67
    //初始化状态
8
    public void initState() {
9
        this.vit = 100;
10
        this.atk = 100;
11
        this.def = 100;
12
    }
1314
    //战斗
15
    public void fight() {
16
        this.vit = 0;
17
        this.atk = 0;
18
        this.def = 0;
19
    }
2021
    //保存角色状态
22
    public Memento saveState() {
23
        return new RoleStateMemento(vit, atk, def);
24
    }
2526
    //回复角色状态
27
    public void recoverState(Memento memento) {
28
        RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
29
        this.vit = roleStateMemento.getVit();
30
        this.atk = roleStateMemento.getAtk();
31
        this.def = roleStateMemento.getDef();
32
    }
3334
    public void stateDisplay() {
35
        System.out.println("角色生命力:" + vit);
36
        System.out.println("角色攻击力:" + atk);
37
        System.out.println("角色防御力:" + def);
3839
    }
4041
    public int getVit() {
42
        return vit;
43
    }
4445
    public void setVit(int vit) {
46
        this.vit = vit;
47
    }
4849
    public int getAtk() {
50
        return atk;
51
    }
5253
    public void setAtk(int atk) {
54
        this.atk = atk;
55
    }
5657
    public int getDef() {
58
        return def;
59
    }
6061
    public void setDef(int def) {
62
        this.def = def;
63
    }
6465
    private class RoleStateMemento implements Memento {
66
        private int vit;
67
        private int atk;
68
        private int def;
6970
        public RoleStateMemento(int vit, int atk, int def) {
71
            this.vit = vit;
72
            this.atk = atk;
73
            this.def = def;
74
        }
7576
        public int getVit() {
77
            return vit;
78
        }
7980
        public void setVit(int vit) {
81
            this.vit = vit;
82
        }
8384
        public int getAtk() {
85
            return atk;
86
        }
8788
        public void setAtk(int atk) {
89
            this.atk = atk;
90
        }
9192
        public int getDef() {
93
            return def;
94
        }
9596
        public void setDef(int def) {
97
            this.def = def;
98
        }
99
    }
100
}

负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容

1
//角色状态管理者类
2
public class RoleStateCaretaker {
3
    private Memento memento;
45
    public Memento getMemento() {
6
        return memento;
7
    }
89
    public void setMemento(Memento memento) {
10
        this.memento = memento;
11
    }
12
}

客户端测试类

1
public class Client {
2
    public static void main(String[] args) {
3
        System.out.println("------------大战Boss前------------");
4
        //大战Boss前
5
        GameRole gameRole = new GameRole();
6
        gameRole.initState();
7
        gameRole.stateDisplay();
89
        //保存进度
10
        RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
11
        roleStateCaretaker.setMemento(gameRole.saveState());
12
        
13
        System.out.println("------------大战Boss后------------");
14
        //大战Boss时,损耗严重
15
        gameRole.fight();
16
        gameRole.stateDisplay();
17
        System.out.println("------------恢复之前状态------------");
18
        //恢复之前状态
19
        gameRole.recoverState(roleStateCaretaker.getMemento());
20
        gameRole.stateDisplay();
21
    }
22
}
23

 

6.10.4 优缺点

1,优点:

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

2,缺点:

  • 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

 

6.10.5 使用场景

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。

6,行为型模式

6.11 解释器模式

6.11.1 概述

如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。

1
//用于两个整数相加
2
public static int add(int a,int b){
3
    return a + b;
4
}
56
//用于两个整数相加
7
public static int add(int a,int b,int c){
8
    return a + b + c;
9
}
1011
//用于n个整数相加
12
public static int add(Integer ... arr) {
13
    int sum = 0;
14
    for (Integer i : arr) {
15
        sum += i;
16
    }
17
    return sum;
18
}

上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。

显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。

定义:

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和+-符号组成的合法序列,“1+3-2” 就是这种语言的句子。

解释器就是要解析出来语句的含义。但是如何描述规则呢?

文法(语法)规则:

文法是用于描述语言的语法结构的形式规则。

1
expression ::= value | plus | minus
2
plus ::= expression+expression   
3
minus ::= expression-expression  
4
value ::= integer

注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。

上面规则描述为 :

表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树:

在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

用树形来表示符合文法规则的句子。

6.11.2 结构

解释器模式包含以下主要角色。

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

 

6.11.3 案例实现

【例】设计实现加减法的软件

代码如下:

1
//抽象角色AbstractExpression
2
public abstract class AbstractExpression {
3
    public abstract int interpret(Context context);
4
}
56
//终结符表达式角色
7
public class Value extends AbstractExpression {
8
    private int value;
910
    public Value(int value) {
11
        this.value = value;
12
    }
1314
    @Override
15
    public int interpret(Context context) {
16
        return value;
17
    }
1819
    @Override
20
    public String toString() {
21
        return new Integer(value).toString();
22
    }
23
}
2425
//非终结符表达式角色  加法表达式
26
public class Plus extends AbstractExpression {
27
    private AbstractExpression left;
28
    private AbstractExpression right;
2930
    public Plus(AbstractExpression left, AbstractExpression right) {
31
        this.left = left;
32
        this.right = right;
33
    }
3435
    @Override
36
    public int interpret(Context context) {
37
        return left.interpret(context) + right.interpret(context);
38
    }
3940
    @Override
41
    public String toString() {
42
        return "(" + left.toString() + " + " + right.toString() + ")";
43
    }
44
}
4546
///非终结符表达式角色 减法表达式
47
public class Minus extends AbstractExpression {
48
    private AbstractExpression left;
49
    private AbstractExpression right;
5051
    public Minus(AbstractExpression left, AbstractExpression right) {
52
        this.left = left;
53
        this.right = right;
54
    }
5556
    @Override
57
    public int interpret(Context context) {
58
        return left.interpret(context) - right.interpret(context);
59
    }
6061
    @Override
62
    public String toString() {
63
        return "(" + left.toString() + " - " + right.toString() + ")";
64
    }
65
}
6667
//终结符表达式角色 变量表达式
68
public class Variable extends AbstractExpression {
69
    private String name;
7071
    public Variable(String name) {
72
        this.name = name;
73
    }
7475
    @Override
76
    public int interpret(Context ctx) {
77
        return ctx.getValue(this);
78
    }
7980
    @Override
81
    public String toString() {
82
        return name;
83
    }
84
}
8586
//环境类
87
public class Context {
88
    private Map<Variable, Integer> map = new HashMap<Variable, Integer>();
8990
    public void assign(Variable var, Integer value) {
91
        map.put(var, value);
92
    }
9394
    public int getValue(Variable var) {
95
        Integer value = map.get(var);
96
        return value;
97
    }
98
}
99100
//测试类
101
public class Client {
102
    public static void main(String[] args) {
103
        Context context = new Context();
104105
        Variable a = new Variable("a");
106
        Variable b = new Variable("b");
107
        Variable c = new Variable("c");
108
        Variable d = new Variable("d");
109
        Variable e = new Variable("e");
110
        //Value v = new Value(1);
111112
        context.assign(a, 1);
113
        context.assign(b, 2);
114
        context.assign(c, 3);
115
        context.assign(d, 4);
116
        context.assign(e, 5);
117118
        AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);
119120
        System.out.println(expression + "= " + expression.interpret(context));
121
    }
122
}

 

6.11.4 优缺点

1,优点:

  • 易于改变和扩展文法。

    由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

  • 实现文法较为容易。

    在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。

  • 增加新的解释表达式较为方便。

    如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 "开闭原则"。

2,缺点:

  • 对于复杂文法难以维护。

    在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。

  • 执行效率较低。

    由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

 

6.11.5 使用场景

  • 当语言的文法较为简单,且执行效率不是关键问题时。
  • 当问题重复出现,且可以用一种简单的语言来进行表达时。
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。

 

7,自定义Spring框架

7.1 spring使用回顾

自定义spring框架前,先回顾一下spring框架的使用,从而分析spring的核心,并对核心功能进行模拟。

  • 数据访问层。定义UserDao接口及其子实现类

    1
    public interface UserDao {
    2
        public void add();
    3
    }
    45
    public class UserDaoImpl implements UserDao {
    67
        public void add() {
    8
            System.out.println("userDaoImpl ....");
    9
        }
    10
    }
    
  • 业务逻辑层。定义UserService接口及其子实现类

    1
    public interface UserService {
    2
        public void add();
    3
    }
    45
    public class UserServiceImpl implements UserService {
    67
        private UserDao userDao;
    89
        public void setUserDao(UserDao userDao) {
    10
            this.userDao = userDao;
    11
        }
    1213
        public void add() {
    14
            System.out.println("userServiceImpl ...");
    15
            userDao.add();
    16
        }
    17
    }
    
  • 定义UserController类,使用main方法模拟controller层

    1
    public class UserController {
    2
        public static void main(String[] args) {
    3
            //创建spring容器对象
    4
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    5
            //从IOC容器中获取UserService对象
    6
            UserService userService = applicationContext.getBean("userService", UserService.class);
    7
            //调用UserService对象的add方法
    8
            userService.add();
    9
        }
    10
    }
    
  • 编写配置文件。在类路径下编写一个名为ApplicationContext.xml的配置文件

    1
    <?xml version="1.0" encoding="UTF-8"?>
    2
    <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3
           xmlns="http://www.springframework.org/schema/beans"
    4
           xmlns:context="http://www.springframework.org/schema/context"
    5
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    6
            http://www.springframework.org/schema/beans/spring-beans.xsd
    7
            http://www.springframework.org/schema/context
    8
            http://www.springframework.org/schema/context/spring-context.xsd">
    9
    ​
    10
        <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
    11
            <property name="userDao" ref="userDao"></property>
    12
        </bean>
    13
    ​
    14
        <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
    15
    ​
    16
    </beans>
    

    代码运行结果如下:

通过上面代码及结果可以看出:

  • userService对象是从applicationContext容器对象获取到的,也就是userService对象交由spring进行管理。
  • 上面结果可以看到调用了UserDao对象中的add方法,也就是说UserDao子实现类对象也交由spring管理了。
  • UserService中的userDao变量我们并没有进行赋值,但是可以正常使用,说明spring已经将UserDao对象赋值给了userDao变量。

上面三点体现了Spring框架的IOC(Inversion of Control)和DI(Dependency Injection, DI)

 

7.2 spring核心功能结构

Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为:

核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试等,下面是 Spring 框架的总体架构图:

 

核心容器由 beans、core、context 和 expression(Spring Expression Language,SpEL)4个模块组成。

  • spring-beans和spring-core模块是Spring框架的核心模块,包含了控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)。BeanFactory使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。BeanFactory属于延时加载,也就是说在实例化容器对象后并不会自动实例化Bean,只有当Bean被使用时,BeanFactory才会对该 Bean 进行实例化与依赖关系的装配。
  • spring-context模块构架于核心模块之上,扩展了BeanFactory,为它添加了Bean生命周期控制、框架事件体系及资源加载透明化等功能。此外,该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext 是该模块的核心接口,它的超类是 BeanFactory。与BeanFactory不同,ApplicationContext实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
  • spring-context-support模块是对Spring IoC容器及IoC子容器的扩展支持。
  • spring-context-indexer模块是Spring的类管理组件和Classpath扫描组件。
  • spring-expression 模块是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也可以方便地调用对象方法,以及操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。EL的特性是基于Spring产品的需求而设计的,可以非常方便地同Spring IoC进行交互。

 

7.1.1 bean概述

Spring 就是面向 Bean 的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中处于核心地位。Bean对于Spring的意义就像Object对于OOP的意义一样,Spring中没有Bean也就没有Spring存在的意义。Spring IoC容器通过配置文件或者注解的方式来管理bean对象之间的依赖关系。

spring中bean用于对一个类进行封装。如下面的配置:

1
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
2
    <property name="userDao" ref="userDao"></property>
3
</bean>
4
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>

为什么Bean如此重要呢?

  • spring 将bean对象交由一个叫IOC容器进行管理。
  • bean对象之间的依赖关系在配置文件中体现,并由spring完成。

 

7.3 Spring IOC相关接口分析

7.3.1 BeanFactory解析

Spring中Bean的创建是典型的工厂模式,这一系列的Bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现供用户选择,其相互关系如下图所示。

其中,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,BeanFactory有三个重要的子接口:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。

那么为何要定义这么多层次的接口呢?

每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。例如,

  • ListableBeanFactory接口表示这些Bean可列表化。
  • HierarchicalBeanFactory表示这些Bean 是有继承关系的,也就是每个 Bean 可能有父 Bean
  • AutowireCapableBeanFactory 接口定义Bean的自动装配规则。

这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。最基本的IoC容器接口是BeanFactory,来看一下它的源码:

1
public interface BeanFactory {
23
    String FACTORY_BEAN_PREFIX = "&";
45
    //根据bean的名称获取IOC容器中的的bean对象
6
    Object getBean(String name) throws BeansException;
7
    //根据bean的名称获取IOC容器中的的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了
8
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
9
    Object getBean(String name, Object... args) throws BeansException;
10
    <T> T getBean(Class<T> requiredType) throws BeansException;
11
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
12
    
13
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
14
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
1516
    //判断容器中是否包含指定名称的bean对象
17
    boolean containsBean(String name);
18
    //根据bean的名称判断是否是单例
19
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
20
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
21
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
22
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
23
    @Nullable
24
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
25
    String[] getAliases(String name);
26
}

在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。

BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。

要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:

  • ClasspathXmlApplicationContext : 根据类路径加载xml配置文件,并创建IOC容器对象。
  • FileSystemXmlApplicationContext :根据系统路径加载xml配置文件,并创建IOC容器对象。
  • AnnotationConfigApplicationContext :加载注解类配置,并创建IOC容器。

 

7.3.2 BeanDefinition解析

Spring IoC容器管理我们定义的各种Bean对象及其相互关系,而Bean对象在Spring实现中是以BeanDefinition来描述的,如下面配置文件

1
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
23
bean标签还有很多属性:
4
    scope、init-method、destory-method等。

其继承体系如下图所示。

 

7.3.3 BeanDefinitionReader解析

Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。这个解析过程主要通过BeanDefinitionReader来完成,看看Spring中BeanDefinitionReader的类结构图,如下图所示。

看看BeanDefinitionReader接口定义的功能来理解它具体的作用:

1
public interface BeanDefinitionReader {
23
    //获取BeanDefinitionRegistry注册器对象
4
    BeanDefinitionRegistry getRegistry();
56
    @Nullable
7
    ResourceLoader getResourceLoader();
89
    @Nullable
10
    ClassLoader getBeanClassLoader();
1112
    BeanNameGenerator getBeanNameGenerator();
1314
    /*
15
        下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
16
    */
17
    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
18
    int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
19
    int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
20
    int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
21
}

 

7.3.4 BeanDefinitionRegistry解析

BeanDefinitionReader用来解析bean定义,并封装BeanDefinition对象,而我们定义的配置文件中定义了很多bean标签,所以就有一个问题,解析的BeanDefinition对象存储到哪儿?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry。

1
public interface BeanDefinitionRegistry extends AliasRegistry {
23
    //往注册表中注册bean
4
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
5
            throws BeanDefinitionStoreException;
67
    //从注册表中删除指定名称的bean
8
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
910
    //获取注册表中指定名称的bean
11
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
12
    
13
    //判断注册表中是否已经注册了指定名称的bean
14
    boolean containsBeanDefinition(String beanName);
15
    
16
    //获取注册表中所有的bean的名称
17
    String[] getBeanDefinitionNames();
18
    
19
    int getBeanDefinitionCount();
20
    boolean isBeanNameInUse(String beanName);
21
}

继承结构图如下:

从上面类图可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:

  • DefaultListableBeanFactory

    在该类中定义了如下代码,就是用来注册bean

    1
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    
  • SimpleBeanDefinitionRegistry

    在该类中定义了如下代码,就是用来注册bean

    1
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(64);
    

 

7.3.5 创建容器

ClassPathXmlApplicationContext对Bean配置资源的载入是从refresh()方法开始的。refresh()方法是一个模板方法,规定了 IoC 容器的启动流程,有些逻辑要交给其子类实现。它对 Bean 配置资源进行载入,ClassPathXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()方法启动整个IoC容器对Bean定义的载入过程。

 

 

7.4 自定义SpringIOC

现要对下面的配置文件进行解析,并自定义Spring框架的IOC对涉及到的对象进行管理。

1
<?xml version="1.0" encoding="UTF-8"?>
2
<beans>
3
    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl">
4
        <property name="userDao" ref="userDao"></property>
5
    </bean>
6
    <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"></bean>
7
</beans>

7.4.1 定义bean相关的pojo类

7.4.1.1 PropertyValue类

用于封装bean的属性,体现到上面的配置文件就是封装bean标签的子标签property标签数据。

1
public class PropertyValue {
23
  private String name;
4
  private String ref;
5
  private String value;
67
  public PropertyValue() {
8
  }
910
  public PropertyValue(String name, String ref,String value) {
11
    this.name = name;
12
    this.ref = ref;
13
    this.value = value;
14
  }
1516
  public String getName() {
17
    return name;
18
  }
1920
  public void setName(String name) {
21
    this.name = name;
22
  }
2324
  public String getRef() {
25
    return ref;
26
  }
2728
  public void setRef(String ref) {
29
    this.ref = ref;
30
  }
3132
  public String getValue() {
33
    return value;
34
  }
3536
  public void setValue(String value) {
37
    this.value = value;
38
  }
39
}

 

7.4.1.2 MutablePropertyValues类

一个bean标签可以有多个property子标签,所以再定义一个MutablePropertyValues类,用来存储并管理多个PropertyValue对象。

1
public class MutablePropertyValues implements Iterable<PropertyValue> {
23
    private final List<PropertyValue> propertyValueList;
45
    public MutablePropertyValues() {
6
        this.propertyValueList = new ArrayList<PropertyValue>();
7
    }
89
    public MutablePropertyValues(List<PropertyValue> propertyValueList) {
10
        this.propertyValueList = (propertyValueList != null ? propertyValueList : new ArrayList<PropertyValue>());
11
    }
1213
    public PropertyValue[] getPropertyValues() {
14
        return this.propertyValueList.toArray(new PropertyValue[0]);
15
    }
1617
    public PropertyValue getPropertyValue(String propertyName) {
18
        for (PropertyValue pv : this.propertyValueList) {
19
            if (pv.getName().equals(propertyName)) {
20
                return pv;
21
            }
22
        }
23
        return null;
24
    }
2526
    @Override
27
    public Iterator<PropertyValue> iterator() {
28
        return propertyValueList.iterator();
29
    }
3031
    public boolean isEmpty() {
32
        return this.propertyValueList.isEmpty();
33
    }
3435
    public MutablePropertyValues addPropertyValue(PropertyValue pv) {
36
        for (int i = 0; i < this.propertyValueList.size(); i++) {
37
            PropertyValue currentPv = this.propertyValueList.get(i);
38
            if (currentPv.getName().equals(pv.getName())) {
39
                this.propertyValueList.set(i, new PropertyValue(pv.getName(),pv.getRef(), pv.getValue()));
40
                return this;
41
            }
42
        }
43
        this.propertyValueList.add(pv);
44
        return this;
45
    }
4647
    public boolean contains(String propertyName) {
48
        return getPropertyValue(propertyName) != null;
49
    }
50
}

 

7.4.1.3 BeanDefinition类

BeanDefinition类用来封装bean信息的,主要包含id(即bean对象的名称)、class(需要交由spring管理的类的全类名)及子标签property数据。

1
public class BeanDefinition {
2
    private String id;
3
    private String className;
45
    private MutablePropertyValues propertyValues;
67
    public BeanDefinition() {
8
        propertyValues = new MutablePropertyValues();
9
    }
1011
    public String getId() {
12
        return id;
13
    }
1415
    public void setId(String id) {
16
        this.id = id;
17
    }
1819
    public String getClassName() {
20
        return className;
21
    }
2223
    public void setClassName(String className) {
24
        this.className = className;
25
    }
2627
    public void setPropertyValues(MutablePropertyValues propertyValues) {
28
        this.propertyValues = propertyValues;
29
    }
3031
    public MutablePropertyValues getPropertyValues() {
32
        return propertyValues;
33
    }
34
}

 

7.4.2 定义注册表相关类

7.4.2.1 BeanDefinitionRegistry接口

BeanDefinitionRegistry接口定义了注册表的相关操作,定义如下功能:

  • 注册BeanDefinition对象到注册表中
  • 从注册表中删除指定名称的BeanDefinition对象
  • 根据名称从注册表中获取BeanDefinition对象
  • 判断注册表中是否包含指定名称的BeanDefinition对象
  • 获取注册表中BeanDefinition对象的个数
  • 获取注册表中所有的BeanDefinition的名称
1
public interface BeanDefinitionRegistry {
23
    //注册BeanDefinition对象到注册表中
4
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
56
    //从注册表中删除指定名称的BeanDefinition对象
7
    void removeBeanDefinition(String beanName) throws Exception;
89
    //根据名称从注册表中获取BeanDefinition对象
10
    BeanDefinition getBeanDefinition(String beanName) throws Exception;
1112
    boolean containsBeanDefinition(String beanName);
1314
    int getBeanDefinitionCount();
1516
    String[] getBeanDefinitionNames();
17
}

 

7.4.2.2 SimpleBeanDefinitionRegistry类

该类实现了BeanDefinitionRegistry接口,定义了Map集合作为注册表容器。

1
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
23
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();
45
    @Override
6
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
7
        beanDefinitionMap.put(beanName,beanDefinition);
8
    }
910
    @Override
11
    public void removeBeanDefinition(String beanName) throws Exception {
12
        beanDefinitionMap.remove(beanName);
13
    }
1415
    @Override
16
    public BeanDefinition getBeanDefinition(String beanName) throws Exception {
17
        return beanDefinitionMap.get(beanName);
18
    }
1920
    @Override
21
    public boolean containsBeanDefinition(String beanName) {
22
        return beanDefinitionMap.containsKey(beanName);
23
    }
2425
    @Override
26
    public int getBeanDefinitionCount() {
27
        return beanDefinitionMap.size();
28
    }
2930
    @Override
31
    public String[] getBeanDefinitionNames() {
32
        return beanDefinitionMap.keySet().toArray(new String[1]);
33
    }
34
}

 

7.4.3 定义解析器相关类

7.4.3.1 BeanDefinitionReader接口

BeanDefinitionReader是用来解析配置文件并在注册表中注册bean的信息。定义了两个规范:

  • 获取注册表的功能,让外界可以通过该对象获取注册表对象。
  • 加载配置文件,并注册bean数据。
1
public interface BeanDefinitionReader {
23
    //获取注册表对象
4
    BeanDefinitionRegistry getRegistry();
5
    //加载配置文件并在注册表中进行注册
6
    void loadBeanDefinitions(String configLocation) throws Exception;
7
}

 

7.4.3.2 XmlBeanDefinitionReader类

XmlBeanDefinitionReader类是专门用来解析xml配置文件的。该类实现BeanDefinitionReader接口并实现接口中的两个功能。

1
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
2
​
3
    private BeanDefinitionRegistry registry;
4
​
5
    public XmlBeanDefinitionReader() {
6
        this.registry = new SimpleBeanDefinitionRegistry();
7
    }
8
​
9
    @Override
10
    public BeanDefinitionRegistry getRegistry() {
11
        return registry;
12
    }
13
​
14
    @Override
15
    public void loadBeanDefinitions(String configLocation) throws Exception {
16
​
17
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configLocation);
18
        SAXReader reader = new SAXReader();
19
        Document document = reader.read(is);
20
        Element rootElement = document.getRootElement();
21
        //解析bean标签
22
        parseBean(rootElement);
23
    }
24
​
25
    private void parseBean(Element rootElement) {
26
​
27
        List<Element> elements = rootElement.elements();
28
        for (Element element : elements) {
29
            String id = element.attributeValue("id");
30
            String className = element.attributeValue("class");
31
            BeanDefinition beanDefinition = new BeanDefinition();
32
            beanDefinition.setId(id);
33
            beanDefinition.setClassName(className);
34
            List<Element> list = element.elements("property");
35
            MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
36
            for (Element element1 : list) {
37
                String name = element1.attributeValue("name");
38
                String ref = element1.attributeValue("ref");
39
                String value = element1.attributeValue("value");
40
                PropertyValue propertyValue = new PropertyValue(name,ref,value);
41
                mutablePropertyValues.addPropertyValue(propertyValue);
42
            }
43
            beanDefinition.setPropertyValues(mutablePropertyValues);
44
​
45
            registry.registerBeanDefinition(id,beanDefinition);
46
        }
47
    }
48
}

 

7.4.4 IOC容器相关类

7.4.4.1 BeanFactory接口

在该接口中定义IOC容器的统一规范即获取bean对象。

1
public interface BeanFactory {
2
    //根据bean对象的名称获取bean对象
3
    Object getBean(String name) throws Exception;
4
    //根据bean对象的名称获取bean对象,并进行类型转换
5
    <T> T getBean(String name, Class<? extends T> clazz) throws Exception;
6
}

 

7.4.4.2 ApplicationContext接口

该接口的所以的子实现类对bean对象的创建都是非延时的,所以在该接口中定义 refresh() 方法,该方法主要完成以下两个功能:

  • 加载配置文件。
  • 根据注册表中的BeanDefinition对象封装的数据进行bean对象的创建。
1
public interface ApplicationContext extends BeanFactory {
2
    //进行配置文件加载并进行对象创建
3
    void refresh() throws IllegalStateException, Exception;
4
}

 

7.4.4.3 AbstractApplicationContext类

  • 作为ApplicationContext接口的子类,所以该类也是非延时加载,所以需要在该类中定义一个Map集合,作为bean对象存储的容器。

  • 声明BeanDefinitionReader类型的变量,用来进行xml配置文件的解析,符合单一职责原则。

    BeanDefinitionReader类型的对象创建交由子类实现,因为只有子类明确到底创建BeanDefinitionReader哪儿个子实现类对象。

1
public abstract class AbstractApplicationContext implements ApplicationContext {
23
    protected BeanDefinitionReader beanDefinitionReader;
4
    //用来存储bean对象的容器   key存储的是bean的id值,value存储的是bean对象
5
    protected Map<String, Object> singletonObjects = new HashMap<String, Object>();
67
    //存储配置文件的路径
8
    protected String configLocation;
910
    public void refresh() throws IllegalStateException, Exception {
1112
        //加载BeanDefinition
13
        beanDefinitionReader.loadBeanDefinitions(configLocation);
1415
        //初始化bean
16
        finishBeanInitialization();
17
    }
1819
    //bean的初始化
20
    private void finishBeanInitialization() throws Exception {
21
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
22
        String[] beanNames = registry.getBeanDefinitionNames();
2324
        for (String beanName : beanNames) {
25
            BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
26
            getBean(beanName);
27
        }
28
    }
29
}

注意:该类finishBeanInitialization()方法中调用getBean()方法使用到了模板方法模式。

 

7.4.4.4 ClassPathXmlApplicationContext类

该类主要是加载类路径下的配置文件,并进行bean对象的创建,主要完成以下功能:

  • 在构造方法中,创建BeanDefinitionReader对象。
  • 在构造方法中,调用refresh()方法,用于进行配置文件加载、创建bean对象并存储到容器中。
  • 重写父接口中的getBean()方法,并实现依赖注入操作。
1
public class ClassPathXmlApplicationContext extends AbstractApplicationContext{
2
​
3
    public ClassPathXmlApplicationContext(String configLocation) {
4
        this.configLocation = configLocation;
5
        //构建XmlBeanDefinitionReader对象
6
        beanDefinitionReader = new XmlBeanDefinitionReader();
7
        try {
8
            this.refresh();
9
        } catch (Exception e) {
10
        }
11
    }
12
​
13
    //根据bean的id属性值获取bean对象
14
    @Override
15
    public Object getBean(String name) throws Exception {
16
​
17
        //return singletonObjects.get(name);
18
        Object obj = singletonObjects.get(name);
19
        if(obj != null) {
20
            return obj;
21
        }
22
​
23
        BeanDefinitionRegistry registry = beanDefinitionReader.getRegistry();
24
        BeanDefinition beanDefinition = registry.getBeanDefinition(name);
25
        if(beanDefinition == null) {
26
            return null;
27
        }
28
        String className = beanDefinition.getClassName();
29
        Class<?> clazz = Class.forName(className);
30
        Object beanObj = clazz.newInstance();
31
        MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
32
        for (PropertyValue propertyValue : propertyValues) {
33
            String propertyName = propertyValue.getName();
34
            String value = propertyValue.getValue();
35
            String ref = propertyValue.getRef();
36
            if(ref != null && !"".equals(ref)) {
37
​
38
                Object bean = getBean(ref);
39
                String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName);
40
                Method[] methods = clazz.getMethods();
41
                for (Method method : methods) {
42
                    if(method.getName().equals(methodName)) {
43
                        method.invoke(beanObj,bean);
44
                    }
45
                }
46
            }
47
​
48
            if(value != null && !"".equals(value)) {
49
                String methodName = StringUtils.getSetterMethodNameByFieldName(propertyName);
50
                Method method = clazz.getMethod(methodName, String.class);
51
                method.invoke(beanObj,value);
52
            }
53
        }
54
        singletonObjects.put(name,beanObj);
55
        return beanObj;
56
    }
57
​
58
    @Override
59
    public <T> T getBean(String name, Class<? extends T> clazz) throws Exception {
60
​
61
        Object bean = getBean(name);
62
        if(bean != null) {
63
            return clazz.cast(bean);
64
        }
65
        return null;
66
    }
67
}

 

7.4.5 自定义Spring IOC总结

7.4.5.1 使用到的设计模式

  • 工厂模式。这个使用工厂模式 + 配置文件的方式。
  • 单例模式。Spring IOC管理的bean对象都是单例的,此处的单例不是通过构造器进行单例的控制的,而是spring框架对每一个bean只创建了一个对象。
  • 模板方法模式。AbstractApplicationContext类中的finishBeanInitialization()方法调用了子类的getBean()方法,因为getBean()的实现和环境息息相关。
  • 迭代器模式。对于MutablePropertyValues类定义使用到了迭代器模式,因为此类存储并管理PropertyValue对象,也属于一个容器,所以给该容器提供一个遍历方式。

spring框架其实使用到了很多设计模式,如AOP使用到了代理模式,选择JDK代理或者CGLIB代理使用到了策略模式,还有适配器模式,装饰者模式,观察者模式等。

7.4.5.2 符合大部分设计原则

7.4.5.3 整个设计和Spring的设计还是有一定的出入

spring框架底层是很复杂的,进行了很深入的封装,并对外提供了很好的扩展性。而我们自定义SpringIOC有以下几个目的:

  • 了解Spring底层对对象的大体管理机制。
  • 了解设计模式在具体的开发中的使用。
  • 以后学习spring源码,通过该案例的实现,可以降低spring学习的入门成本。